WordPress 誕生 20 年

Matt Mullenweg 寫了一篇文章簡單提到 WordPress 誕生 20 年:「WP20 & Audrey Scholars」。

雖然 Matt Mullenweg 在文章裡都沒提到,但 WordPress 的興起其實跟當年 2004 年最大的 blog 軟體 Movable Type 自己出的包有很大的關係:

With the release of version 3.0 in 2004, there were marked changes in Movable Type's licensing, most notably placing greater restrictions on its use without paying a licensing fee. This sparked criticism from some users of the software, with some moving to the then-new open-source blogging tool WordPress. With the release of Movable Type 3.2, the ability to create an unlimited number of weblogs at all licensing levels was restored. In Movable Type 3.3, the product once again became completely free for personal users.

當年 hlb 在社團主機上用 Movable Type 架了服務讓大家寫,結果後來發生了 license 問題,大家就都順勢跑到 WordPress 上了;而等到 Movable Type 再次想放寬 license 的時候已經來不及了,大家都已經搬完了。

翻了一下最舊的文章 (在另外一個 WordPress 上) 是在 2004 年十月的時候寫的,就有提到當時從 Movable Type 換到 WordPress 的考量:「開場:為什麼用 WordPress」。

用 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 做雲端歌單自己搞半天...

Amazon EC2 的 Trn1 正式開放使用

AWS 自家研發晶片的 trn1.* 上線了:「Amazon EC2 Trn1 Instances for High-Performance Model Training are Now Available」。

先前三家雲端的廠商只有 Google Cloud PlatformTPU 可以 train & evaluate,現在 AWS 推出 AWS Trainium,補上 train 這塊的產品。其中官方宣稱可以比 GPU 架構少 50% 的計算成本:

Trainium-based EC2 Trn1 instances solve this challenge by delivering faster time-to-train while offering up to 50% cost-to-train savings over comparable GPU-based instances.

然後 PyTorchTensorFlow 都有支援:

The Neuron plugin natively integrates with popular ML frameworks, such as PyTorch and TensorFlow.

另外用 neuron-ls 可以看到 Neuron 裝置的資訊,不過沒看懂為什麼要 mask 掉 private ip 的資訊:

大型的 cluster 會使用 Amazon FSx for Lustre 整合提供服務:

For large-scale model training, Trn1 instances integrate with Amazon FSx for Lustre high-performance storage and are deployed in EC2 UltraClusters. EC2 UltraClusters are hyperscale clusters interconnected with a non-blocking petabit-scale network.

但第一波開放的區域有點少,只有萬年美東一區 us-east-1 與美西二區 us-west-2

You can launch Trn1 instances today in the AWS US East (N. Virginia) and US West (Oregon) Regions as On-Demand, Reserved, and Spot Instances or as part of a Savings Plan.

us-east-1trn1.2xlarge 的價錢是 US$1.34375/hr,但沒有實際跑過比較好像沒辦法評估到底行不行...

但總算是擺出個產品對打看看,畢竟要夠大才能去訂製這些東西。

目前 AWS 台北區只能開 *.2xlarge 的機器

前面在「AWS 的台北區 (Local Zone) 開了」這邊有提到機器開不起來,剛剛查價錢的時候才發現只能開 {c5,g4dn,m5,r5}.2xlarge

改成 c5.2xlarge 然後就開起來了:

翻了目前所有的 local zone,看起來大多都是類似的情況,選擇性會很少... 目前只有邁阿密與洛杉磯的選擇比較多,這是邁阿密:

這是洛杉磯:

這樣目前要拿來當 VPS 取代品還不太好用,就真的是 local zone 的定位。

歐盟 2024 年年底強制使用 USB-C 充電頭 (終於,iPhone...)

Hacker News Daily 上看到「EU Passes Law to Switch iPhone to USB-C by End of 2024」,裡面指到了歐盟的新聞稿:「Long-awaited common charger for mobile devices will be a reality in 2024」。

2024 年年底 (所以是 2025 年) 將強制手機與平板都使用 USB-C 充電頭,2026 年則是延伸涵蓋到筆電:

By the end of 2024, all mobile phones, tablets and cameras sold in the EU will have to be equipped with a USB Type-C charging port. From spring 2026, the obligation will extend to laptops.

終於定案公告了,之前傳言好久了...

電子紙的螢幕 BOOX Mira Series

看到「BOOX Mira Series」這個,主要是因為現在公司的老闆是電子墨水的發明人之一 (也是 E Ink 的 cofounder),所以看到一些電子墨水的新聞也會覺得有趣...

比較小的 BOOX Mira 是 13.3" 的 4:3 螢幕,但 resolution 也已經拉到 2200x1650 了,207 ppi,走 Mini HDMI 或是 Type C 輸入,賣 US$799,從圖片上看起來像是攜帶性的。

而比較大的 BOOX Mira Pro 則是 25.3" 的 16:9 螢幕 (3200x1800),145 ppi,可以走 HDMI/Mini HDMI/DP 或是 Type C,價錢則是 US$1799,從圖片上看起來是放在桌面上的。

因為電子紙的特性,畫面的更新速度不像傳統螢幕那樣,要想一下使用的情境...

AWS 將新的 Nitro 架構回過投來支援以前 Xen 的機種

Twitter 上看到 Jeff Barr 提到的這篇,講 AWS 決定讓新的 Nitro 架構支援舊的 Xen 機種:

