mcpField
D
ddia-principles

ddia-principles

github.com/luoling8192データ・データベース17237 回インストール31 週間前に更新
リポジトリ

ddia-principles について

『Designing Data-Intensive Applications』の要点をまとめた学習ガイドです。データシステムの設計や運用を学ぶ人向けに、信頼性、ストレージ、分散整合性、派生データなどを整理しています。

主な機能

  1. 01信頼性とスケーラビリティの原則を要約
  2. 02データモデルとストレージエンジンを比較
  3. 03レプリケーションとパーティショニングを解説
  4. 04トランザクション分離と異常を整理
  5. 05分散整合性とコンセンサスを網羅

活用シーン

  1. 01データシステム設計の概念を復習
  2. 02ストレージやレプリケーション方式を比較
  3. 03整合性とトランザクションのトレードオフを学習

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/luoling8192/ai-coding-principles --skill ddia-principles

インストール後、Claude Code で/ddia-principlesを入力して読み込まれたか確認します。

詳細

データ集約型アプリケーションを設計する — 要点ガイド

出典:Martin Kleppmann、Designing Data-Intensive Applications 中心的な主張:現代のアプリケーションにおける課題の中核はデータであり、コンピューティングではありません。


パート I:データシステムの基礎

第1章:信頼性、スケーラビリティ、保守性
3つの柱
柱定義主な指標
信頼性障害が発生してもシステムが正しく動作すること障害 ≠ 失敗:障害に耐え、失敗を防ぐ
スケーラビリティ負荷の増加にシステムが適切に対応できることパーセンタイルで測定:p50、p95、p99、p999
保守性システムの運用、理解、進化が容易であること運用性 + シンプルさ + 進化性
障害のカテゴリ
  • ハードウェア:ランダムかつ独立して発生します(ディスク、RAM、電源)。冗長化(RAID、二重電源)で軽減します。
  • ソフトウェア:すべてのノードに同時に影響する系統的なバグです(うるう秒のバグ)。プロセス分離、監視、カオスエンジニアリングで軽減します。
  • 人的要因:停止の最大の原因です(設定ミス)。優れた抽象化、サンドボックス、カナリアデプロイ、高速なロールバックで軽減します。
スケーラビリティのパターン
  • 垂直(スケールアップ):より大きなマシンを使います。シンプルですが、上限があります。
  • 水平(スケールアウト):マシンを増やします(共有なし)。複雑ですが、上限はありません。
  • 弾力的:負荷を検知して自動スケールします。予測できないワークロードに適しています。

Twitterのファンアウトに関するケーススタディ:書き込みは4.6k回/秒ですが、読み取りは300k回/秒です。解決策:ほとんどのユーザーにはタイムラインを事前計算し(書き込み時ファンアウト)、著名人には読み取り時にマージします。

パフォーマンス:平均値ではなくパーセンタイルを使う
  • p50 = 中央値です。p99では、ユーザー体験にとってテールレイテンシが重要です。
  • Amazon:100msの遅延は売上を1%減少させます。
  • テールレイテンシの増幅:1つの遅いバックエンド呼び出しによって、並列リクエスト全体が遅くなります。

第2章:データモデルとクエリ言語
モデル選択ガイド
モデル最適な用途弱点
リレーショナル構造化データ、複雑な結合、ACIDトランザクションスキーマが厳格、OOPとのインピーダンスミスマッチ
ドキュメント階層型データ、柔軟なスキーマ、データ局所性結合が不得意、多対多の関係
グラフ高度につながったデータ、深さが可変のトラバーサルツールが未成熟、パーティション分割が困難
スキーマ戦略
  • 書き込み時スキーマ(リレーショナル):書き込み時に構造を強制します。早期にエラーを検出できますが、移行コストがかかります。
  • 読み取り時スキーマ(ドキュメント):読み取り時に構造を解釈します。柔軟ですが、アプリに検証の負担がかかります。
正規化と非正規化
  • 正規化:単一の信頼できる情報源、一貫した更新を実現しますが、結合が必要です。
  • 非正規化:読み取りは高速になりますが、一貫性が損なわれるリスクや更新時の異常が生じます。

