DB 系列 · Durability

⚓ 「寫入成功」到底代表什麼

應用程式送出一筆寫入後,資料庫回覆成功,這句成功背後對資料安全保存的承諾強度,每一種資料庫都不一樣,這篇整理把這件事拆開來看。

ACID 的 D WAL / Journal Crash Recovery
01

成功回覆背後藏著什麼

假設應用程式執行了一筆 INSERT INTO orders (...),接著收到資料庫回覆 success。這個 success 其實可能代表很多不同層級的安全程度,從只是進了應用程式自己的記憶體,到已經跨機房複製到其他節點,中間差距非常大。

1
Database process 記憶體

資料先進到資料庫程式自己的記憶體結構裡,這一層本身完全不具備斷電保護能力。

2
OS page cache

作業系統層級的快取,仍然停留在記憶體,尚未真正落到磁碟上。

3
WAL / Journal

描述這次修改內容的日誌被寫進持久儲存,這是多數資料庫定義「已經安全」的最低門檻。

4
SSD / disk persistent storage

資料真正落在磁碟的持久儲存介質上。

5
其他 replica

資料被複製到其他節點,即使整台機器故障,其他節點仍保有這份資料。

⚠️

只停在記憶體的風險:如果資料庫只做到「資料進了 RAM 就回覆成功」,一旦發生斷電,RAM 裡的內容會直接消失,等於已經回報成功的資料實際上並不存在。

寫入
回覆 success
突然斷電
RAM 消失
資料消失
02

比較可靠的做法:WAL / Journal

比較可靠的資料庫通常會使用 WAL(Write-Ahead Log)或 Journal。以 PostgreSQL 為例,每次 transaction commit 並不會立刻把真正的 table data page 全部寫進磁碟,而是先確保描述這次修改的 WAL record 被寫進持久儲存,再告訴客戶端 transaction committed。即使真正的資料頁還沒完成寫入,重新開機後也能利用 WAL 把資料重做回來。PostgreSQL 官方文件也明確說明,正常的 synchronous commit 會等待 WAL flush 到永久儲存後才回覆成功,詳見 WAL Reliability

🧭

這就是 ACID 裡面的 D:Durability。只要資料庫正式告訴使用端 transaction committed,就不應該因為普通的 process crash、OS crash 或 power loss 而消失。

縮寫名稱保證內容
AAtomicity(原子性)一筆 transaction 要嘛全部生效,要嘛完全不生效
CConsistency(一致性)transaction 結束後,資料要符合資料庫定義的所有規則
IIsolation(隔離性)多個 transaction 同時執行時,彼此互不干擾
DDurability(持久性)一旦 commit 成功,就不能因為一般性的當機而消失
03

各家資料庫的 durability 機制一覽

DB主要 durability 機制一般預設行為單機斷電保護多機故障保護
PostgreSQLWAL + fsynccommit 等 WAL 持久化取決於 replication 設定
MySQL / InnoDBRedo Log + fsynccommit flush redo log取決於 replication 設定
MongoDBJournal + Write Concern預設多為 majority很強,可要求 majority
DynamoDBMulti-AZ replicationAWS 自動處理很強預設跨 3 個 AZ
SQL ServerTransaction Logcommit 等 log 持久化取決於 Availability Group 等設定
RedisRDB / AOFdurability 可高度調整預設不像傳統 RDBMS 那麼強取決於 replication / persistence 設定
04

越安全,代價就是效能

這裡碰到一個非常重要的資料庫設計思想:越要求高 durability,一次 write 需要等待的事情就越多,延遲與成本也會跟著上升。

寫 WAL
fsync
replica 1
replica 2
跨 AZ
跨 region
💡

很多 NoSQL 系統真正提供的並不是「不保證資料」,而是把選擇權交出來:由使用端決定這次資料值得付出多少 consistency、durability、latency 成本。

05

不同情境需要的 durability 程度不一樣

  • 銀行轉帳:非常在乎 durability,不能有任何遺失。
  • Like 數計數器:偶爾掉一個可能可以接受。
  • Session cache:掉了之後重新登入也許就足夠。
  • Analytics event:可能可以接受批次或非同步處理。
  • 訂單付款:絕對不能隨便遺失。
重點結論 該記住的不是「NoSQL 會丟資料」

真正該記住的是,不要只看到 API 回傳 success,就假設已經知道這個 success 背後的 durability semantics。現代資料庫的 durability 並不是單純「SQL 有、NoSQL 沒有」,真正的差異是它預設把「成功」定義在哪個層級,以及允許怎麼調整 durability 與 performance 之間的交換。

