把 RabbitMQ 換成 PostgreSQL 的那篇文章...

Hacker News 上看到「SQL Maxis: Why We Ditched RabbitMQ and Replaced It with a Postgres Queue (prequel.co)」這篇文章,原文在「SQL Maxis: Why We Ditched RabbitMQ And Replaced It With A Postgres Queue」這邊,裡面在講他們把 RabbitMQ 換成 PostgreSQL 的前因後果。

文章裡面可以吐嘈的點其實蠻多的,而且在 Hacker News 上也有被點出來,像是有人就有提到他們遇到了 bug (或是 feature) 卻不解決 bug,而是決定直接改寫成用 PostgreSQL 來解決,其實很怪:

In summary -- their RabbitMQ consumer library and config is broken in that their consumers are fetching additional messages when they shouldn't. I've never seen this in years of dealing with RabbitMQ. This caused a cascading failure in that consumers were unable to grab messages, rightfully, when only one of the messages was manually ack'ed. Fixing this one fetch issue with their consumer would have fixed the entire problem. Switching to pg probably caused them to rewrite their message fetching code, which probably fixed the underlying issue.

另外一個吐嘈的點是量的部份,如果就這樣的量,用 PostgreSQL 降低使用的 tech stack 應該是個不錯的決定 (但另外一個問題就是,當初為什麼要導入 RabbitMQ...):

>To make all of this run smoothly, we enqueue and dequeue thousands of jobs every day.

If you your needs aren't that expensive, and you don't anticipate growing a ton, then it's probably a smart technical decision to minimize your operational stack. Assuming 10k/jobs a day, thats roughly 7 jobs per minute. Even the most unoptimized database should be able to handle this.

在同一個 thread 下面也有人提到這個量真的很小,甚至直接不講武德提到可以用 Jenkins 解 XD:

Years of being bullshitted have taught me to instantly distrust anyone who is telling me about how many things they do per day. Jobs or customers per day is something to tell you banker, or investors. For tech people it’s per second, per minute, maybe per hour, or self aggrandizement.

A million requests a day sounds really impressive, but it’s 12req/s which is not a lot. I had a project that needed 100 req/s ages ago. That was considered a reasonably complex problem but not world class, and only because C10k was an open problem. Now you could do that with a single 8xlarge. You don’t even need a cluster.

10k tasks a day is 7 per minute. You could do that with Jenkins.

然後意外看到 Simon Willison 提到了一個重點,就是 RabbitMQ 到現在還是不支援 ACID 等級的 job queuing (尤其是 Durability 的部份),也就是希望 MQ 系統回報成功收到的 task 一定會被處理:

The best thing about using PostgreSQL for a queue is that you can benefit from transactions: only queue a job if the related data is 100% guaranteed to have been written to the database, in such a way that it's not possible for the queue entry not to be written.

Brandur wrote a great piece about a related pattern here: https://brandur.org/job-drain

He recommends using a transactional "staging" queue in your database which is then written out to your actual queue by a separate process.

這也是當年為什麼用 MySQL 幹類似的事情,要 ACID 的特性來確保內容不會掉。

這也是目前我覺得唯一還需要用 RDBMS 當 queue backend 的地方,但原文公司的想法就很迷,遇到 library bug 後決定換架構,而不是想辦法解 bug,還很開心的寫一篇文章來宣傳...

npm 裡的 redis 與 ioredis

前幾天在噗浪的偷偷說上看到有人提到 npmtrends 上的 redis (官方的) 與 ioredis:「https://www.plurk.com/p/p6wdc9」。

意外發現以下載量來看,ioredis 已經超越官方的 redis 了:

找了一下差異,看起來的確有些團隊在 loading 很高的情況下會考慮用 ioredis 取代 redis:「Migrating from Node Redis to Ioredis: a slightly bumpy but faster road」。

但沒有特別需求的話應該還是會用官方版本?

AWS 官方推出了自己的 Amazon S3 FUSE 套件

