Dev Study
← データの入口
レッスン 4 / 7·約 8 分·第17期 データの入口

「コミットが返った」は、どこまで届いたことか

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存今ここ

前回は BEGIN と COMMIT で「全部か無しか」の境界を引きました。今回は、その COMMIT が成功を返した時点で、変更がどこまで届いているのかを見ます。

まず結論

既定の PostgreSQL は、変更の記録(WAL)がディスクへ書き出されるのを待ってから COMMIT の成功を返します。だから返った直後にサーバが落ちても変更は残ります。これが ACID の D(永続性)です。

ただしこれは既定値で成り立つ性質で、設定で手放せます。待たずに返す設定にすると、クラッシュで直近のコミットが失われえます。公式はそれを「破損ではなく損失」と書いています。

? 読む前に予測

既定設定の PostgreSQL(1台構成)で COMMIT が成功を返したとき、何が済んでいるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

成功の返事は、何を待ってから来るのか

COMMIT に「成功」が返ってきました。変更はもう安全でしょうか。答えは「何を待ってから返したか」で決まります。PostgreSQL 公式は、既定の動きをこう書きます。

transaction commit is normally synchronous: the server waits for the transaction's WAL records to be flushed to permanent storage before returning a success indication to the client. The client is therefore guaranteed that a transaction reported to be committed will be preserved, even in the event of a server crash immediately after.

WAL は PostgreSQL が変更を書き留めるログです。サーバはその記録が永続記憶へ flush されるのを待ってから成功を返します。だから返った直後に落ちても残る。「コミットが返った」とは、この書き出しが済んだという返事です。 なぜ本体ではなく記録を先に書くのかは、次に読むコース『データベースの仕組み』で扱います。

この待ちは設定 synchronous_commit で決まり、既定は on です。off にすると待ちません。

In off mode, there is no waiting, so there can be a delay between when success is reported to the client and when the transaction is later guaranteed to be safe against a server crash. (The maximum delay is three times wal_writer_delay.)

COMMIT の成功がどの時点で返るか(PostgreSQL、1台構成)
COMMIT を受け取る変更の記録(WAL)ができている
ここで成功を返すか、待つか
on(既定)WAL の flush を待ってから返す
off待たずにすぐ返す
off のときは、返したあとで
WAL writer が書き出す遅れは最大 wal_writer_delay の3倍
この間に落ちると失われうる
  • ・on と off で違うのは、成功を返す時点だけ
  • ・wal_writer_delay の既定は 200ms
具体実物

待たない分だけ、速く返る

既定の設定を確かめてから、1行 INSERT を 5,000 回(自動コミットなので 5,000 コミット)流し、on と off を交互に3回ずつ測りました。

PostgreSQL 18.6 / 5,000 コミットの壁時計(Docker Desktop の VM 上)コマンド出力
round 1 synchronous_commit=on: 5000 INSERT (autocommit, 1 row each) 460 ms; rows=5000
round 1 synchronous_commit=off: 5000 INSERT (autocommit, 1 row each) 199 ms; rows=5000
round 2 synchronous_commit=on: 5000 INSERT (autocommit, 1 row each) 433 ms; rows=5000
round 2 synchronous_commit=off: 5000 INSERT (autocommit, 1 row each) 192 ms; rows=5000
round 3 synchronous_commit=on: 5000 INSERT (autocommit, 1 row each) 438 ms; rows=5000
round 3 synchronous_commit=off: 5000 INSERT (autocommit, 1 row each) 188 ms; rows=5000

平均は on 443.7 ms、off 193.0 ms。この環境では on が約2.3倍かかりました。どちらも行は 5,000 件そろっています。差は結果ではなく、成功を返すまでに待ったものです。

では off の代償は何か。公式は正面から書いています。

The risk that is taken by using asynchronous commit is of data loss, not data corruption. If the database should crash, it will recover by replaying WAL up to the last record that was flushed. The database will therefore be restored to a self-consistent state, but any transactions that were not yet flushed to disk will not be reflected in that state.

失うのは直近のいくつかのコミットで、データベースは壊れません。失われた分は、きれいに中止された場合と同じ状態になります。なお、この教材では損失そのものの実演はしていません。

✓ ここまでの確認

synchronous_commit を off にした PostgreSQL がクラッシュしたとき、起こりうると公式文書が書いているのはどれですか。

抽象原理

D は性質の名前で、設定がそれを成り立たせる

ACID の D(永続性)は「コミットが完了したデータは、その直後に障害が起きても失われない」という性質の定義です。PostgreSQL 用語集も once a transaction has been committed, the changes remain even after a system failure or crash と定義します。

