TL

ベクトル化I/Oとダイレクトディスク・io_uring・mmap論争

DB のページ I/O が遅い元凶は OS キャッシュとの二重管理かもしれません。ダイレクト I/O・バッファード I/O・mmap の三択を、キャッシュの二重持ちと fsync 障害の原理から見極められます。

応用ダイレクトI/OバッファードI/Ommapio_uringデータベース最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.バッファード I/O は OS のページキャッシュを通すため、DBMS の自前バッファプールと内容が二重に乗り、メモリと帯域を浪費する。ダイレクト I/O(O_DIRECT)はこのコピーを外し、キャッシュ管理を DBMS に一本化する。
  • 2.mmap は I/O をメモリアクセスに見せて手軽だが、ページの追い出し時刻・書き戻し順序・I/O エラーを DBMS が制御できず、トランザクション DB の WAL 規律と相性が悪い。LMDB のような例外を除き主流から外れた。
  • 3.ベクトル化 I/O(preadv/pwritev)は複数バッファを1コールで束ね、io_uring は提出・完了キューでシステムコールと同期点を削減する。どちらもダイレクト I/O と組んで効果が出る。

なぜ I/O 経路の選択が論争になるのか

DBMS はディスク上のデータを固定長のページ(PostgreSQL なら 8KB、InnoDB なら 16KB)として読み書きします。ファイルへの読み書きを OS に頼む方法は大きく三つあり、どれを選ぶかでメモリ効率・耐障害性・実装の複雑さが大きく変わります。論争の核心は一つ、OS のページキャッシュと DBMS 自前のバッファプールが、同じデータを二重に持つ無駄をどう扱うかです。

DBMS はそもそもバッファプールとページ置換で説明したとおり、自前でホットページをメモリに留めます。アクセスパターンと書き戻し順序(WAL 規律)を正しく制御できるのは DBMS だけだからです。ところが通常のファイル読み書き(バッファード I/O)は、その手前で OS が勝手にもう一段キャッシュを挟みます。この二段重ねが論争の出発点です。

三つの I/O 経路

経路OS キャッシュDBMS から見たデータの渡り方
バッファード I/O(read/write)通す(ページキャッシュにコピー)OS キャッシュ ↔ ユーザー空間バッファ間でメモリコピーが1回入る
ダイレクト I/O(O_DIRECT)迂回するユーザー空間バッファとディスクが DMA で直結。コピーなし
mmap通す(ページキャッシュを直接マップ)ファイルを仮想アドレス空間に貼り、メモリアクセスがそのまま I/O になる

バッファード I/O では、read() のたびにデータがディスク → OS ページキャッシュ → ユーザー空間バッファと流れ、最後のコピーが必ず発生します。OS キャッシュに残ったページは、DBMS のバッファプールにも同じ内容が載るため、同一ページが物理メモリ上に2枚存在します。これが二重管理(double caching / double buffering)です。

バッファード I/O の二重管理問題

二重管理は単なる気持ち悪さではなく、実害があります。

  • メモリの二重消費: 8KB のホットページが OS 側とバッファプール側で計 16KB を占める。実効的なキャッシュ容量が目減りする。
  • 置換ポリシーの衝突: OS は OS の LRU 近似で、DBMS は DBMS の置換戦略で、それぞれ独立にページを追い出す。DBMS が「ホット」と判断したページを OS が裏で捨てる、といったちぐはぐが起きる。
  • 書き戻しタイミングの不透明さ: write() はデータを OS キャッシュに置くだけで、実際のディスク到達は OS まかせ。永続化には別途 fsync() が要る。
それでも PostgreSQL がバッファード I/O を使う理由

PostgreSQL は伝統的にバッファード I/O を使い、shared_buffers を物理メモリの一部にとどめて残りを OS キャッシュに委ねる設計を採ってきました。二重管理は承知のうえで、OS の先読み(read-ahead)や書き戻しスケジューリングに乗れる利点、そして O_DIRECT の移植性・整合性の難しさを避ける現実解です。近年は非同期 I/O とダイレクト I/O のオプションを取り込みつつあり、設計の振り子は揺れ続けています。