原文是「Xen-on-Nitro: AWS Nitro for Legacy Instances」這篇,裡面雖然很美化的在講這件事情,但提到了幾個很現實的問題,第一個是仍然有大量使用者 (120 萬) 在用 Xen 架構的機器:

Today, we still have over 1.2 Million unique customers using Xen-based instances.

但這些機器其實愈來愈難維護,一方面是 Nitro 讓 AWS 省下很多軟體上的維護,另外一方面是幾乎不會有新的使用者用這些舊機種,在採購上面也會是問題。

However, the underlying hardware is old and it’s getting increasingly difficult to maintain support for these older hypervisor systems.

所以 EC2 的團隊把 Nitro 的 Xen 相容架構給實做出來,從 2022 年開始就可以全部都用 Nitro 系統,這樣對 EC2 團隊的維護成本就會大幅下降:

All of these innovations enable us to continue to offer many of our older instance types well past the lifetime of the original hardware. Starting in 2022, customers launching M1, M2, M3, C1, C3, R3, I2 and T1 instances will land on Nitro supported instances hardware and existing running instances will also be migrated.

技術債沒辦法消失,就用這種方式降低維護成本耗 XD

SQLite 3.37.0 以及 STRICT table 的設計

Hacker News 首頁上看到「SQLite Release 3.37.0 (sqlite.org)」,原文在「SQLite Release 3.37.0 On 2021-11-27」這邊。

這個版本引入了 STRICT Tables,先前在「SQLite 目前在規劃的 Strict Table,以及我從來不知道原來可以這樣惡搞...」這邊有提過。

官方給出來的範例是這樣,如果沒有要求 STRICT 的話,可以看到各種變化:

CREATE TABLE t1(a ANY);
INSERT INTO t1 VALUES('000123');
SELECT typeof(a), quote(a) FROM t1;
-- result: integer 123

加上 STRICT 後就會與「預期」的結果比較接近:

CREATE TABLE t1(a ANY) STRICT;
INSERT INTO t1 VALUES('000123');
SELECT typeof(a), quote(a) FROM t1;
-- result: text '000123'

對於希望 database 在處理資料嚴謹一點的人來說,應該是個不錯的新功能,但畢竟不是預設值,對於剛跨進來用的人應該還是有中獎機會 XD

用 PostgreSQL 的 int4range 與 GiST

發現自己根本還不熟悉 PostgreSQL 的特性,寫一下記錄起來。

產品上常常會有 coupon 與 voucher 之類的設計,這時候通常都會設定 coupon 或 voucher 的有效期間,在 MySQL 的環境下可能會這樣設計:

CREATE TABLE coupon (
  id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  code VARCHAR(255) NOT NULL,
  started_at INT UNSIGNED NOT NULL,
  ended_at INT UNSIGNED NOT NULL
);

另外是設計 index 的部份,在產品推出夠久後,通常是過期的 coupon 或 voucher 會比目前還有效的多,而還沒生效的 coupon 與 voucher 通常都不多,所以會設計成對 ended_at 放一組 B-tree index:

CREATE INDEX ON t1 (ended_at);

這個設計不算差,不過用了一些假設。

如果不想要用這些假設,可以改用 Spatial 的資料型態去模擬並且加上 index (使用到 LineString Class),這樣就直接對 a < x < b 這類查詢更有效率,不過缺點就是可讀性會比較差。

在 PostgreSQL 這邊就有更清晰的資料結構來處理這些事情,主要是有一般性的 int4rangeint8range 以及時間類的 tsrangetstzrangedaterange (參考「Range Types」這邊有更多資料型態),所以會變成:

CREATE TABLE coupon (
  id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  code VARCHAR NOT NULL,
  active_at INT4RANGE NOT NULL
);

然後用 GIST 建立 index:

CREATE INDEX ON t1 USING GIST(active_at);

後續的 query 語法就用 <@ 的語法:

SELECT COUNT(*) FROM coupon WHERE 10000 <@ active_at;

塞了 10M 筆資料後的 table 可以看到本來需要的時間是:

Time: 779.542 ms

變成:

Time: 5.510 ms

不過缺點就是 SQLite 沒支援這些資料型態,對於 test case 就一定得跑個 PostgreSQL 起來測...

SQLite 目前在規劃的 Strict Table,以及我從來不知道原來可以這樣惡搞...

Hacker News Daily 上看到「STRICT Tables」這篇,在講 SQLite 目前在規劃 strict table,對應的討論可以參考「Strict Tables – Column type constraints in SQLite - Draft (sqlite.org)」這邊。

我在 draft 文件開頭看到這個驚人的事實:轉型失敗的時候會直接寫進去,不是錯誤或是 0 或是 NULL 之類的值 XDDD

For example, if a table column has a type of "INTEGER", then SQLite tries to convert anything inserted into that column into an integer. So an attempt to insert the string '123' results in an integer 123 being inserted. But if the content cannot be losslessly converted into an integer, for example if the input is 'xyz', then the original string is inserted instead. See the Datatypes In SQLite document for additional information.

實際上測試建了一個表格測試:

SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> CREATE TABLE a (id INTEGER PRIMARY KEY NOT NULL, col1 INTEGER);
sqlite> INSERT INTO a (id, col1) VALUES (1, 'a');
sqlite> SELECT * FROM a;
id          col1      
----------  ----------
1           a         

我果然跟 SQLite 不熟...