読み書きの非対称性はあなたが(おそらく)想像しているほど大きい

2026/10/28 18:38

読み書きの非対称性はあなたが(おそらく)想像しているほど大きい

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

要約▶

Japanese Translation:

POSIX システムにおける重要な教訓は、成功した

write(2)
システムコールがデータを永続ストレージに到達したことを保証しないことであり、電力喪失やクラッシュ時にアプリケーションが破損するリスクを残すことです。このリスクは、複数のバッファリングレイヤーが存在することから生じます:ユーザープログラムは通常、バッファ付き I/O(例えば C stdio の
FILE*
、Go の
bufio
、Java の
BufferedReader
)を使用し、システムコールを呼び出す前に書き込みをバッファリングします。その上で、カーネルはバイト単位で揃った要求をブロック単位で揃った操作(ブロックは歴史的には 512 バイト、現代的なストレージでは後期にしばしば 4k)に変換し、ブロック単位でないデータをキャッシュします。
write
システムコールは、データがユーザー空間のバッファにコピーされたかカーネルキャッシュに引き渡された時点で即時リターンしますが、物理的なコミット後にではありません。対照的に、
read(2)
はリターンする前にストレージからバッファを充填するため、エラーは呼び出しが完了する前に発生します。したがって、成功した
write()
の後でも、データは揮発性キャッシュ(ユーザー空間と/またはカーネル)のみ、あるいは RAID コントローラに存在するだけであり、システムクラッシュやデバイスエラーが発生した場合の失うリスクがあります。NFS や SMB/CIFS などのネットワークファイルシステムでは、書き込みエラーは即時報告されず、返りコードが無視された場合はクローズ時にのみ報告されることがあります。データの耐久性を確保するためには、開発者はエラーコードを確認し、ファイル記述子をクローズする前に明示的に
fsync()
(Windows 上では
FlushFileBuffers
)を呼び出す必要があります。ディレクトリメタデータの変更がある場合、その更新の可視性を保証するには、別途ディレクトリのファイル記述子に対する
fsync()
を呼び出す必要があることもあります。したがって、デフォルトのバッファ付き I/O に依存するだけで、ハイリスクなプロダクション環境において重大な破損または重要な情報の完全な損失につながることがあります。

本文

コンピューティングにおける入出力(I/O)操作の非対称性とリスク

コンピューティングにおける 入出力(I/O)操作 の実装と公開について解説します。本記事では、特に読み込みと書き込みにおける振る舞いの違い(非対称性)、その原因であるバッファリング機構、そして開発者が見落としがちな重要な注意点に触れます。


1. システムコールとバッファ付き I/O の基本

POSIX システムコール

read(2)
と
write(2)
は、入出力処理の基本単位となります。

システムコールの定義

以下の C 関数型定義は、生(raw)のシステムコールを表します。ユーザ空間プログラムが直接使用可能ですが、実際には高層階級のレイヤーを経由するのが一般的です。

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

C スタンダードライブラリの役割

プログラムは通常、

FILE*
タイプおよび
fread
/
fwrite
関数を通じて I/O を実行します。これらは内部で
count
の値を分割処理しますが、本質的にはシステムコールと同様の動作を行います。

基本概念の対称性と非対称性

  • 概念的な対称性:
    • read
      系:ディスク上のファイルを参照し、バイトをメモリバッファへコピー。
    • write
      系:メモリバッファ内のデータを格納先(ストレージ)へ書き込み。
  • 実装的な非対称性:
    • エラー発生時や内部機構(バッファリング)により、両者の挙動に重要な違いが生じます。

2. バッファ付き I/O の仕組み

プログラム内のファイル I/O はストレージデバイスへ直接到達せず、複数の バッファレイヤー を通過して伝達されます。これらは技術的な動機に基づき有効になっています。

カーネル空間側のバッファキャッシュ

  • デバイスは「ブロックデバイス」として動作し、固定サイズ(例:4 KB)のブロック単位でデータを格納・処理します。
  • ランダムな I/O リクエストの変換:
    • バイト境界に揃っていないリクエスト(例:オフセット 1234567 バイトから 100 バイト読み取り)も、ブロック境界に揃った操作に変換して実行されます。
    • 書き込み時、既存ブロックの内容を読み取る必要がある場合も同様にバッファキャッシュを経由します。
  • 役割: デバイスの理解するブロック境界への要求変換とデータの一時的な保存(キャッシュ)。

ユーザ空間側のバッファリング

システムコールの呼び出しにはオーバーヘッド(数 hundred サイクル)が発生するため、一度に処理するデータ量を増やす必要があります。

バッファサイズ例(C スタンダードライブラリ・言語別)
4096 バイトVisual C++ 2013 の
FILE*
、Go (
bufio.Reader
/
Writer
)
8192 バイトGNU libc の
FILE*
、Java (
BufferedReader
/
BufferedWriter
)