ダイレクト I/O――キャッシュ管理を一本化する

ダイレクト I/O(Linux の O_DIRECT)は、OS ページキャッシュを迂回し、ユーザー空間バッファとディスクを DMA で直結します。OS 側のコピーが消え、キャッシュ管理が DBMS のバッファプールに一本化されます。InnoDB の innodb_flush_method=O_DIRECT が代表例で、Oracle や多くの商用 DB もこの路線です。

利点と引き換えに、ダイレクト I/O は制約がきびしい。

  • アラインメント要件: バッファのアドレス・オフセット・長さが、デバイスのブロックサイズ(典型的に 512B や 4KB)の倍数でなければならない。DBMS はページバッファを境界整列して確保する必要がある。
  • 先読みやキャッシュの恩恵を失う: OS が肩代わりしていた先読み・書き戻しのまとめ上げを、DBMS が自前で実装し直すことになる。
  • 永続化は依然 fsync が必要: O_DIRECT はキャッシュを通さないだけで、ディスク内部の揮発キャッシュまで保証しない。コミットの耐久性には fsync とグループコミットの規律が別途要る。
O_DIRECT は「同期書き込み」ではない

O_DIRECT を付けても、書き込みがディスクのプラッタ/フラッシュセルに着地した保証にはなりません。ストレージデバイスの揮発キャッシュにとどまり得るため、耐久性が要る場面では fsync()(または O_DIRECT と O_SYNC の併用)が必要です。「ダイレクト=即永続」と取り違えると、電源断でコミット済みデータを失います。

mmap――手軽さの裏にある制御不能

mmap はファイルを仮想アドレス空間に貼り付け、ポインタ経由のメモリアクセスがそのままページ単位の I/O になる方式です。read/write のコードが消え、実装が劇的に簡単になります。組み込み DB の LMDB はこれを全面採用し、読み取りロックフリーという美点を引き出しました。

しかし汎用トランザクション DB では mmap は地雷が多い。論文「Are You Sure You Want to Use MMAP in Your DBMS?」が整理した主な問題は次のとおりです。

  • トランザクション安全性の喪失: OS がいつページを書き戻すか DBMS が制御できない。WAL を書く前に変更ページが先にディスクへ落ちると、WAL 規律(ログ先行)が破れ、リカバリの辻褄が合わなくなる。
  • I/O エラーを捕まえられない: 読み込み失敗が SIGBUS(シグナル)として飛んでくる。read() の戻り値でエラー処理する DBMS の流儀と噛み合わない。
  • トーンページ: ページ書き戻しの原子性が OS まかせで、電源断で半分だけ書かれたトーンページを DBMS が検知・補正しにくい。
  • TLB シュートダウンとスケール限界: ページ追い出し時にコア間で TLB を無効化する処理(TLB shootdown)が、コア数が増えるほど重くのしかかる。
mmap が向くのは「読み取り中心・単純な耐久性」

mmap は、読み取りが支配的でクラッシュ一貫性の要求が単純な用途(LMDB の MVCC 読み取りなど)では強力です。一方、多数の書き込み・複雑な WAL・厳密な書き戻し順序が要る汎用 DB では、追い出し時刻・書き戻し順序・I/O エラーを DBMS が握れないことが致命傷になります。手軽さと制御は両立しにくい、という点を見誤らないことが肝心です。

ベクトル化 I/O と io_uring――システムコールを削る

経路(キャッシュを通すか)の話とは別に、呼び出しの効率という軸があります。ページを1枚ずつ read()/write() するとシステムコールのオーバーヘッドが積み上がります。これを削るのが二つの技術です。

ベクトル化 I/O(scatter/gather I/O、preadvpwritev)は、複数のバッファ領域を1回のシステムコールでまとめて読み書きします。ばらばらのメモリ上にある複数ページを、1コールで連続したファイル範囲へ書き出せるため、コール回数を N 分の1に減らせます。

io_uring は Linux の新しい非同期 I/O インターフェースで、カーネルと共有する提出キュー(submission queue)と完了キュー(completion queue)の二つのリングバッファを介して I/O を依頼・回収します。