定義は変わりません。変わるのは、その性質を成り立たせる設定を選んでいるかです。MySQL 公式は、対応する設定 innodb_flush_log_at_trx_commit をこう説明します。

The default setting of 1 is required for full ACID compliance. Logs are written and flushed to disk at each transaction commit.

0 と 2 はどちらも flush を1秒ごとにまとめ、Transactions for which logs have not been flushed can be lost in a crash. と続きます。既定値で成り立ち、設定で手放せる。2つの製品が同じ構図を書いています。

具体実物

MySQL では、待ちが2か所ある

同じ 5,000 コミットを MySQL 8.4 で測ると、設定1つでは縮みませんでした。MySQL 8.4 は既定で binlog という別のログが有効で(Binary logging is enabled by default)、sync_binlog=1 がそれをコミットごとにディスクへ同期するからです。

MySQL 8.4.11 / 5,000 コミット、3回の平均(生出力から計算、Docker Desktop の VM 上)表
flush_log_at_trx_commit sync_binlog 平均
1(既定) 1(既定) 2246 ms
0 1 1644 ms
2 1 1818.7 ms
1 0 685 ms
2 0 295.3 ms
0 0 157 ms

InnoDB のログだけを 0 にしても約27%減。両方を緩めて初めて約14分の1です。「コミットが返った」の意味は1つの設定では決まりません。 返事の手前で何を待つのかを、全部数える必要があります。

✓ ここまでの確認

この教材の環境(MySQL 8.4 既定)で innodb_flush_log_at_trx_commit だけを 1 から 0 にしたとき、5,000 コミットの時間はどうなりましたか。

⇄ 2つを見比べる
具体例 A

PostgreSQL の COMMIT(on と off)

on は WAL の flush を待ってから成功を返し、off は待たずに返します。どちらも成功と表示されますが、クラッシュで残ると言えるのは on のときだけです。

成功が返った時点で言えること表
設定 待つもの クラッシュ直後
on WAL の flush 残る
off 何も待たない 直近分が失われうる
具体例 B

OS の write() と fsync()

ファイルへの write() が成功を返しても、データはカーネルのメモリに入っただけでディスクに届いた保証はありません。中身が届いたと言えるのは fsync() が返ってからです。新しく作ったファイルの名前(ディレクトリの項目)は、そのファイルの fsync() だけでは保証されません。別コース『OSとカーネルの土台』で実測とともに扱います。

成功が返った時点で言えること表
呼び出し 待つもの 電源断の直後
write() カーネルのメモリへの写し 残る保証はない
fsync() デバイスの完了報告 中身は残る(名前は別)

データベースの COMMIT と OS のファイル書き込み。層は違いますが、「成功」が意味するものの決まり方に共通する構造は何でしょうか。

どちらも「成功」という同じ返事です。返事の手前で何を待ったかを並べてみてください。

この章のまとめ

  • 既定の PostgreSQL は WAL の flush を待ってから COMMIT の成功を返す。だから直後のクラッシュでも残る。
  • synchronous_commit=off は待たずに返す。代償は直近のコミットの損失で、破損ではない。
  • D は性質の定義。既定値で成り立ち、設定で手放せる。MySQL 公式も既定の 1 が完全な ACID 準拠に必要と書く。
  • MySQL 8.4 は binlog の同期もある。待ちを全部数えないと、設定の意味も速さも読み違える。
  • 時間はすべて Docker Desktop の VM 上の値。倍率を一般値にしない。
  • 「消えなかった」は「消えない」ではない。問うべきは、失ってよい業務かどうか。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

D の定義「コミットが完了したデータは、その直後に電源断などの障害が起きても失われないこと」と、「off にすると直近のコミットが失われうる」の関係として適切なのはどれですか。

用語集(4語)
永続性(Durability、ACID の D)
PostgreSQL 用語集は `once a transaction has been committed, the changes remain even after a system failure or crash` と定義する。性質の定義で、どの設定で成り立つかは別に決まる。
WAL
PostgreSQL が変更を記録するログ。既定ではコミットのたびにこれをディスクへ flush してから成功を返す。先に書く理由は次に読むコース『データベースの仕組み』で扱う。
synchronous_commit
PostgreSQL の設定。既定 on。off にすると WAL の flush を待たずに成功を返し、遅れは最大で wal_writer_delay(既定 200ms)の3倍。
innodb_flush_log_at_trx_commit / sync_binlog
MySQL の設定。前者は InnoDB のログ、後者は binlog を、コミットのたびにディスクへ同期するか。8.4 の既定はどちらも 1。