ハードディスクや RAID コントローラーにも独自のキャッシュが搭載されており、実装はさらに複雑になりますが、基本的な構造はこの 2 つのレイヤーで理解できます。


3. I/O 操作発行時の挙動と非対称性

読み込みと書き込みにおける「世界の状態」の変化に違いがあります。

read
系コール(読み込み)

  1. バッファの充填: アプリケーションが要求したデータ量(例:100 バイト)よりも多いデータ量(例:4 KB)を一度に読み取ろうとします。
  2. データの確保: カーネルはファイルシステムに問いかけ、必要ならストレージから読み込み、バッファキャッシュへコピーします。
  3. 完了: データがメモリバッファに到達し、アプリケーションの指定した先頭にコピーされます。

エラー処理の特徴

  • ファイルサイズ不足、読み取りエラー、接続切断などが発生した場合、システムコールを呼び出す直後にエラーを検知し、適切なエラーコードを返します。
  • 結論: 読み込み失敗は即座に報告されるため、アプリケーション側で安全にエラーハンドリングが可能です。

write
系コール(書き込み)

  1. 仮保存: アプリケーションのデータを受け取り、バッファへコピーしますが、直ちにシステムコールを介してストレージへ書き込むわけではありません。
  2. 即時的成功: ユーザ空間関数はすぐに「成功」として戻ってきます。
  3. 後置実行: 実際の書き込みは以下のいずれかのタイミングで実行されます。
    • バッファが満杯になった時
    • fflush()
      、
      fseek()
      ,
      fclose()
      を呼び出した時

エラー処理のリスク

  • 「成功」を報告されたからといって書き込みに成功したとは限りません。
  • 多くのプログラマは
    fwrite
    の戻り値を確認しますが、
    fflush
    や
    fclose
    のエラーチェックを行う人は少ないです。
  • 結論: バッファ付き書き込みの本質は「実行の遅延」であり、エラー報告も同様に遅延されています。

4. その他のバッファリングとリスク

カーネル空間のバッファキャッシュ(
write
システムコール)

write
システムコールが成功したとしても、データはまだ物理的なストレージデバイスには到達していません。

  • リスク: デバイスエラーやシステムクラッシュにより、この段階で停電などが起こるとデータは失われます。
  • 遅延書き込みの問題:
    • ローカルファイルシステムでも、空き容量の確保処理が後から実行(delayed write-back)される場合があります。
    • ネットワークファイルシステム(NFS, SMB/CIFS)では、
      close()
      までの間にエラーが発生している場合でも通知されないことがあります。
  • 対策:
    close()
    の戻り値だけでなく、データ永続性を確保するには
    fsync
    を必要です
    。

データの真正性とメタデータ

  • バッファキャッシュに到達した=ストレージへ書き込まれたという保証はありません。
  • 永続性の確保手順:
    1. 書き込みシステムコールを実行し、エラーを確認。
    2. close()
      の戻り値をチェックする(Windows では
      FlushFileBuffers
      など)。
    3. 直前に
      fsync(2)
      を呼び出し
      、エラーを確認する。

ディレクトリ記述子への注意

fsync()
はそのファイルを含むディレクトリのメタデータまでディスクに到達することを保証しません。

  • 正しい実装: ディレクトリに対応するファイル記述子上でも明示的に
    fsync()
    を行う必要があります(多くのライブラリはこの点の保証を行っていません)。

脚注 [3]:

fsync
は C ライブラリの関数ではなく、正当なシステムコールです。


5. 結論と概要

I/O 操作におけるリスクを要約します。

特性読み込み (
read
)
書き込み (
write
)
非対称性なし(ほぼ透明)非常に高い(複雑)
失敗時の挙動データを見る前にエラーが検知され、即座に報告される。書き込みの成功を報告しても、実際には未実行またはエラー中の可能性がある。
開発者の課題エラーハンドリングが比較的容易。実行の遅延とエラーの遅延を理解していないと深刻なデータ喪失を招く。
OS の役割抽象化により単純化する。抽象化機構(
fsync
など)自体が複雑で、正しく使う必要があります。

開発者への要約

  • 書き込みは実際の実行とエラー報告が遅延していることを常に意識してください。
  • データの永続性を保証するためには、以下の手順を踏む必要があります:
    • 書き込み完了直前に
      fsync()
      を実行する。
    • 関連するシステムコール(
      close
      ,
      fflush
      など)の 戻り値をチェックする。
  • 「ドラゴン」と呼ばれるこれらの難しい問題は、OS の抽象化が必ずしも状況を楽にしてくれないことに起因します。

6. 脚注

  1. [1] バッファモード: ここでの議論は「フルバッファモード」に関するものです(行バッファモードとは異なります)。
  2. [2] 遅延割り当てのリスク: 現代のファイルシステムではブロック割り当てを遅らせることは一般的ですが、空き容量確保処理も同様に後置されます。ユーザーが重要ドキュメントを保存している最中に OS が再起動したり、容量不足に気づいたのが数分後だったりすると、アプリケーションからはエラーが発生しないままクラッシュやデータ喪失が進む可能性があります。
  3. [3] fsync の性質: 名前の響きから C ライブラリの一部のように思えますが、実際にはカーネル空間で動作するシステムコールです。