従来(同期 read を N 回):
  for page in pages: read(fd, buf, ...)   # システムコール N 回・各回でブロック

io_uring:
  提出キューに N 件の read 要求を積む
  io_uring_enter() を1回呼ぶ              # まとめて投入(コール1回)
  完了キューから順次結果を回収            # ブロックせず非同期に処理

io_uring はシステムコール回数と文脈切り替えを大幅に削り、ダイレクト I/O と組み合わせると効果が際立ちます。buf のコピーが無い O_DIRECT のページを、低オーバーヘッドの非同期キューで大量に捌けるからです。PostgreSQL の非同期 I/O サブシステムや ScyllaDB などがこの方向を採っています。

ベクトル化 I/O と io_uring は直交する手段

ベクトル化 I/O は「1コールに複数バッファを束ねる」空間方向の集約、io_uring は「複数 I/O を非同期に投げて待たない」時間方向の集約です。両者は排他ではなく、経路(バッファード/ダイレクト)の選択とも独立です。ただし真価が出るのはダイレクト I/O との併用時で、OS キャッシュを介すバッファード I/O ではコピーと二重管理のコストが残ります。

設計判断のまとめ

観点バッファード I/Oダイレクト I/O(O_DIRECT)mmap
二重管理起きる(OS+自前で二重)起きない(自前に一本化)実質 OS キャッシュに依存
書き戻し制御OS まかせDBMS が完全制御OS まかせ(制御不能)
実装の難しさ易しいアラインメント等で難しいコードは簡単だが落とし穴多数
主な採用PostgreSQL(従来)InnoDB・Oracle・商用DBLMDB など読み取り中心
試験・面接で問われる定番

「ダイレクト I/O が速いとは限らないのはなぜか」には、OS の先読み・書き戻し集約を失い DBMS が自前実装する必要がある点で答えます。「mmap が手軽なのに主流 DB で避けられる理由」は、ページ追い出し時刻・書き戻し順序・I/O エラー(SIGBUS)を DBMS が制御できず WAL 規律が破れる点で答える。「O_DIRECT を付ければ永続化は保証されるか」には、ノー、デバイスキャッシュが残るため fsync が別途要ると答えられると強いです。

まとめ

DB のページ I/O 経路の選択は、突き詰めれば「OS のページキャッシュと自前バッファプールの二重管理をどう始末するか」に集約されます。バッファード I/O は移植性と OS 機能の恩恵を取る代わりに二重持ちを許し、ダイレクト I/O はコピーを外してキャッシュ管理を DBMS に一本化する代わりにアラインメントと自前最適化の負担を負い、mmap は手軽さの代償に書き戻し順序と I/O エラーの制御を手放します。

その上で、ベクトル化 I/O と io_uring は経路選択と直交する「呼び出し効率」の改善で、ダイレクト I/O と組んで真価を発揮します。最終的な正解はワークロード次第ですが、キャッシュの所在と書き戻し順序を握れるのは DBMS だけだという原則が、論争の底に一貫して流れています。書き戻しの永続化規律は WAL の仕組みfsync とグループコミット を、そのフラッシュをならす仕組みは チェックポイント を参照してください。

データベース Article

ベクトル化I/Oとダイレクトディスク・io_uring・mmap論争を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

ダイレクトI/O

比較で見る軸

難易度: advanced / カテゴリ: データベース / タグ数: 5

導入後に効く点

mmap は I/O をメモリアクセスに見せて手軽だが、ページの追い出し時刻・書き戻し順序・I/O エラーを DBMS が制御できず、トランザクション DB の WAL 規律と相性が悪い。LMDB のような例外を除き主流から外れた。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
データベース
タグ数
5

判断チェックリスト

  • 自社の用途が「ダイレクトI/O / バッファードI/O」に近いか確認する。
  • 強みである「バッファード I/O は OS のページキャッシュを通すため、DBMS の自前バッファプールと内容が二重に乗り、メモリと帯域を浪費する。ダイレクト I/O(O_DIRECT)はこのコピーを外し、キャッシュ管理を DBMS に一本化する。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

ダイレクトI/OバッファードI/Ommapio_uringデータベースダイレクトI/OバッファードI/Ommap