傾向:モデルは収束しつつあります。PostgreSQLはJSONをサポートし、MongoDBは結合を追加しました。思想ではなく、アクセスパターンに基づいて選びます。


第3章:ストレージと検索
ストレージエンジンの比較
特徴B-TreeLSM-Tree
書き込みスループット低い(インプレース更新 + WAL)高い(シーケンシャルな追記)
読み取りレイテンシより予測しやすい複数のSSTableを確認する場合があります
書き込み増幅高い低い
スペース効率断片化する可能性があります圧縮効率が高い
トランザクションのサポートよりシンプル(ツリーノードをロック)より複雑
使用例PostgreSQL、MySQL、OracleLevelDB、RocksDB、Cassandra
OLTPとOLAP
観点OLTPOLAP
アクセスランダム、少数のレコードシーケンシャルスキャン、数百万行
ユーザーエンドユーザーアナリスト
データ現在の状態過去のイベント
規模GB–TBTB–PB
最適化対象低レイテンシスループット
データウェアハウジング
  • ETL:OLTP から抽出 → 変換 → ウェアハウスにロードします。
  • スター・スキーマ:中央のファクトテーブル(イベント)+ディメンションテーブル(属性)。ファクトテーブルは 100 列以上、ペタバイト規模になることがあります。
  • 列指向ストレージ:各列を個別に保存します。クエリが少数の列だけにアクセスする場合、I/O を大幅に削減できます。ビットマップエンコーディング、ランレングス圧縮、ベクトル化処理が可能になります。

第 4 章:エンコーディングと進化
フォーマットの比較
フォーマットサイズ(例)スキーマ進化言語間の互換性
JSON81 bytes暗黙的手動優秀
Thrift59 bytes必須フィールドタグ良好
Protobuf33 bytes必須フィールドタグ優秀
Avro32 bytes必須名前の照合良好
互換性のルール
  • 後方互換性:新しいコードで古いデータを読み取れます。(常に必須です)
  • 前方互換性:古いコードで新しいデータを読み取れます。(ローリングアップグレードに必要です)
  • ルール:デフォルト値を持つフィールドのみ追加・削除します。削除したフィールドタグは再利用しないでください。
データフローパターン
  1. データベース経由:ローリングデプロイ中に複数のコードバージョンが共存します。データはコードより長く存続します。
  2. サービス経由(REST/RPC):クライアントより先にサーバーを更新します。リクエストには後方互換性、レスポンスには前方互換性が必要です。
  3. 非同期メッセージング経由:プロデューサーとコンシューマーを分離します。独立したバージョン進化をサポートします。

避けるもの:言語固有のシリアライズ(Java Serializable、Python pickle)—ベンダーロックインとセキュリティリスクにつながります。


第 II 部:分散データ

第 5 章:レプリケーション
レプリケーションモデル
モデル書き込み競合ユースケース
シングルリーダー1 つのノードなし最も一般的(PostgreSQL、MySQL)
マルチリーダー複数のノード解決が必要マルチデータセンター、オフラインクライアント
リーダーレス任意のノード解決が必要Cassandra、Riak、Voldemort
同期レプリケーションと非同期レプリケーション
  • 同期:耐久性がありますが、レプリカに障害が発生するとブロックします。
  • 非同期:高速ですが、リーダーに障害が発生するとデータを失うリスクがあります。
  • 準同期:1 つのレプリカは同期し、残りは非同期です。現実的な妥協案です。
レプリケーションラグの問題と解決策
問題症状解決策
書き込み後の読み取りユーザーが自分の書き込みを確認できないユーザー自身のデータはリーダーから読み取る
単調読み取りデータが時間を遡るユーザーを 1 つのレプリカに固定する
一貫したプレフィックス読み取り因果順序が崩れる因果関係のあるデータを同じパーティションに書き込む
競合解決
  • 最終書き込み優先(LWW):シンプルですが、データを失います。キーが不変の場合に限り安全です。
  • マージ:値の和集合、連結、CRDT データ構造を使用します。
  • アプリケーションレベル:すべてのバージョン(「兄弟」)を返し、アプリに判断させます。
  • バージョンベクトル:レプリカごとの因果依存関係を追跡します。
