PostgreSQL 的 SERIALIZABLE 的 bug

這是 Jespen 第一次測試 PostgreSQL,就順利找出可重製的 bug 了:「PostgreSQL 12.3」。

第一個 bug 是 REPEATABLE READ 下的問題,不過因為 SQL-92 定義不夠嚴謹的關係,其實算不算是 bug 有討論的空間,這點作者 Kyle Kingsbury 在文章裡也有提出來:

Whether PostgreSQL’s repeatable-read behavior is correct therefore depends on one’s interpretation of the standard. It is surprising that a database based on snapshot isolation would reject the strict interpretation chosen by the seminal paper on SI, but on reflection, the behavior is defensible.

另外一個就比較沒問題了,是 SERIALIZABLE 下的 bug,在 SQL-92 下對 SERIALIZABLE 的定義是這樣:

The execution of concurrent SQL-transactions at isolation level SERIALIZABLE is guaranteed to be serializable. A serializable execution is defined to be an execution of the operations of concurrently executing SQL-transactions that produces the same effect as some serial execution of those same SQL-transactions. A serial execution is one in which each SQL-transaction executes to completion before the next SQL-transaction begins.

也就是說,在 SERIALIZABLE 下一堆 transaction 的執行結果,你至少可以找到一組排序,使得這些 transaction 的結果是等價的。

而 Jespen 順利找出了一組 transaction (兩個 transaction),在 SERIALIZABLE 下都成功 (但不應該成功):

對於這兩個 transaction,不論是上面這條先執行,還是下面這條先執行,都不存在等價的結果,所以不符合 SERIALIZABLE 的要求。

另外也找到一個包括三個 transaction 的情況:

把 transaction 依照執行的結果把 dependency 拉出來,就可以看出來裡面產生了 loop,代表不可能在 SERIALIZABLE 下三個都成功。

在 Jespen 找到這些 bug 後,PostgreSQL 方面也找到軟體內產生 bug 的部份,並且修正了:「Avoid update conflict out serialization anomalies.」,看起來是在 PostgreSQL 引入 Serializable Snapshot Isolation (SSI) 的時候就有這個 bug,所以 9.1 以後的版本都有這個問題...

這次順利打下來,測得很漂亮啊... 翻了一下 Jespen 上的記錄,發現好像還沒測過 MySQL,應該會是後續的目標?

SQL-92 裡定義 Isolation Level 的背景

Twitter 上看到這則推,講到在 SQL-92 裡面 Isolation Level 定義的背景:

先是講了為什麼有 SERIALIZABLEREPEATABLE READREAD COMMITTED,然後講為什麼是用 anomalies 定義 (除了 SERIALIZABLE),也因此造成了定義不清楚而導致問題。

SQL-92 的 isolation 問題後來在其他文件裡面有被討論,像是 1995 年的「A Critique of ANSI SQL Isolation Levels」,以及 2000 年的論文「Generalized Isolation Level Definitions」,過了二十年後的現在,大家也都大概知道有哪些雷區了。

另外講到 Isolation Level,實務上會希望知道 database 與標準之間的差異,在「Hermitage: Testing transaction isolation levels」這篇可以看到各家 RDBMS 在不同設定下實際的 isolation level,包括了 open source 的 MySQLPostgreSQL 與商用常遇到的 OracleMicrosoft SQL Server

MySQL 的 REPEATABLE READ 因為 SQL-92 的定義不清楚,所以大概知道這邊本來就有爭議,比較意外的反倒是 Oracle 裡面的 SERIALIZABLE 實際上是 Snapshot Isolation,沒有辦法達到 SQL-92 裡面最高等級的 Isolation Level。

然後發現有些知識還是有漏,趁這個機會補...

SQL Antipatterns: Avoiding the Pitfalls of Database Programming

標題裡是說這本書:「SQL Antipatterns: Avoiding the Pitfalls of Database Programming」,在 2010 年出版的書。

我是在 Hacker News Daily 上看到「Ask HN: What are some examples of good database schema designs?」這篇,裡面提到了這本書,看了一下章節看到只有 USD$25 就馬上先買起來丟到 Kindle 裡面...

這不是給資料庫初學者看的書,主要的讀者是對於「標準」夠熟 (學校教的那些理論基礎,像是各種 index 的底層結構,正規化的方法,以及正規化的原因),然後也有一些實務經驗後的開發者。

因為裡面把常遇到的問題,與可能的解決方式 (通常都違反當初在學校學的理論基礎) 整理成這本書,在底子還沒打穩前跑來看這本書並不是個好主意...

