永続メモリ(PMEM)とDBの耐久性プリミティブ
DRAM 並みの速度でデータが停電に耐える――永続メモリ上でログを省き即時永続化する設計と、キャッシュフラッシュとフェンスで耐久性順序を保証する原理を、ハードウェアの嘘まで含めて解き明かします。
- 1.永続メモリ(PMEM)は CPU のロード・ストア命令でバイト単位にアクセスでき、停電後も内容が残る。だが store 命令の完了はキャッシュへの書き込みでしかなく、CPU キャッシュは揮発性なので、明示的にフラッシュ+フェンスしない限り永続化は保証されない。
- 2.耐久性の核心は『順序』にある。clwb/clflushopt でキャッシュラインを永続ドメインへ追い出し、sfence でフラッシュ完了を待ってからコミットフラグを書く。この依存順序を守ることで、WAL を介さずデータ構造そのものを即座に永続化(ログレス)できる。
- 3.電源断時に保証されるのは『永続ドメイン』に到達した書き込みだけ。メモリコントローラより手前のキャッシュはドメイン外であり、ADR や eADR でどこまでが保護範囲かが変わる。これを誤ると一見コミット済みのデータが消える。
バイト単位永続化という新しい地平
従来のストレージ階層は「速いが揮発する DRAM」と「遅いが永続するディスク・SSD」の二層に分かれていました。データベースがWAL(先行書き込みログ)や fsync に依存してきたのは、この断絶を埋めるためです。永続メモリ(PMEM、persistent memory)は両者の中間に座り、DRAM に近いレイテンシ(数百ナノ秒)で、しかも電源を切っても内容が残ります。
決定的な違いはアクセス粒度です。SSD はブロックデバイスで、最小でも 512 バイトや 4 KB 単位の I/O を発行します。PMEM は CPU のメモリバスに直結し、通常の load/store 命令で バイト単位 に読み書きできます。read()/write() システムコールもページキャッシュも介さず、ポインタをたどるだけでデータが永続化される――これが設計を根本から変えます。
PMEM は通常、ファイルシステムの DAX(direct access)モードでマップします。mmap した領域への store がカーネルのページキャッシュを経由せず、デバイスへ直接届く経路です。これにより二重コピー(ページキャッシュ→デバイス)が消え、バイト単位アクセスの利点が活きます。ただし「ストアが届く」ことと「永続化された」ことは別問題で、ここが本稿の主題です。
なぜ store だけでは永続化されないのか
最大の落とし穴は、store 命令の完了が永続化を意味しないことです。CPU が store を実行すると、値はまず L1/L2/L3 のキャッシュラインに入ります。これらのキャッシュは 揮発性 です。ここで停電すれば、まだ PMEM デバイスへ追い出されていない値は消えます。
つまり耐久性を得るには、書いたキャッシュラインを明示的に「永続ドメイン(power-fail protected domain)」まで押し込む必要があります。永続ドメインとは、電源が落ちても残留電力(蓄電)で書き切られることが保証された境界です。CPU コアのキャッシュはこの外側、メモリコントローラ配下の書き込みペンディングキューは内側、という具合に境界が引かれます。
store v → [L1/L2/L3 キャッシュ] → [メモリコントローラの WPQ] → [PMEM メディア]
↑揮発・ドメイン外 ↑ここから永続ドメイン
この境界を越えさせる命令が、キャッシュラインフラッシュ系(clflush / clflushopt / clwb)です。とくに clwb(cache line write back)はラインを書き戻しつつキャッシュに残す(無効化しない)ため、直後に再アクセスする局所性の高いデータ構造で有利です。
フェンスが定める耐久性の順序
フラッシュ命令を発行しても、それだけでは足りません。clflushopt や clwb は 非同期 で、後続命令を待たせずに走ります。フラッシュが永続ドメインに到達したことを保証するには、ストアフェンス sfence で完了を待ち合わせる必要があります。順序保証の原理はこの一点に尽きます。
1. store data # データ本体を書く
2. clwb data # データのキャッシュラインを永続ドメインへ
3. sfence # 2 の完了を待つ(ここで data は確実に永続)
4. store commit_flag # 「有効」を立てる
5. clwb commit_flag
6. sfence # commit_flag を永続化
重要なのは、commit_flag を立てる前に必ずデータ本体のフラッシュが完了していなければならない、という 依存順序 です。もし順序が逆転すると、停電復帰時に「フラグは立っているのに本体が未永続」という不整合が起こり得ます。これはWAL の write-ahead 規則――ログを書く前にデータを永続化してはならない――と同じ思想を、ハードウェアプリミティブで直接実現したものです。
フラッシュ後の sfence を省いても、テスト中は CPU が偶然タイミングよく書き戻すため正常に見えます。障害は実際の電源断時にだけ顕在化し、再現が極めて困難です。耐久性プリミティブのバグは「ほとんどの場合は通る」性質を持つため、命令列の順序はコードレビューとモデル検査で守るべき対象です。
ログレス/即時永続化の設計
PMEM の最大の魅力は、WAL を介さずにデータ構造を直接永続化する ログレス設計 が可能になる点です。従来は「データを更新する前に redo/undo ログを書く」二重書きが必須でしたが、PMEM ではデータそのものがバイト単位で即座に永続化されるため、ログを省ける余地が生まれます。
ただし無条件ではありません。問題は アトミック性の粒度 です。x86 の PMEM が単一の store でアトミックに永続化できるのは 8 バイト(自然境界に整列したワード)までです。これより大きい構造を「全部書けたか/全く書けていないか」の二択にするには、追加の工夫が要ります。
| 手法 | 原理 | 向くケース |
|---|---|---|
| 8 バイトアトミック更新 | ポインタや有効フラグなど 8 バイトに収まる量の単一 store で『切り替え』を表現する | 新旧ノードの差し替え(CoW 風) |
| PMEM ローカルログ(redo/undo) | 更新前に小さなログ領域へ書き、フラッシュ後に本体を更新。クラッシュ時はログから巻き直す | 8 バイトを超える複合更新 |
| シャドウコピー | 新版を別領域に組み立てて永続化し、最後に 8 バイトのポインタを差し替える | 大きなページ・ノード全体の置換 |
つまり「ログレス」とは「あらゆるログを消せる」意味ではなく、8 バイトアトミック更新で表現できる範囲はログ無しで即時永続化し、それを超える部分だけ局所的なログで補う、という設計判断です。グローバルな WAL とディスク fsync の往復が消える効果は大きく、コミットレイテンシは桁で縮みます。
ログレス構造の定石は、可視性を 1 つの 8 バイト値(バージョン番号や有効ビット)に集約することです。データ本体を先にフラッシュ+フェンスで永続化しておき、最後にこの 8 バイトを書いてフラッシュすれば、その store が成功=コミット、失敗(停電)=未コミット、と二値で決まります。中途半端な状態が原理的に存在しなくなります。
永続ドメインの正体と「嘘をつくハードウェア」
耐久性は「どこまでが永続ドメインか」というハードウェアの保証範囲に完全に依存します。ここを誤解すると、コミット済みに見えたデータが電源断で消えます。
- ADR(Asynchronous DRAM Refresh): 電源断を検知すると、メモリコントローラの書き込みペンディングキュー(WPQ)までの内容を残留電力で PMEM へ書き切る。CPU キャッシュは保護対象外。よってソフトウェアが
clwb+sfenceでキャッシュを WPQ まで追い出す責任を負う。 - eADR(enhanced ADR): 保護範囲を CPU キャッシュまで広げる。停電時にキャッシュ全体が書き戻されるため、原理的にはフラッシュ命令が不要になる(フェンスによる順序は依然必要な場面がある)。
同じコードでも、走るプラットフォームが ADR か eADR かで「フラッシュが必須か省けるか」が変わります。eADR 前提で書いたコードを ADR マシンで動かすと、フラッシュ漏れで永続化されません。
「store が返った」「clwb を呼んだ」だけでは永続化は保証されません。フラッシュが永続ドメイン(ADR なら WPQ、eADR ならキャッシュ)に到達し、sfence で完了を確認して初めて耐久性が立ちます。これは二重書き込みとトーンページ問題やグループコミットの fsyncで論じた「ストレージが揮発キャッシュで嘘をつくと永続性が崩れる」のと同型の罠が、メモリ階層で再来したものです。
torn write とリカバリ
8 バイトを超える更新が途中で電源断に遭うと、一部だけ永続化された torn write(部分書き込み) が残ります。PMEM ではブロックデバイスのような 4 KB セクタアトミシティすら保証されないため、復旧時に「どこまで有効か」を自力で判定する仕組みが必須です。
典型的にはチェックサムやバージョン番号、あるいは前述の 8 バイトコミットフラグで「完全に書き切れたレコードだけを有効とみなす」設計にします。中途半端なレコードは無効として捨て、ローカルログがあればそこから巻き直します。リカバリの考え方自体はARIESの redo/undo と地続きですが、PMEM では「ログを舐めて再現する」コストが小さく、構造が直接永続化されているため復旧が高速になります。
「PMEM で store しただけでは永続化されないのはなぜか」――キャッシュが揮発性で永続ドメイン外だから。「ではどう保証するか」――clwb でキャッシュラインを永続ドメインへ追い出し、sfence で完了を待ってからコミットフラグを書く、という依存順序を守る。「ログレスにできる理由は」――8 バイトアトミック store でコミットを表現でき、それを超える更新だけ局所ログで補うから、と答えられると強いです。
まとめ
永続メモリは「バイト単位で永続化できるメモリ」という新しいプリミティブですが、その耐久性は無料ではありません。store の完了はキャッシュへの書き込みにすぎず、揮発性キャッシュは永続ドメインの外側にあります。耐久性を立てる鍵は順序であり、clwb/clflushopt でキャッシュラインを永続ドメインへ追い出し、sfence で完了を待ち合わせてからコミットフラグを書く、という依存順序を厳守します。これを土台に、8 バイトアトミック更新でコミットを表現できる範囲はログ無しで即時永続化し、超える部分だけ局所ログで補うのがログレス設計です。最後に効いてくるのは ADR か eADR かという永続ドメインの境界で、ここを誤ると一見コミット済みのデータが電源断で消えます。DRAM 並みの速度と永続性を両立できる代わりに、耐久性保証の責任がハードウェアからソフトウェアの命令列へと降りてくる――それが PMEM 時代のデータベース設計の本質です。
データベース Article
永続メモリ(PMEM)とDBの耐久性プリミティブを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
永続メモリ
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
耐久性の核心は『順序』にある。clwb/clflushopt でキャッシュラインを永続ドメインへ追い出し、sfence でフラッシュ完了を待ってからコミットフラグを書く。この依存順序を守ることで、WAL を介さずデータ構造そのものを即座に永続化(ログレス)できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「永続メモリ / PMEM」に近いか確認する。
- 強みである「永続メモリ(PMEM)は CPU のロード・ストア命令でバイト単位にアクセスでき、停電後も内容が残る。だが store 命令の完了はキャッシュへの書き込みでしかなく、CPU キャッシュは揮発性なので、明示的にフラッシュ+フェンスしない限り永続化は保証されない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。