接下來幾個分頁,依序拆解 PostgreSQL、MySQL、SQL Server、MongoDB、DynamoDB、Redis 各自的 durability 機制與可調整項目。

06

PostgreSQL:WAL 是 durability 的核心

PostgreSQL 正常的 synchronous commit 流程如下,重點在於它並不是等真正的 table data page 寫入 SSD,只需要 WAL 已經 durable 就足夠。

1
UPDATE

修改記憶體裡的 page。

2
產生 WAL

把這次修改描述成一筆 WAL record。

3
WAL 寫入 disk 並 fsync

確保這筆 WAL record 已經落到永久儲存。

4
COMMIT SUCCESS

此時才回覆客戶端 commit 成功。

資料庫是否發生 crash?
YES重新啟動後讀取 WAL,透過 REDO 重建尚未落盤的 data page,已 commit 的資料不會遺失。
NO系統持續正常運作,data page 之後再依內部排程慢慢落盤即可。

PostgreSQL 官方文件正是這樣描述 WAL 的作用:先確保描述修改的 WAL record 進入永久儲存,data page 不需要在每次 commit 時同步落盤。

07

可調整項:synchronous_commit

PostgreSQL 允許把 synchronous_commit 設成 off,流程會變成先回覆成功,稍後才由 WAL writer 補寫。

WAL 還沒 fsync
先回 COMMIT SUCCESS
稍後由 WAL writer 補寫
⚠️

這時如果發生 crash,可能出現「資料庫已經回覆這筆 transaction 成功,但實際上這筆 transaction 還沒真正 durable」的落差,也就是最近幾筆 transaction 可能遺失。PostgreSQL 的設計仍然保證,即使遺失最近的 transaction,也不會因此讓資料庫變成不一致的狀態。

🧨

PostgreSQL 官方特別區分 synchronous_commit=off 與更危險的 fsync=off:後者在 OS 或硬體發生 crash 時,甚至可能導致無法恢復的資料損毀,兩者的風險等級並不相同。

設定commit 回覆時機crash 後果
預設(synchronous commit)WAL durable 之後才回覆 success已 commit 的 transaction 不會遺失
synchronous_commit = off先回覆 success,稍後才補寫 WAL可能遺失最近幾筆 transaction,但資料庫仍保持一致
fsync = off連 WAL 本身都可能沒有真正落盤OS / 硬體 crash 時可能造成不可恢復的損毀
08

MySQL:InnoDB 的概念跟 PostgreSQL 非常像

這裡特別要說 MySQL 加上 InnoDB,因為 MySQL 本身是 DBMS,實際行為會受 storage engine 影響。InnoDB 的核心機制是 Redo Log。

1
UPDATE

修改 buffer pool。

2
寫 redo log

把修改描述寫進 redo log。

3
flush redo log

把 redo log flush 到磁碟。

4
COMMIT success

此時才回覆成功。

09

可調整項:innodb_flush_log_at_trx_commit

設定值行為power loss 後果
1(預設)每次 transaction commit 都 write log 並 flush disk 才回覆成功,這是 MySQL 官方要求的完整 ACID durability 設定不會遺失已 commit 的 transaction
2commit 先 write 並回覆成功,大約每秒才真正 flush 一次可能損失最近尚未 flush 的 transaction
0write 與 flush 都主要變成週期性處理可能損失更大範圍尚未寫入的 transaction
💡

對照兩套系統,可以看到相同的哲學:PostgreSQL 用 WAL 搭配 synchronous_commit 調整 durability 與效能,MySQL InnoDB 用 Redo Log 搭配 innodb_flush_log_at_trx_commit 調整同一件事,兩者的取捨方式非常接近。

另外,如果 MySQL 涉及 binary log 與 replication,官方對高 durability 的建議還包括同時設定 innodb_flush_log_at_trx_commit = 1sync_binlog = 1,詳見 MySQL 官方文件

10

SQL Server:Transaction Log,本質上是同一派

1
修改 data

先在記憶體裡修改資料頁。

2
寫 Transaction Log

把修改描述寫進 transaction log。

3
log flush disk

確保 log 已經落到永久儲存。

4
COMMIT SUCCESS

Microsoft 把這個預設行為稱為 Fully Durable Transaction。

🧭

到這裡可以看出同一個 pattern:PostgreSQL 的 WAL、MySQL 的 Redo Log、SQL Server 的 Transaction Log,三個名稱不同,但共同的基本思想是不急著把整個 data page 寫完,而是先把「如何恢復這次 transaction」的日誌安全保存下來,再回覆成功。