看到「Mountpoint for Amazon S3」這個專案,AWS 自己推出了自己的 Amazon S3 FUSE 套件。Hacker News 上也有一些討論:「Mountpoint – file client for S3 written in Rust, from AWS (github.com/awslabs)」。

Amazon S3 的價錢比其他 AWS 提供的 storage 都便宜不少。以美東第一區 us-east-1 來說,S3 是 $0.023/GB,而 EBS (gp3) 要 $0.08/GB,即使是 EBS (st1) 也要 $0.045/GB。

S3 相較於 EBS 來說,多了 API call 的費用,所以對於不會產生大量 API call 的應用來說 (像是常常會寫很大包的資料到檔案裡),透過 FUSE 操作 Amazon S3 可以讓現有的套裝軟體或是程式直接跑上去。

另外一個常見的應用是讓套裝軟體或是現成的程式可以讀取 S3 的資料。

之前這類應用馬上會想到的專案是 s3fs-fuse,這個專案很久了,大家也都知道多人寫入的部份會是痛點。

這次 AWS 自己出來做的事情有點重工,看起來他想做的事情 s3fs-fuse 都解的差不多了,目前看起來唯一的賣點應該只有 Rust-based,但 s3fs-fuse 主要是 C++,其實也沒差到哪裡:

Mountpoint for Amazon S3 is optimized for read-heavy workloads that need high throughput. It intentionally does not implement the full POSIX specification for file systems.

目前專案還是 alpha release,不確定專案的方向到底是什麼...

用 Automerge 處理 CRDT 問題

上個月看到 Automerge 出了 2.0 版的消息:「Automerge 2.0」,Automerge 這個套件可以幫你處理複雜的 CRDT 結構 (Conflict-free replicated data type)。

可以看到 Automerge 在 2.0 之後的效能改善不少,可以跟 yjs 比較了:

所以練了一下手測界面怎麼用,另外也看一下 conflict 時的處理方式。

這邊先產生 hello, world.,然後做了三個操作,第一個是把開頭的 h 改成 H;第二個是把 world 改成 test;第三個是把 world 改成 example