另外裡面雖然不一定有提到,但有蠻多技巧其實是用在「為了效能而不則手段」的情境下,所以這些內容對於想要調校比較大的網站應該也是很有幫助。

從 Oracle 轉移到 PostgreSQL 的工具 Orafce

在「Migrating from Oracle to PostgreSQL: Tips and Tricks」這邊看到在討論怎麼從 Oracle 資料庫轉移到 PostgreSQL 上。

開頭介紹了 Orafce,實做了一些 Oracle 上的 function,可以使得轉移時不用改寫太多 SQL:

The "orafce" project implements of some functions from the Oracle database. The functionality was verified on Oracle 10g, and the module is useful for production work.

先記錄起來,之後如果有機會的話比較好找...

把 PostgreSQL 的 EXPLAIN 轉成 Flamegraph

Hacker News Daily 上看到 mgartner/pg_flame 這個專案,可以把 PostgreSQLEXPLAIN 結果 (JSON 格式) 轉成 Flamegraph (用 HTML 呈現):

不過我是直接看 EXPLAIN 的輸出比較習慣... 但如果需要做投影片的時候,應該是個好工具?

RDBMS 裡的各種 Lock 與 Isolation Level

來推薦其他人寫的文章 (雖然是在 Medium 上...):「複習資料庫的 Isolation Level 與圖解五個常見的 Race Conditions」、「對於 MySQL Repeatable Read Isolation 常見的三個誤解」,另外再推薦英文維基百科上的「Snapshot isolation」條目。

兩篇文章都是中文 (另外一個是英文維基百科條目),就不重複講了,這邊主要是拉條目的內容記錄起來,然後寫一些感想...

SQL-92 定義 Isolation 的時候,技術還沒有這麼成熟,所以當時在訂的時候其實是以當時的技術背景設計 Isolation,所以當技術發展起來後,發生了一些 SQL-92 的定義沒那麼好用的情況:

Unfortunately, the ANSI SQL-92 standard was written with a lock-based database in mind, and hence is rather vague when applied to MVCC systems. Berenson et al. wrote a paper in 1995 critiquing the SQL standard, and cited snapshot isolation as an example of an isolation level that did not exhibit the standard anomalies described in the ANSI SQL-92 standard, yet still had anomalous behaviour when compared with serializable transactions.

其中一個就是 Snapshot Isolation,近代的資料庫系統都用這個概念實做,但實際上又有不少差別...

另外「Jepsen: MariaDB Galera Cluster」這篇裡出現的這張也很有用,裡面描述了不同層級之間會發生的問題:

這算是當系統有一點規模時 (i.e. 不太可能使用 SERIALIZABLE 避免這類問題),開發者需要了解的資料庫限制...

在 SQL 裡面避免大量刪除資料的方式

看到 Percona 的「An Overview of Sharding in PostgreSQL and How it Relates to MongoDB’s」這篇,雖然是在講 PostgreSQL 上的 sharding (以及 partition),突然想到好像沒寫過要怎麼避免大量刪除資料的操作...

一個常見的情境是,想要讓某個表格只保留這一個月的資料,所以每個月開頭都會跑一隻 cron job 負責刪掉上個月的資料,像是 DELETE FROM xxx WHERE timestamp < yyy; 這樣的指令。

這個方式無論是在 PostgreSQL 或是 MySQL 都需要很多時間與 I/O 資源,而透過 partition 將不同時間區段切開到不同的表格,再用 TRUNCATE 直接清空表格剛好可以解這樣的問題。

Percona 的文章裡說了一些 PostgreSQL 的歷史與目前的進展。

在 PostgreSQL 9 或更早以前的版本,一個常見的作法是透過 table inheritance 實做 partition,然後用再用 function 實做 INSERT

CREATE TABLE temperature (
  id BIGSERIAL PRIMARY KEY NOT NULL,
  city_id INT NOT NULL,
  timestamp TIMESTAMP NOT NULL,
  temp DECIMAL(5,2) NOT NULL
);

CREATE TABLE temperature_201901 (CHECK (timestamp >= DATE '2019-01-01' AND timestamp <= DATE '2019-01-31')) INHERITS (temperature);
CREATE TABLE temperature_201902 (CHECK (timestamp >= DATE '2019-02-01' AND timestamp <= DATE '2019-02-28')) INHERITS (temperature);
CREATE TABLE temperature_201903 (CHECK (timestamp >= DATE '2019-03-01' AND timestamp <= DATE '2019-03-31')) INHERITS (temperature);