クォーラム:w + r > n
  • w = 書き込み確認応答数、r = 読み取りクエリ数、n = レプリカの総数です。
  • スロッピークォーラム:パーティション発生中にホーム以外のノードでの書き込みを受け入れます(ヒンテッドハンドオフ)。可用性が向上する一方、一貫性は弱まります。

第 6 章:パーティショニング(シャーディング)
パーティショニング戦略
戦略メリットデメリット
キー範囲範囲クエリが効率的連番キーでホットスポットが発生するリスク
ハッシュ均等に分散範囲クエリができない
複合最初の部分をハッシュ化し、残りをソートより複雑ですが、両方の利点を備える
セカンダリインデックスのパーティショニング
  • ローカル(ドキュメントベース):各パーティションが自身のデータにインデックスを付けます。書き込みはシンプルですが、読み取りでは散布・収集が必要です。
  • グローバル(用語ベース):インデックスを用語ごとにパーティショニングします。読み取りは効率的ですが、書き込みでは複数のパーティションを更新します。
リバランシング
  • 固定パーティション数:ノード数より多くのパーティションを用意します。ノードの変更時に再分配します。(Riak、Elasticsearch)
  • 動的:サイズに応じて分割・マージします。(HBase、RethinkDB)
  • ノード数に比例:ノードごとに固定数のパーティションを割り当てます。(Cassandra)
リクエストのルーティング
  • 任意のノードにラウンドロビン方式で送信(必要に応じてノードが転送)
  • ルーティング層(パーティションを認識するプロキシ)
  • クライアント認識型(クライアントがパーティションマップを把握)
  • ZooKeeper:パーティションとノードの対応付けを一元管理します。HBase、Kafkaで使用されています。

第7章:トランザクション
分離レベル(弱い → 強い)
レベル防止するもの許容するもの実装
Read Committedダーティリード、ダーティライト非反復読み取り、更新の消失行ロック+古い値のコピー
Snapshot Isolation+ 非反復読み取りライトスキュー、ファントムMVCC(マルチバージョン)
Serializableすべてなし2PL、逐次実行、またはSSI
並行性の異常
異常説明例
ダーティリードコミットされていないデータを読み取る書き込み途中の送金を読み取る
ダーティライトコミットされていないデータを上書きする2人の購入者が同じ商品を「獲得」する
更新の消失読み取り・変更・書き込みの競合2つのカウンター増分処理が同時に実行される
ライトスキュー古い読み取り結果に基づいて判断する2人の医師がどちらも当直を外れる
ファントム新しい行によってクエリ結果が変わる会議室の二重予約
Serializableの実装
  1. 逐次実行:単一スレッド、インメモリ。高速ですが、スループットに限界があります。(VoltDB、Redis)
  2. 2フェーズロック(2PL):共有ロック/排他ロックをコミットまで保持します。強力ですが、遅く、デッドロックが発生しやすくなります。
  3. SSI(Serializable Snapshot Isolation):楽観的方式です。自由に実行し、コミット時に競合を検出します。読み取り負荷の高いワークロードで最高のパフォーマンスを発揮します。(PostgreSQL 9.1+)

第8章:分散システムの課題
3つの信頼性のない要素

ネットワーク:非同期パケットネットワークには、配信の保証もタイミングの保証もありません。クラッシュとネットワーク遅延を区別することはできません。障害検出に使えるのはタイムアウトだけですが、正しいタイムアウト値は存在しません。

クロック:

  • 壁時計:逆戻りすることがあります(NTP補正)。イベントの順序付けには決して使用しないでください。
  • 単調増加時計:経過時間の計測には安全ですが、ノード間の比較には使えません。
  • クォーツ時計のずれ:約200ppm → 30秒ごとに6msの誤差。

プロセス:GCの停止、VMの一時停止、ページフォールトにより、スレッドが予告なく停止します。一時停止したノードは、時間が経過したことを認識できません。

主な原則
  • 障害 ≠ 故障:部分障害を前提に設計します。一部のノードは動作し、ほかのノードは動作しないことがあります。
  • 真実は多数決で決まる:個々のノードだけではシステムの状態を判断できません。クォーラム投票によって決定します。
  • フェンシングトークン:単調増加するトークンによって、ゾンビプロセスが状態を破損するのを防ぎます。
  • 安全性と活性:安全性(悪いことが決して起きないこと)は常に維持されなければなりません。活性(良いことがいずれ起きること)には条件が伴う場合があります。

