Disk can lie to you when you write to it

2025/12/12 10:16

Disk can lie to you when you write to it

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

本番向けの書き込み先行ログ(WAL)は、データがクラッシュや潜在的なセクタエラーに耐えるために、複数の保護機構を組み合わせる必要があります。

  1. チェックサム:各レコードヘッダーにはマジックナンバー、シーケンス番号、および CRC32C チェックサムが含まれ、
    fsync()
    後にチェックサムが検証されて静かな破損を検出します。
  2. 冗長性:2 つの WAL ファイルを別々のディスクに並列で書き込み、一方が LSE(潜在的セクタエラー)を起こしてももう一方は壊れません。
  3. Direct/Durable I/O
    O_DIRECT
    /
    O_DSYNC
    を使用すると、カーネルにデータを安定したストレージへフラッシュさせるため、ページキャッシュ遅延を防止します。
  4. 順序付き操作:Linux では
    io_uring
    IOSQE_IO_LINK
    を組み合わせることで、各書き込みが直後に
    fsync()
    が実行され、追加のコンテキストスイッチを排除します。
  5. 書き込み後検証:両方の
    fsync()
    が完了した後、WAL を再読込しチェックサムを再確認します。失敗した場合はセカンダリ WAL を使用します。
  6. 回復:クラッシュリカバリ中にシステムは両方の WAL ファイルをスキャンし、重複レコードをマージして最高連続シーケンス番号を特定し、操作を再実行してインメモリ状態を再構築します。

この層状設計は、ページキャッシュ遅延・静かな LSE 及び順序バグを軽減し、5 層の組み合わせにより単一コピーに依存せずに大規模データベースやストレージシステムで信頼性の高いクラッシュリカバリを実現します。

本文

書き込み先行ログ(WAL)の真実

書き込み先行ログ(Write‑Ahead Log、WAL)は、一見すると単純そうに聞こえるデータベース概念です。
まずディスクへレコードを書き込み、その後でメモリ上の状態を更新します。クラッシュしたらログを再生して復旧するだけ―これが「完了」です。

しかし実際はディスクが嘘をつくこともあるのです。PostgreSQL、SQLite、RocksDB、Cassandra… いわゆる耐久性を主張する本番システムはいずれもWALに頼っています。「ここに書けばデータは確実に残る」と約束しますが、その約束を守るには「ディスクが無音で失敗する」様々なケースを理解しておく必要があります。


① 単純想定と現実

write(fd, record, sizeof(record));   // これで完了?

ラップトップのテスト環境ではうまく動きます。
しかし日々数百万回の書き込みを行えば、1 ミリオンに1の確率でエラーが発生し、何度も起こります。テストでは捕捉できない失敗は多いです。

  • ページキャッシュ問題
    write()
    はカーネルバッファへコピーするだけで、まだディスクには書き込まれていません。クラッシュすると消えてしまいます。
  • 成功を嘘つくディスク
    write()
    が成功を返し、カーネルは同期したと言うが、実際に物理的に保存されていないケースがあります。
  • 順序混乱 – 書き込みAとBが同時に開始して、B が先に完了すると、リカバリコードは A を見逃す可能性があります。
  • 単一障害点 – WAL の唯一のコピーにひっかけられたセクタエラーで全データを失います。

これが「耐久性」に対する恐怖の根底です。


② より安全な設計 ― 五つの防御層

WAL を強化するには、次のように五段階で対策します。
それぞれは「どこで失敗しうるか?」という質問への具体的な答えです。

レイヤー 1:チェックサム(CRC32C)

レコードごとに内容のハッシュを付与し、書き込み後に同じ値になることを確認します。

Record Header (20 bytes):
  [magic_num : 4][sequence_num : 8][checksum : 4]
  [payload : variable]
  [padding to 512‑byte alignment]

ハードウェアのビットフリップやディスクファームウェアの誤動作、メモリバスの乱れなどはエラーを返さず、I/O は成功したとみなされます。チェックサムがないと、復旧時にログが汚染されたことに気づかず、数週間後に大きな障害となります。

レイヤー 2:二重 WAL ファイル(LSE 対策)

「潜在的セクタエラー(Latent Sector Error, LSE)」を防ぐため、異なるディスクに同じ WAL を二重で書きます。
Google の運用調査では LSE が頻繁に発生し、単一コピーは無責任と判定されました。

WAL Primary      WAL Secondary
[Record 1]        [Record 1]
[Record 2]        [Record 2]
[Record 3]        [Record 3]
…

片方に破損が見つかっても、もう一方がバックアップになるので安全です。

レイヤー 3:O_DIRECT + O_DSYNC

write()
の際に
O_DIRECT
O_DSYNC
を併用します。

  • O_DIRECT – カーネルページキャッシュをバイパスし、書き込みは直ちにディスクへ行くようにします。
    注意:Linux の全ファイルシステムがサポートしているわけではありません。
  • O_DSYNC
    write()
    が戻る前にデータを物理的に確定させます。

これらは「遅くても耐久性が欲しい」ことを明示しています。ページキャッシュは読み取り中心の最適化で、WAL には逆効果です。

レイヤー 4:リンク付き I/O 順序(Linux の io_uring)

io_uring
は送信キューと完了キューという二つのリングバッファを持ち、コンテキストスイッチやシステムコールオーバーヘッドを減らします。
書き込み→
fsync()
をリンクし、同様に副次 WAL も連結します。

Submit:    [Write Primary] → [Fsync Primary] → [Write Secondary] → [Fsync Secondary]
           (それぞれが相手とリンク)