(() => {
  const Automerge = require('@automerge/automerge');

  let doc1 = Automerge.init();

  doc1 = Automerge.change(doc1, 'Init', doc => {
    doc.text = new Automerge.Text();
    doc.text.insertAt(0, 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.');
  });
  
  let doc2 = Automerge.clone(doc1);
  let doc3 = Automerge.clone(doc1);

  doc1 = Automerge.change(doc1, 'Capitalize', doc => {
    doc.text.deleteAt(0);
    doc.text.insertAt(0, 'H');
  });
  doc2 = Automerge.change(doc2, 'world => test', doc => {
    delete doc.text.deleteAt(7, 5);
    doc.text.insertAt(7, 'test');
  });
  doc3 = Automerge.change(doc3, 'world => example', doc => {
    delete doc.text.deleteAt(7, 5);
    doc.text.insertAt(7, 'example');
  });

  let finalDoc = Automerge.merge(doc1, doc2);
  finalDoc = Automerge.merge(finalDoc, doc3);
  console.log(finalDoc);
})();

這樣最後會產生出 Hello, testexample.

{
  text: Text {
    elems: [
      'H', 'e', 'l', 'l', 'o',
      ',', ' ', 't', 'e', 's',
      't', 'e', 'x', 'a', 'm',
      'p', 'l', 'e', '.'
    ]
  }
}

這結果看起來還行。

只能說以前要是有這些 library 就好了,當初在 KKBOX 做雲端歌單自己搞半天...

Intel 用 AVX-512 加速 NumPy 的排序演算法被整合進主線了

IntelAVX-512 加速 NumPy 排序的實做被整合進主線了:「「Intel Publishes Blazing Fast AVX-512 Sorting Library, Numpy Switching To It For 10~17x Faster Sorts」」。

GitHub 的 PR 在「ENH: Vectorize quicksort for 16-bit and 64-bit dtype using AVX512 #22315」這邊,可以看到相關的留言:

This patch adds AVX512 based 64-bit on AVX512-SKX and 16-bit sorting on AVX512-ICL. All the AVX512 sorting code has been reformatted as a separate header files and put in a separate folder. The AVX512 64-bit sorting is nearly 10x faster and AVX512 16-bit sorting is nearly 16x faster when compared to std::sort. Still working on running NumPy benchmarks to get exact benchmark numbers

16-bit int sped up by 17x and float64 by nearly 10x for random arrays. Benchmarked on a 11th Gen Tigerlake i7-1165G7.

有點「有趣」的情況是,AVX-512 在新的 Intel 消費級 CPU 被拔掉了,只有伺服器工作站的 CPU 有保留。而 AMDZen 4 則是跳下去支援 AVX-512...

另外在「Intel Publishes Fast AVX-512 Sorting Library, 10~17x Faster Sorts in NumPy (phoronix.com)」這邊當然也有人提到,如果用更廣泛的 AVX2 (寬度是 256bits) 加速的話應該也會有很大的進步才對?幾乎這十年的 CPU 都有 AVX2 了... 不過看起來沒有什麼深入的討論?

jQuery 3.6.2

看到 jQuery 出新版的消息:「jQuery 3.6.2 Released!」。

這個版本的說明裡面針對了 :has() 這個功能著墨,主要還是因為這功能 jQuery 支援很久了,而 ChromiumSafai 在最近都開始支援了:「Issue 669058: CSS selectors Level 4: support :has()」、「Using :has() as a CSS Parent Selector and much more」。

不過看起來 jQuery 要整合原生的 :has() 還是有技術困難,因為裡面可能會包括 jQuery 自己定義的 selector extension:

That was problematic in cases where :has() contained another jQuery selector extension (e.g. :has(:contains("Item"))) or contained itself (:has(div:has(a))).

總之,難得又看到 jQuery 的消息... 前一版的 3.6.1 是 2022/08/26,而 3.6.0 則是再一年多前的 2021/03/02,再往前面是 3.5.1 的 2020/05/04 與 3.5.0 的 2020/04/10。

是個歷史...

calloc() 與 malloc() 的差異

前陣子在 Hacker News Daily 上看到的,原文是 2016 的文章:「Why does calloc exist?」,裡面講的東西包括了 implementation dependent 的項目,所以要注意一下他的結論未必適用於所有的平台與情境。

malloc()calloc() 的用法是這樣,其中 calloc() 會申請 countsize 的空間:

void* buffer1 = malloc(size);
void* buffer2 = calloc(count, size);

第一個差異是,count * size 可能會 overflow (而 integer overflow 在 C 裡面是 undefined behavior),這點除非你在乘法時有檢查,不然大多數的行為都還是會生一個值出來。

calloc() 則是會幫你檢查,如果會發生 overflow 的時候就不會真的去要一塊記憶體用。

第二個差異是 calloc() 保證會將內容都設定為 0,這點在 POSIX 的標準裡面是這樣寫的:

The calloc() function shall allocate unused space for an array of nelem elements each of whose size in bytes is elsize. The space shall be initialized to all bits 0.

但作者就發現 malloc() + memset() + free() 還是比 calloc() + free() 慢很多:

~$ gcc calloc-1GiB-demo.c -o calloc-1GiB-demo
~$ ./calloc-1GiB-demo
calloc+free 1 GiB: 3.44 ms
malloc+memset+free 1 GiB: 365.00 ms

研究發現是 calloc() 用了 copy-on-write 的技巧,先把所有的 page 都指到同一塊完全被塞 0 的記憶體,只有在真的寫到該段記憶體時,系統才會要一塊空間來用:

Instead, it fakes it, using virtual memory: it takes a single 4 KiB page of memory that is already full of zeros (which it keeps around for just this purpose), and maps 1 GiB / 4 KiB = 262144 copy-on-write copies of it into our process's address space. So the first time we actually write to each of those 262144 pages, then at that point the kernel has to go and find a real page of RAM, write zeros to it, and then quickly swap it in place of the "virtual" page that was there before. But this happens lazily, on a page-by-page basis.

但畢竟這是 implementation dependent,看看有個印象就好。

AOM-AV1 v3.5.0 的效能改善

在翻資料的時候翻到「Google Releases AOM-AV1 3.5 With More Speedups & Memory Optimizations」這篇,講 AOM-AV1 v3.5.0 的效能改善,看了 v3.4.0 的 changelog 可以看到最主要的大幫助是有 multithreading 的支援,但不確定跟其他的 AV1 library 相比可以有多快...

印象中 (好幾個月前的印象) 目前最快的應該是 SVT-AV1?記得 dav1d 有宣稱很快但不是 apple-to-apple 比較,比較像是 marketing 上的宣稱。

先前測試 AV1 在 Android 上的支援度與效能,記得在老一點的手機上軟解還是很辛苦啊,最後還是先選了 VP9 用,硬體解碼支援度比較廣,加上軟解需要的 CPU resource 也低不少。

OpenSSL 3.0 RCE

OpenSSL 居然出了一包 RCE:「CVE-2022-3786 and CVE-2022-3602: X.509 Email Address Buffer Overflows」,security advisory 在「OpenSSL Security Advisory [01 November 2022]」這邊。

看第一個 CVE-2022-3602,本來看到這個 CVE 號碼為覺得應該是年初的號碼 (現在已經三萬多了?),但依照官方的說法是十月的事情,看起來是用假名回報 (Polar Bear):

This issue was reported to OpenSSL on 17th October 2022 by Polar Bear.
The fixes were developed by Dr Paul Dale.

在 CVE 的頁面上也可以看到「Date Record Created」這邊標的是 20221019,不知道是什麼情況。

另外一組 CVE-2022-3786 也是類似的情況,官方收到也是十月的事情:

This issue was discovered on 18th October 2022 by Viktor Dukhovni while
researching CVE-2022-3602. The fixes were developed by Dr Paul Dale.

在 CVE 頁面上則是標 20221101 建立,昨天的事情。

這次出事的範圍是 3.0.0 系列的 OpenSSL,前面的 1.1 與 1.0 系列是沒中的:

[T]he bugs were introduced as part of punycode decoding functionality (currently only used for processing email address name constraints in X.509 certificates). This code was first introduced in OpenSSL 3.0.0. OpenSSL 1.0.2, 1.1.1 and other earlier versions are not affected.

Ubuntu 上中的是 22.04 的 jammy 以及之後的版本,包括了 22.10 的 kinetic。還在用 20.04 的基本上都還是 1.1 系列的 OpenSSL 在跑,這次沒被貓到。

Python 上的 reals 套件 (需要 3.10+ 以上才能裝)

看到「A lightweight python3 library for arithmetic with real numbers.」這個有趣的 Python 延伸套件,可以用他進行高精度的實數運算...

一開始在 Python 3.9 環境裝,結果就跳出需要 3.10+ 的環境,想了一下,開了一個 Docker container 裝 pyenv 來測,測過以後覺得還蠻有趣的,看起來之後把預設環境變成 3.10+ 應該會裝起來用...

這個 reals 的重點在於保證顯示數字的正確性:

It allows you to compute approximations to an arbitrary degree of precision, and, contrary to most other libraries, guarantees that all digits it displays are correct.

目前支援的常數與操作有這些:

Constants: pi, e, phi
Functions related to powers: sqrt, exp, log
Operators: negation, addition, subtraction, multiplication, division, powers
Trigonometric functions: sin, sinh, csc, csch, cos, cosh, sec, sech, tan, tanh, cot, coth

用法的部份,先把 reals 拉進來:

>>> from reals import sqrt

然後用法算直覺:

>>> sqrt2 = sqrt(2)
>>> sqrt2
<reals._real.Real object at 0x10d182560 (approximate value: 1.41421)>
>>> sqrt2.evaluate(10)
'1.4142135624'
>>> '{:.10f}'.format(sqrt2)
'1.4142135624'
>>> sqrt2.to_decimal(10)
Decimal('1.4142135624')

不過作者有提到效能沒有處理到很好,所以應該是拿來快速做一些運算得到結果而已。