第9章:一貫性と合意
一貫性モデル(強い → 弱い)
モデル保証コスト
線形化可能性1つのコピーがあるかのように振る舞い、すべての操作がアトミック高レイテンシ、パーティション発生時の可用性低下
因果一貫性原因と結果の順序を尊重パフォーマンスが向上し、パーティションにも耐性がある
結果整合性レプリカが最終的に収束最高のパフォーマンス、最も弱い保証
線形化可能性のユースケース
  • リーダー選出/分散ロック
  • 一意性制約(ユーザー名、ファイル名)
  • チャネル間の調整(メッセージキュー+ストレージ)
合意アルゴリズム
  • 2PC:コーディネーター方式で、コーディネーターの障害時にはブロックします。実用的ですが、脆弱です。
  • Paxos/Raft/Zab:エポックベースのリーダー選出+クォーラム投票。ブロックしません。etcd、ZooKeeper、Consulで使用されています。
  • FLP不可能性:クラッシュが発生する純粋な非同期システムでは、合意の達成は不可能です。実用的なアルゴリズムではタイムアウトを使用します。
全順序ブロードキャスト ≡ 合意 ≡ 線形化可能CAS

この3つの問題は数学的に等価です。1つを解決すれば、すべてを解決できます。

ZooKeeper / etcd パターン
  • 調整のための小規模なコンセンサスクラスター(3~5 ノード)。
  • 線形化可能なアトミック CAS 操作。
  • セッションのハートビートによる障害検出。
  • 用途:リーダー選出、パーティション割り当て、分散ロック、サービスディスカバリー。

第 III 部:派生データ

第 10 章:バッチ処理
Unix の哲学 → MapReduce
  • 各プログラムは、1 つのことをうまく行います。
  • あるプログラムの出力 = 別のプログラムの入力です。
  • 入力は不変で、処理は決定論的です。
MapReduce パイプライン

Input → Mapper (extract key-value) → Sort/Partition → Reducer (aggregate by key) → Output

分散結合戦略
結合タイプ使用する状況方法
ソートマージ両方の入力が大きい結合キーでソートし、リデューサーでマージします
ブロードキャストハッシュ一方の入力が小さい(RAM に収まる)小さい側をハッシュテーブルとして読み込みます
パーティション化ハッシュ両方の入力が同じ方法でパーティション分割されているパーティションごとにハッシュ結合します
MapReduce の先へ:データフローエンジン(Spark、Flink、Tez)
  • ワークフロー全体を単一のジョブとして扱います。
  • 中間結果をパイプライン処理します(HDFS への完全な実体化を避けます)。
  • 可能な場合はデータをメモリ内に保持します。
  • 障害復旧のために計算のリネージ(RDD)を追跡します。
  • 反復アルゴリズムをサポートします(Pregel/BSP モデルによるグラフ処理)。

第 11 章:ストリーム処理
メッセージブローカーモデル
モデル配信順序再生用途
AMQP/JMSメッセージごとに確認応答し、その後削除順序の保証なしなしタスクキュー、非同期 RPC
ログベース(Kafka)オフセットベースで保持パーティション内の順序ありイベントストリーミング、CDC
変更データキャプチャ(CDC)

データベースの変更をイベントストリームとして抽出し、派生システム(検索インデックス、キャッシュ、データウェアハウス)を同期します。信頼できる情報源はデータベースのままであり、派生ビューはコンシューマーです。

イベントソーシング

状態を、ビジネスイベントの追加専用シーケンス(DB 操作ではありません)としてモデル化します。イベントは不変の事実です。現在の状態 = イベント履歴を畳み込んだ結果です。