11

可調整項:Delayed Durability

SQL Server 也提供 Delayed Durability,開啟後流程會變成先回覆成功,log 之後才批次 flush。

Transaction
log 還停留在 memory
COMMIT SUCCESS
之後批次 flush
⚠️

在這種模式下,crash 發生時最近的 transaction 可能會消失。Microsoft 官方也明確表示,delayed durable transaction 要等到 transaction log 真正 flush 到 disk 之後,才算真正變成 durable。

12

MongoDB:Journal 之外,多了 Write Concern

MongoDB 有 Journal 機制,概念跟 WAL 很像,write 之後先進 journal 再進 disk。MongoDB 官方也把 journaling 描述為用 write-ahead logging 提供 crash durability。除此之外,MongoDB 多了一個需要理解的概念:Write Concern,用來決定要等多少節點確認,才算真正完成這次寫入。

Write Concern意義
{ w: 1 }Primary 收到寫入就先回覆 ACK
{ w: "majority" }等多數節點都達到要求,才回覆 ACK
{ j: true }要求相關 write 進入 on-disk journal 之後,才回覆 ACK

MongoDB 官方目前說明,大多數部署情境下 implicit 的預設 write concern 是 { w: "majority" }。在預設 writeConcernMajorityJournalDefault=true 的一般 replica-set 設定中,majority acknowledgment 同時也要求 on-disk journaling,這跟早期 MongoDB 給人的刻板印象已經差很多。

13

majority acknowledgment 涵蓋的失效範圍不同

單機 PostgreSQL 的 WAL 落盤,跟 MongoDB replica set 要求 majority durable,兩者涵蓋的是不同層級的 failure model。

Primary A
Secondary B
Secondary C
寫入並產生 journal
複製 →
確認寫入
尚未確認
💡

如果只有單機的 PostgreSQL,一旦整台伺服器故障,只能靠 replication 或 backup 補救。而 MongoDB 搭配 majority write concern,即使 Primary 整台故障,只要多數節點已經確認寫入,資料仍然安全。這代表沒有「SQL durability 一定大於 NoSQL durability」這種結論,真正要問的是這次寫入要求什麼樣的 acknowledgment semantics。

14

DynamoDB:由 AWS 處理 infrastructure durability

DynamoDB 是 managed distributed database,幾乎不需要思考 fsync、WAL file、SSD controller 或 server crash 這些細節,因為 AWS 已經把底層基礎設施抽象掉了。DynamoDB 在一個 Region 裡會自動把資料複製到三個 Availability Zone,AWS 把這件事作為其內建高 durability 設計的一部分。

AZ-B

另一份資料複本。

AZ-C

第三份資料複本。

因此這裡的 failure model 已經不只是單一硬碟壞掉,而是同時考慮 server failure、storage failure,甚至整個 AZ failure,這正是 managed distributed DB 吸引人的地方。

15

durability 不等於 consistency

⚠️

使用 DynamoDB 時要特別小心一件事:durability 不等於 consistency。資料已經安全保存,跟某次 read 是否立刻看到最新資料,是完全不同的兩件事。

Consistency

目前讀到的是不是最新資料。

後續延伸

ACID 與 CAP 的討論會延伸這個區分。

16

Redis:以 RAM 為中心,Persistence 是額外機制

Redis 的設計中心首先是 RAM,所以一筆 SET user:1 ... 只要進到記憶體就會非常快。Persistence 屬於額外機制,主要有兩種:RDB 與 AOF。

RDB:定期拍照式快照

RDB 比較像定期幫整個資料庫拍一次照片。

1
12:00 snapshot

完成一次完整快照。

2
12:05 snapshot

完成下一次快照。

3
12:09 發生 crash

距離上一次快照已經過了 4 分鐘。

4
12:10 snapshot(未能完成)

這次快照還沒執行,crash 已經發生。

⚠️

如果 crash 發生在 12:09,介於 12:05 快照與下一次快照之間的資料就可能遺失,因為 RDB 只保證「最近一次快照當下」的狀態,不保證快照之間的每一筆寫入。

補充 AOF 的角色

相較於 RDB 的定期快照,AOF 會持續記錄每一筆寫入指令,可以把資料遺失的視窗縮到更小,但需要付出額外的效能與磁碟成本,這也回到最前面提到的原則:durability 越強,代價越高,最終要依情境決定合理的取捨。

整理自 PostgreSQL、MySQL、SQL Server、MongoDB、DynamoDB、Redis 官方文件與社群討論