これで、両方の書き込みが完了するまでアプリは戻らず、順序保証を確保できます。カーネル I/O スケジューラに任せると、実際には A が先に完了しても B を先に処理するケースがあります。

レイヤー 5:
fsync
後の検証

両方の

fsync()
が終了したら、直前に書いたデータを読み取りチェックサムを再計算します。
これで潜在的セクタエラーを即座に検知し、破損が発見された場合は二重 WAL を利用して復旧できます。


③ 復旧プロセス

システム起動時の手順:

  1. 両方の WAL を順次読み込み、有効なチェックサムを持つレコードだけを集める。
  2. 重複(主副で同じ操作が記録されている)を統合。
  3. 最も高い連続したシーケンス番号を見つけ、復旧ポイントとする。
  4. その順序で操作を再実行し、メモリ状態を再構築。

これにより:

  • 耐久性が保証されたすべてのトランザクションが再演出される。
  • 未コミットの操作はスキップされる。
  • 一方の WAL が部分的に破損していても、安定した状態へ復旧できる。

④ なぜこれらが必要なのか?

「五層構造は過剰だ」と思うかもしれません。趣味プロジェクトなら足りないこともあります。しかし本番環境では次のような事態が起きます。

シナリオ 1:静かな破損

WAL 書込み完了 → 48 時間後に LSE が発生 → 知らずに進む。
*二重 WAL 未使用時:データは消失し、耐久性保証が破綻。
*二重 WAL + 検証あり:副次 WAL が即座に検知し、復旧は正常に行える。

シナリオ 2:ページキャッシュの罠

アプリが WAL に書き込み → カーネルが成功を返す → データはページキャッシュに残る。数秒・数分後にカーネルクラッシュ。
*

O_DIRECT
未使用時:データは失われる。
*
O_DIRECT
使用時:
fsync()
が戻った瞬間に物理的にディスクへ書き込まれる。

これらは実際に本番で発生している問題です。経験者が苦しみ、他の人は学びながら設計を整えていきました。


⑤ 結論

本格的な WAL は単なるコードではなく、「このレコードを書けば確実に残る」ことを約束する契約です。
それを守るには:

  • チェックサムで破損検知
  • 冗長性(二重 WAL)で障害耐性
  • O_DIRECT
    +
    O_DSYNC
    で物理的な書き込み保証
  • 操作リンクで順序を確保し、並行性を失わない
  • 復旧前に 検証読み取り を実施

ディスクは嘘をつく。ページキャッシュも同様。ファームウェアのバグやセクタエラーも。WAL はそれらすべてを信用しません。

この設計を体現したコード例は GitHub で公開しています。

同じ日のほかのニュース

一覧に戻る →

2025/12/15 6:53

Anthropic Outage for Opus 4.5 and Sonnet 4/4.5 across all services

## Japanese Translation: アノマリーは複数のAnthropicサービスに影響を与え、特にSonnet 4.0、Sonnet 4.5、およびOpus 4.5モデルが対象でした。事件は2025年12月14日21:31(UTC)に初めて報告され、同日の21:46(UTC)に調査更新が行われました。この更新で問題は該当するモデルバージョンに起因することが確認されました。影響範囲は複数のAnthropicプラットフォームに及びます——claude.ai、platform.claude.com(旧console.anthropic.com)、Claude API(api.anthropic.com)およびClaude Codeです。タイムラインや次のステップについてはまだ発表されていません。

2025/12/15 6:05

2002: Last.fm and Audioscrobbler Herald the Social Web

## Japanese Translation: *変更は不要です。要約はすべての列挙された重要ポイントを正確に反映していますが、商業的実現可能性について小さな推測上の結論も追加されています。

2025/12/15 1:55

Hashcards: A plain-text spaced repetition system

## Japanese Translation: Hashcards は、すべてのフラッシュカードデータをプレーンマークダウンファイル(例:`Cards/Math.md` や `Chemistry.md`)として保持する軽量でローカル優先のスペースドリピテーションアプリです。ウェブ UI を起動すると (`hashcards drill <path>` が `localhost:8000` を開く)、レビュー履歴は SQLite データベースに保存されますが、カード自体は Git に対応し続けます。各カードはテキストのハッシュでコンテンツアドレス化されているためです。インターフェイスは作成をスムーズにすることを優先しており、クローズ削除は Mochi の冗長な `{{ }}` ではなく `[ ]` を使い、単一行ブロック(例:`Q: … A:` や `C: …`)でカードを定義します。 著者は Anki の煩雑な UI、“すべて期限切れを学習” ボタンの欠如、WYSIWYG エディティング、そして不安定なプラグインサポートを批判しています。Mochi は過度に冗長なクローズ構文、ノートタイプ自動化がないこと、および長期的には性能が劣る単純な倍率ベースのスケジューラーという欠点があります。Hashcards はこれらの痛みを解消し、最小限の摩擦、最適なスケジューリングのための高度な FSRS アルゴリズム、および任意のエディタや Unix ユーティリティ(例:`wc`、`awk`)でカードを編集できる機能を提供します。デッキは Git でバージョン管理・ブランチ化・マージが可能で、サードパーティサービスなしにユーザーがデータを完全にコントロールできます。 将来的には CSV ベースのカードインポート、Python で書かれたノートタイプロジック、および Git 主導の共有ワークフロー拡張をロードマップに含めています。これらの機能は協力を容易にし、コンテンツ生成を自動化するとともに、開発者が Hashcards を自身のプロジェクトへ拡張できるようにし、オープンソーススペースドリピテーション エコシステムを形成する可能性があります。