CREATE OR REPLACE FUNCTION temperature_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
    IF ( NEW.timestamp >= DATE '2019-01-01' AND NEW.timestamp <= DATE '2019-01-31' ) THEN INSERT INTO temperature_201901 VALUES (NEW.*);
    ELSIF ( NEW.timestamp >= DATE '2019-02-01' AND NEW.timestamp <= DATE '2019-02-28' ) THEN INSERT INTO temperature_201902 VALUES (NEW.*);
    ELSIF ( NEW.timestamp >= DATE '2019-03-01' AND NEW.timestamp <= DATE '2019-03-31' ) THEN INSERT INTO temperature_201903 VALUES (NEW.*);
    ELSE RAISE EXCEPTION 'Date out of range!';
    END IF;
    RETURN NULL;
END;
$$
LANGUAGE plpgsql;

在 PostgreSQL 10 之後,就直接支援一些與 partition 相關的設計,像是這樣:

CREATE TABLE temperature (
  id BIGSERIAL NOT NULL,
  city_id INT NOT NULL,
  timestamp TIMESTAMP NOT NULL,
  temp DECIMAL(5,2) NOT NULL
) PARTITION BY RANGE (timestamp);

CREATE TABLE temperature_201901 PARTITION OF temperature FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');
CREATE TABLE temperature_201902 PARTITION OF temperature FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');
CREATE TABLE temperature_201903 PARTITION OF temperature FOR VALUES FROM ('2019-03-01') TO ('2019-04-01');

雖然還是有些限制,但可以看出比起以前簡單不少。

而有了 partition 後,文章的後續就在討論這跟 MongoDB 的 sharding 有什麼關係,但這就不是我關注的事情了...

CockroachDB 也拋棄 Open Source License 了

CockroachDB 的主力在於 PostgreSQL 的相容層 (包括底層資料結構,SQL 語法,以及 Protocol,所以原有的 client 不需要太多修改就可以用),並且提供橫向擴充的能力 (實作類似於 F1 與 Spanner 這些論文的功能)。

現在他們也宣佈拋棄 Open Source License 了,從本來的 Apache License 2.0 轉為他們自己定義的 Business Source License:「Why We’re Relicensing CockroachDB」。

最大的差異就是擋提供服務:

The one and only thing that you cannot do is offer a commercial version of CockroachDB as a service without buying a license.

商業版本最終會以 open source license 釋出,但會有三年延遲 (以現在的社群速度,基本上就等於不提供了),不算太意外,但這樣的話也需要先從可用的列表上移除了...

從 Microsoft SQL Server 轉移到 PostgreSQL 的工具

在「How to Migrate from Microsoft SQL Server to PostgreSQL」這邊看到作者的客戶需要把 Microsoft SQL Server 轉移到 PostgreSQL (但沒有提到原因)。

裡面主要是兩個階段的轉換,第一個階段是 schema 的轉換,作者提到了 dalibo/sqlserver2pgsql 這個用 Perl 寫的工具:

Migration tool to convert a Microsoft SQL Server Database into a PostgreSQL database, as automatically as possible http://dalibo.github.io/sqlserver2pgsql

第二個階段是資料的轉換,是選擇用 Pentaho Data Integration 的 Community Edition:

Pentaho offers various stable data-​centric products. Pentaho Data Integration (PDI) is an ETL tool which provides great support for migrating data between different databases without manual intervention. The community edition of PDI is good enough to perform our task here. It needs to establish a connection to both the source and destination databases. Then it will do the rest of work on migrating data from SQL server to Postgres database by executing a PDI job.

所以用兩個工具串起來... 另外在文章裡面沒提到 stored procedure 之類的問題,應該是他們的客戶沒用到或是很少用到?

大家在猜 Amazon DocumentDB 的底層是不是 PostgreSQL...

Amazon DocumentDB 的出現讓人驚訝的倒不是 AWS 推出這塊服務,而是 AWS 對於這類對 PaaS 有攻擊性的 license model 的反擊姿態。這也導致了在 AWS 推出後 MongoDB 的股價掉了 13%。

另外一方面,大家也都想要知道 AWS 怎麼堆底層的系統,畢竟要從頭開發一個所需要的功夫應該不小... (雖然 AWS 應該有這個能力)

從「Is DocumentDB really PostgreSQL?」這邊看到 Hacker News 上的「My bet is that it is built on top of Aurora PostgreSQL.」這篇討論,透過目前 DocumentDB 的限制,大家在猜 Amazon DocumentDB 是不是拿 PostgreSQL 改出來的...

目前看起來 Identifiers 的 63 chars 限制,單一 collection 的 32TB 限制 (對應到表格),以及 UTF-8 null character 限制,都跟 PostgreSQL 一樣。

也許過一陣子 AWS 的人會找個地方透漏,不過目前看起來只能猜...