同じ日のほかのニュース

一覧に戻る →

2026/11/01 4:56

HackerTube:HN で推奨される動画から構築された YouTube

## Japanese Translation: 最も重要な教訓は、hackertube.org の公開であり、これは「enshittified」化した標準的な YouTube の代替手段です。低品質なコンテンツや誇張されたサムネイルから脱却するために設計されています。AI エージェント(建設には Opus 5.5、フィルタリングには Sonnet を使用)を活用してわずか 2 時間で構築されたこのプロジェクトは、2007 年から 2026 年までの Hacker News のコメントをダウンロードし、信頼できるデータセットにフィルタリングしました。40,000 の歴史的コメントから 150 トピックにわたる 6,454 つの高品質なビデオが生み出されました。プラットフォームはデフォルトのサムネイル(全体の 50% がデフォルト)、アカウント登録やサーバーサイドのトラッキングを必要とせずブラウザ上に保存されるパーソナライズされたローカル推奨機能、そして便利なキーボードショートカットを提供しています。ユーザーはオープンデータセットをエクスポートして独自のバージョンを作成するか、エージェントを使用してさらにパーソナライズすることもでき、コミュニティが独自の信頼性のある推薦エンジン構築を可能にし、アルゴリズムによる摩擦を回避できるようになっています。

2026/11/01 4:16

TCP のUse-After-Free:CVE-2026-98432/CVSS 9.8

## 日本語翻訳: ## まとめ: 認証不要なリモートコード実行を許可する重大なセキュリティ脆弱性、CVE-2026-98432 が発生しており、Linux システムにおける Multipath TCP リッスナーを利用した攻撃が可能です。これは、Go 1.24 以降を実行している環境において特に深刻であり、デフォルトで危険なネットワーク構成を有効にできるようになっています。根本原因は不安定なメモリアクセス管理にあります:RCU(Read-Copy-Update)同期が完了する前に `kfree()` を使用して特定のデータを解放しており、安全な `kfree_rcu()` の代わりにこれを用いることで、ユーザー認証なしでシステムメモリへの操作が可能になっています。

2026/11/01 8:08

電気自動車が女性向けだった時代

## Japanese Translation: 直近の世紀の転換期に、米国自動車業界は急速な拡大を遂げ、メーカー数は 1898 年の約 50 社から 1910 年には数百社に達し、18 万 1 千台の車両を生産しました。1911 年以前、電気自動車は静かに運転でき排気ガスが出ず、バッテリースイッチで即座に始動するため、騒々しい手動クランクが不要だったという点で、ガソリン車に対して明確な優位性を誇っていました。しかし、これらのユーザーフレンドリーな機能にもかかわらず、普及には重大な技術的障壁があり、主としてバッテリーの航続距離の限界と、漏れに苦しむ不安定な鉛蓄電池によるものでした。所有者は充電に 12 時間かかるという大きな不便さに直面し、その際には水銀アーク整流器(ピーター・クーパー・ヒュイットが発明、ペルシー・H.トマスに付与された特許番号 968,896 および 1,163,706)のような巨大な設備を使用したり、車両のプラグイン方法が標準化されていなかったため予備のアダプターを携行する必要があったりと、不便さが伴いました。ゼネラル・エレクトリックはさらに、電気自動車を女性や夜間の自宅訪問をする医師にとって理想的な乗り物としてマーケティングしていました。具体的な例としては、ファニー・グッドナウが Gould Storage Battery Co. から入手した 30 セルの鉛蓄電池に依存し、専用のもので 12 時間の充電が必要となる水銀アーク整流器を使用して、米ドル 2,000(現在の価値で約 7 万 3 千ドル)で 64〜80 キロメートルの航続距離を得ていたことなどが挙げられます。この技術的な脆弱性が大量生産を防ぎ、価格を莫高にし、所有を少数の富裕層に限定していました。さらに、トーマス・エディソンは 1903 年にアルカリ電池を導入しましたが、当初は漏れを起こし、1910 年までに改善されました。エディソン自身も、自社ブランドのバッテリーモデルよりもデトロイト・エレクトリック・ビクトリアを好んでいました。ヘンリー・フォードの有効なモデル T の参入と、業界標準化されたコンポーネントの欠如により、S.R. Bailey & Co. といった初期の製造業者は 1915 年までに事業廃止に追い込まれました(約 500〜600 台の電気車両を生産)。特筆すべきは、ファニー・グッドナウの充電ステーションが商業市場が消滅した後も存続し、2022 年に遺産博物館に寄贈されるまで残ったことです。教訓は明らかです:バッテリーの耐久性を解決し、統一された充電インフラストラクチャを構築しない限り、安価でスケール可能な競争相手に対し、革新ある産業は生き延びることはできません。