ストリーム結合
結合入力 A入力 B状態
ストリーム-ストリームイベントイベント時間ウィンドウ付きバッファー
ストリーム-テーブルイベントDB スナップショット(CDC 経由)ローカルの実体化テーブル
テーブル-テーブルCDC ストリームCDC ストリーム派生した実体化ビュー
時間とウィンドウ処理
ウィンドウ説明
タンブリング固定サイズで重複なし(例:1 分ごと)
ホッピング固定サイズで重複あり(例:30 秒ごとに 1 分間のウィンドウ)
スライディング相互の時間しきい値内にあるすべてのイベント
セッションアクティビティの間隔でグループ化(例:30 分間の非アクティブ状態)

イベント時刻 ≠ 処理時刻です。 正確性を保つため、常にイベント時刻を使用します。ウォーターマークまたは訂正イベントの発行によって、遅延イベントを処理します。

処理保証
  • マイクロバッチ処理(Spark Streaming):約 1 秒のレイテンシーで、アトミックな小規模バッチを処理します。
  • チェックポイント(Flink):メッセージバリアを使用した定期的なスナップショットです。
  • 冪等性:メッセージオフセットまたは一意の ID を使用して重複を排除します。
  • エンドツーエンドの厳密に 1 回の処理には、冪等な出力と重複排除の両方が必要です。

第 12 章:データシステムの未来
データ統合パターン

すべてを処理できる単一のデータベースはありません。統合の基盤としてイベントログを使用します。

  1. すべての書き込みを、信頼できるイベントログ経由で行います。
  2. 派生システム(インデックス、キャッシュ、ML モデル)がログを消費します。
  3. 決定論的で冪等な関数が、レイヤー間のデータを変換します。
データベースの分割

関心事を分離します:

  • レコードシステム:信頼できる書き込みを記録します。
  • 派生システム:インデックス、キャッシュ、実体化ビューが変更ストリームを消費します。
  • 段階的な移行が可能になり、旧システムと新システムを並行稼働できます。
エンドツーエンドの厳密な一回限りの処理

低レベルの保証(TCP、DB トランザクション)だけでは、アプリケーションの正しさは保証されません。次の点が必要です。

  • 操作識別子:アプリケーションレベルで UUID ベースの重複排除を行います。
  • べき等な操作:一度実行しても複数回実行しても、同じ効果が得られます。
  • 一意制約の適用:パーティション分割されたストリーム処理によって行います。
非同期の制約適用

分散トランザクションの代わりに、次の手順を実行します。

  1. 制約フィールドに基づいて、リクエストをパーティション分割されたログにルーティングします。
  2. ストリームプロセッサーが競合するリクエストの順序を決定します。
  3. 違反を拒否し、出力ストリームを通じてクライアントに通知します。
  4. 一部のアプリケーションでは、一時的な違反を補償トランザクションで許容できます。
監査可能性
  • データを不変のイベントログとして扱うことで、再構築と検証が可能になります。
  • 暗号学的な監査証跡(Merkle ツリー)を実装します。
  • バックアップの復元とデータの再構築を定期的にテストします。

意思決定フレームワーク:クイックリファレンス

データモデルの選択
Many-to-many relationships?     → Relational or Graph
Hierarchical / nested data?     → Document
Highly connected data?          → Graph
Flexible / evolving schema?     → Document (schema-on-read)
Strong consistency required?    → Relational (ACID)
ストレージエンジンの選択
Write-heavy workload?           → LSM-tree (RocksDB, Cassandra)
Read-heavy, predictable?        → B-tree (PostgreSQL, MySQL)
Analytical queries?             → Column store (ClickHouse, Redshift)
Full-text search?               → Inverted index (Elasticsearch)
レプリケーション戦略の選択
Single datacenter?              → Single-leader
Multi-datacenter?               → Multi-leader
Offline-first clients?          → Multi-leader or Leaderless
Maximum availability?           → Leaderless with sloppy quorum
Strong consistency?             → Single-leader with sync replication
分離レベルの選択
Read-only analytics?            → Snapshot isolation
General OLTP?                   → Read committed (default in most DBs)
Financial / critical?           → Serializable (prefer SSI over 2PL)
High write contention?          → Serial execution (if data fits in RAM)
バッチ処理とストリーム処理の選択
Historical data reprocessing?   → Batch (Spark, Flink batch mode)
Real-time derived views?        → Stream (Kafka + Flink/Spark Streaming)
Both needed?                    → Unified engine (Flink) over Lambda architecture

コメント