先日PostgreSQLの新しいマイナーバージョンがリリースされました。このマイナーリリースでメインとなる修正は「fsync周りのバグ修正」で、このバグは間違ったfsyncに対する間違った認識から約20年間存在してたバグということで注目されていました。

このバグについてPostgreSQLのコミッタ(Tomas Vondra氏)が解説しているセッションが、先々週開催されたFOSDEM 2019でありました。私もFOSDEM 2019に参加していたのですがその際は裏セッションに参加していて聞けず、資料が公開されないかなーと思っていたら、講演動画が公開されていたので観てみました。

以下は、その時の聴講メモです。より詳しく知りたい方は是非動画の方も観て下さい。

TL;DR

  • PostgreSQLはずっとfsyncについて一部間違った認識をしており(具体的には、fsyncがエラーした後の動作)を間違っており、稀なケースではあるけどユーザの知らない所でデータが破損するリスクがあった
  • PostgreSQL 11以下の最近リリースされた全てのバージョンで、fsyncに失敗したらpanic(データベースのクラッシュ)になるような変更をいれることでこの修正されている

聴講メモ

Intro into durability

WALはDirect I/Oを使っているけど、テーブルやインデックスなどのスデータについては、kernelが管理しているpage cacheを使ったBuffered I/Oを使っている(PostgreSQLの共有バッファ、OSのpage cache、ディスクの関係は講演資料に図があるのでそちらをぜひ見ていただきたい)

データの変更(INSERT/UPDATE/DELETE)が発生すると、

  1. WALそして共有バッファ上のデータを更新する。
  2. WALの変更をflushして永続化する
  3. 完了

となり、共有バッファ上の変更データは、すぐにディスクには反映されない。

この時にデータベースが落ちた場合、共有バッファ上の更新データは無くなる。けど、WALはディスク上に書かれているため、WALを再生することで落ちた直前の状態に回復することが出来る。

ここで一つ問題なのは、運用を続けているとWALはとても大きいサイズになる可能性があり、リカバリ時にWALを先頭から再生すると、とても時間がかかる。そのため、CHECKPOINTを使ってリカバリ時間を短縮する。

PostgreSQLのCHECKPIONT

PostgreSQLのCHECKPOINTは以下のように動く

  1. CHECKPOINT開始のLSNを記録する
  2. 共有バッファ上にある変更されたデータをpage cacheに書き出す(write)
  3. page cacheのデータをディスクに書き出す(fsync)
  4. 不要になったWALを消す

リカバリ時は、1で記録したLSNから再生できる。

CHECKPOINT中にエラーが発生したら?

CHECKPOINTは完了してはいけないし、WALも削除されてはいけない。

  • writeの失敗
    • 原理的には可能性はあるけど稀
    • writeするべきデータはPostgreSQLが持っているのでリトライできる
  • fsyncの失敗
    • SAN、NFSとか使っていると簡単に発生する
    • fsyncするべきデータはkernelが持っている

fsyncへの2つの間違った期待

1: fsyncが失敗した場合、次のfsyncのタイミングで失敗したdirty pageは再度書き込まれる

実際には・・・最初のfsyncに失敗したらデータはpage cacheから削除される。なので、次のfsyncはリトライしない。 さらに、これは、ファイルシステムによって挙動は変わる。ext4は、dirty dataをpage cacheにcleanとして残すし、xfsは捨てる。

2: 複数のファイルディスクリプタがある場合(例えばマルチプロセスの時)、一つのプロセスでfsyncが失敗したら、他のプロセスでも同じようにエラーとなる

実際には・・最初のプロセスだけがエラーとなる。その際、ファイルはcloseされopenされる。さらに、これはkernel versionによって挙動は異なる。

BSDでも同じように発生するけど、FreeBSD、illumosでは起きない。

なぜ今になって問題が明らかになってきた?

  • SAN、EBS、NFSとか使うようになってきた
  • thin provisioning
    • ENOSPCとかよくある

つまり、fsyncが失敗しやすい条件を持つユースケースが増えてきた

そもそもなぜBufferd I/Oなのか?

Postgresはもともとresearch projectで、当時その辺を頑張る研究者がいなかった。また、複雑さをなくすため。

どうやって直すかか

  1. カーネルを修正する
    • カーネル開発者たちに受け入れられたとしても数年単位で時間がかかる
  2. PostgresでCEHCKPOINTのfsyncが失敗したらpanicを起こすようにする
    • panicしてWALからcrash recoveryする

Direct I/Oのアプローチもありだしパッチもでている、だけどこれも数年かかるだろう。

参考リンク

  • pgsql-hackers
    • 開発者MLでの議論
    • https://goo.gl/Y47xFs
    • https://goo.gl/dk4F3n
  • PostgreSQL fsync() surprise
    • https://lwn.net/Articles/752063/
  • Improved block-layer error handling
    • https://lwn.net/Articles/724307
  • PGCon 2018 fsyncgate : Matthew Wilcox
    • 昨年のPGConでKernel開発者が発表したlinux kernelのfsyncについての話
    • 個人的にもこれはおススメ
    • https://goo.gl/Qst2Lf

質疑

いくつか抜粋。

  • Q. どうやって修正パッチをテストした?
    • A. エラーを再現するスクリプトを作ってそれでテストした。テストはすでに公開されている。
  • Q. どのようにこの問題は明らかになった?
    • A. 実環境でデータ破損が起きて、インデックスが壊れているとか色々探した結果、この問題を発見した
  • Q. zfs on freebsdではこの問題は起きない、と言っていたけど、zfs on linuxではどう?
    • A. zfs on linuxもセーフだと思う。page cacheではなくarcを使っているから

最後に

対応する修正コミットはこれこれdata_sync_retryという新しいパラメータが導入されて、デフォルトではfsync()の失敗でPANICになるようになった。fsyncの失敗は稀なケースではあるけれど、NFSとかEBSとかthin provisioningを使っている場合は気を付けたい。