ddia-systems について
ddia-systemsは、信頼性と拡張性を備えたデータシステムを設計するエンジニア向けのフレームワークスキルです。七つの領域に判断事項を整理し、診断スコアでアーキテクチャの不足点を示します。
主な機能
- 01データシステム設計の七領域を網羅
- 02データモデルとストレージエンジンを比較
- 03レプリケーション、分割、一貫性を検討
- 04バッチ、ストリーム、障害対応を評価
- 05診断を採点し改善点を提示
活用シーン
- 01データモデルとストレージエンジンの選定
- 02レプリケーションと分割の計画
- 03一貫性や並行処理の異常調査
- 04アーキテクチャの障害対応力の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wondelai/ skills --skill ddia-systemsインストール後、Claude Code で/ddia-systemsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/batch-stream.mddata-models.mdfault-tolerance.mdpartitioning.mdreplication.mdstorage-engines.mdtransactions.md詳細
データ集約型アプリケーションの設計フレームワーク
信頼性、スケーラビリティ、保守性に優れたデータシステムを構築するための、原則に基づいたアプローチです。データベースの選定、スキーマの設計、分散システムのアーキテクチャ設計、一貫性や耐障害性の検討に、これらの原則を適用してください。
中核となる原則
データはコードより長く存続します。 アプリケーションは書き直され、フレームワークは移り変わりますが、データは何十年も残ります。データレイヤーの長期的な正確性、永続性、進化しやすさを優先してください。ほとんどのアプリケーションは計算集約型ではなく、データ集約型です。難しい問題はデータの量、複雑さ、変化の速さにあり、一貫性・可用性・レイテンシーのトレードオフを明確にすることが、堅牢なシステムと脆弱なシステムを分けます。
スコアリング
目標:10/10。 以下の7つのクイック診断項目でデータアーキテクチャを評価します。根拠(意図的に選択され、文書化されたトレードオフ)を伴って「はい」と回答できる項目ごとに約1.4点を加点し、「いいえ」または不明の場合は0点とします。
- 9-10: データモデル、ストレージエンジン、レプリケーション、パーティショニング、分離レベル、派生データ、障害対応のすべての領域で、実際の読み取り/書き込み/一貫性の要件に合った意図的な選択が行われ、文書化されています。フェイルオーバーもテスト済みです。
- 5-6: 中核となる選択は行われていますが、診断項目が2つか3つ失敗しています。たとえば、デフォルトの分離レベルが不明、ホットキーのリスクに未対応、またはフェイルオーバーが未テストです。
- <=3: 要件ではなく、慣れを基準に選択されています。障害モード(レプリケーションの遅延、書き込みスキュー、ホットパーティション)が無視され、偶発的な複雑さが支配的になっています。
現在のスコア、失敗した診断項目、10/10に到達するために必要な改善点を報告してください。
DDIA フレームワーク
データ集約型システムを考察するための7つの領域:
1. データモデルとクエリ言語
中核となる概念: データモデルは、問題をどう捉えるかを左右します。リレーショナル、ドキュメント、グラフの各モデルは、それぞれ異なる制約を課し、異なるクエリパターンを可能にします。
有効な理由: 不適切なデータモデルを選ぶと、表現方法の不一致を補うためにアプリケーションコードが必要になり、時間の経過とともに積み重なる偶発的な複雑さが生じます。
主な洞察:
- リレーショナルモデルは多対多の関係やアドホッククエリに優れています。ドキュメントモデルは一対多の関係やデータの局所性に、グラフモデルは相互に接続されたデータの再帰的な探索に優れています。
- スキーマオンライト(リレーショナル)は早期にエラーを検出します。スキーマオンリード(ドキュメント)は柔軟性をもたらします。
- ポリグロット永続化、つまりアクセスパターンに応じて異なるストアを使い分ける方法が、適切な選択となることがよくあります。
- オブジェクト・リレーショナルインピーダンスミスマッチは実際のコストです。ドキュメントモデルは、自己完結型の集約においてこのミスマッチを軽減します。
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| ネストされたデータを持つユーザープロフィール | 自己完結型の集約にドキュメントモデルを使用 | プロフィール、住所、設定を1つのMongoDBドキュメントに格納 |
| ソーシャルネットワークのつながり | 関係の探索にグラフモデルを使用 | Neo4j Cypher:友達の友達を検索する MATCH (a)-[:FOLLOWS*2]->(b) |
| 結合を伴う金融台帳 | 参照整合性にリレーショナルモデルを使用 | PostgreSQLで口座、取引、明細の間に外部キーを設定 |
リレーショナル、ドキュメント、グラフのどれを選ぶか、またはスキーマオンリードを評価するときは、references/data-models.mdを参照してください。詳しいトレードオフ一覧とクエリ言語の比較が記載されています。
2. ストレージエンジン
基本概念: ストレージエンジンでは、読み取り性能と書き込み性能の間でトレードオフが生じます。ログ構造型エンジン(LSM ツリー)は書き込みを最適化し、ページ指向型エンジン(B ツリー)は読み取りと書き込みのバランスを取ります。
主なポイント:
- LSM ツリー:追記専用の書き込みと定期的なコンパクションにより、優れた書き込みスループットを実現しますが、読み取り増幅は大きくなります
- B ツリー:インプレース更新により、読み取りレイテンシーが予測しやすくなりますが、ページ分割による書き込み増幅が発生します
- 書き込み増幅(1 回の論理書き込みによって複数回の物理書き込みが発生すること)は、書き込み回数に限りがある SSD で重要です
- 列指向ストレージは、圧縮とベクトル化処理によって分析クエリの性能を大幅に向上させます
- インメモリデータベースが高速なのは、ディスクを使わないからではなく、エンコードのオーバーヘッドを回避できるからです
コードでの活用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| 高い書き込みスループット | LSM ツリーエンジン | 1 秒あたり 100K 件以上の書き込みが発生する時系列データの取り込みに Cassandra または RocksDB を使用 |
| 読み書きが混在する OLTP | B ツリーエンジン | トランザクションのポイント検索に PostgreSQL の B ツリーインデックスを使用 |
| 分析クエリ | 列指向ストレージ | 数十億行から少数の列をスキャンする場合に ClickHouse または Parquet を使用 |
ワークロードが読み取りまたは書き込みのどちらかに制約される場合や、インデックスを選択する必要がある場合は、references/storage-engines.md を参照してください。読み書きパスの図、コンパクション戦略、列指向ストレージ、ベンチマークに基づく判断手順を追加で解説しています。
3. レプリケーション
基本概念: レプリケーションは、耐障害性、スケーラビリティ、レイテンシーの低減を目的として、複数のマシンにデータのコピーを保持します。中心的な課題は、変更を一貫性のある形で処理することです。
効果がある理由: どのレプリケーション戦略も、一貫性、可用性、レイテンシーの間でトレードオフが生じます。トレードオフを明示することで、高負荷時や障害時に初めて表面化する微妙な異常を防げます。
主なポイント:
- シングルリーダー:シンプルで、強い一貫性を実現できますが、リーダーがボトルネックかつ単一障害点になります
- マルチリーダー:データセンター間で書き込み可用性が向上しますが、競合解決が複雑になります
- リーダーレス:クォーラム読み書きによって可用性を最大化できますが、慎重な競合処理が必要です
- レプリケーションラグは、読み書きの順序保証、単調読み取り、因果関係に関する整合性違反を引き起こします
- 同期レプリケーションは耐久性を保証しますが、レイテンシーが増加します。一方、非同期レプリケーションではフェイルオーバー時にデータが失われるリスクがあります
- CRDT と最終書き込み優先は、正しさの保証が大きく異なる方法で競合を解決します
コードでの活用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| 読み取り中心のウェブアプリ | 読み取りレプリカを備えたシングルリーダー | PostgreSQL のプライマリと読み取りレプリカを pgBouncer の背後に配置 |
| マルチリージョンでの書き込み | マルチリーダーレプリケーション | 有界な古さを許容する CockroachDB または Spanner |
| ショッピングカートの可用性 | マージ機能を備えたリーダーレス方式 | 最終書き込み優先を使用する DynamoDB、またはアプリケーションレベルでカートをマージ |
シングルリーダー、マルチリーダー、リーダーレスのいずれかを選ぶ場合や、古い読み取りをデバッグする場合は、references/replication.md を参照してください。ラグによる異常、クォーラムの計算、競合解決、CRDT について追加で解説しています。
4. パーティショニング
中核となる概念: パーティショニング(シャーディング)では、データをノード間で分散し、各ノードがその一部を処理することで、単一マシンを超えた水平スケーリングを可能にします。
主な知見:
- キー範囲パーティショニングは効率的な範囲スキャンに適していますが、連番キーではホットスポットが発生するリスクがあります
- ハッシュパーティショニングは負荷を均等に分散しますが、ソート順を失わせるため、範囲クエリのコストが高くなります
- ローカルセカンダリインデックスではクエリの散開・集約が必要です。グローバルセカンダリインデックスではパーティションをまたぐ更新が必要です
- ハッシュを使っていても、単一のキーが極端に人気になるとホットスポットが発生します(有名人問題)
- リバランスの戦略:パーティション数を固定する、動的に分割する、またはノード数に比例させる
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| 時系列データ | 時刻とソースによるキー範囲パーティショニング | (sensor_id, date) でパーティショニングし、当日分の書き込みホットスポットを回避する |
| 大規模なユーザーデータ | ユーザー ID によるハッシュパーティショニング | Cassandra の user_id に対するコンシステントハッシュで均等に分散する |
| 有名人/ホットキー問題 | ランダムな接尾辞を使ったキー分割 | ホットキーにランダムな数字を追加し、10 個のサブパーティションに読み取りを分散する |
シャーディングやホットキーへの対処時は、references/partitioning.md を参照してください。リバランス戦略、リクエストのルーティング、ローカルセカンダリインデックスとグローバルセカンダリインデックスのトレードオフについて解説しています。
5. トランザクションと一貫性
中核となる概念: トランザクションは安全性を保証する(ACID)ことで、トランザクションの範囲内では障害や並行性が存在しないかのように扱え、アプリケーションコードを簡素化します。
有効な理由: トランザクションがなければ、アプリケーションコードのすべての箇所で部分的な障害、競合、並行変更に対処する必要があります。トランザクションを使うと、その複雑さをデータベースに移し、一度正しく処理できます。
主な知見:
- 分離レベルには、未コミット読み取り、コミット済み読み取り、スナップショット分離、直列化可能といった段階があります
- ほとんどのデータベースのデフォルトはコミット済み読み取りかスナップショット分離であり、直列化可能ではありません。そのため、許容される異常を理解する必要があります
- 書き込みスキュー:2 つのトランザクションが同じデータを読み取り、それぞれ判断して異なるレコードに書き込みます。行ロックでは防げません
- 直列化可能スナップショット分離(SSI)は、楽観的な方式で完全な直列化可能性を実現します。ブロックは発生しませんが、競合時には中断されます。2 フェーズロックはブロックを発生させ、競合時にはデッドロックを引き起こします
- 分散トランザクション(2 フェーズコミット)はコストが高く、壊れやすいため、代わりに単一パーティション内での操作を中心に設計してください
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| 口座残高の振り替え | 直列化可能トランザクション | BEGIN; UPDATE accounts ... -100 WHERE id=1; UPDATE accounts ... +100 WHERE id=2; COMMIT; |
| 在庫の予約 | 書き込みスキューを防ぐための SELECT FOR UPDATE | 減算する前に SELECT stock FROM items WHERE id = X FOR UPDATE を実行する |
| サービス間の操作 | 分散トランザクションの代わりにサーガを使う | カードに請求して在庫を予約し、失敗した場合は補償処理として返金を実行する |
分離レベルの設定や並行性のバグの調査時は、references/transactions.md を参照してください。分離レベルごとの異常をまとめた表、書き込みスキューの例、2PL と SSI の比較、分散トランザクションの落とし穴について解説しています。
6. バッチ処理とストリーム処理
核心となる概念: バッチ処理は、範囲が定まったデータセットを一括で変換します。ストリーム処理は、範囲が定まらないイベントストリームを継続的に変換します。どちらも派生データを計算します。
効果がある理由: 信頼できるデータの保存先と、派生データ(キャッシュ、インデックス、マテリアライズドビュー)を分離すると、それぞれを独立して最適化でき、要件の変更時には元データから再構築できます。
主なポイント:
- MapReduceは概念的にはシンプルですが、運用が扱いにくい手法です。データフローエンジン(Spark、Flink)は任意のDAGでこれを一般化します
- 変更データキャプチャ(CDC)を使うと、データベースへの書き込みを、下流システムが利用できるストリームに変換できます
- ストリームとテーブルの双対性:ストリームはテーブルの変更ログであり、テーブルはストリームのマテリアライズされた状態です
- Exactly-onceセマンティクスには、冪等な操作またはトランザクション対応の出力が必要です
- 時間ウィンドウ(タンブリング、ホッピング、セッション)は、範囲が定まらないストリームを集計するうえで不可欠です
コードへの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| 日次分析パイプライン | Sparkによるバッチ処理 | S3からその日のイベントを読み込み、集計して、データウェアハウスに書き込みます |
| リアルタイムの不正検知 | Flinkによるストリーム処理 | Kafkaの決済イベントに対し、5秒間のタンブリングウィンドウでルールを適用します |
| 検索インデックスの同期 | 変更データキャプチャ | DebeziumがPostgreSQLのWALをキャプチャし、KafkaがElasticsearchにデータを送ります |
| 監査証跡/イベントの再生 | イベントソーシング | OrderPlaced、OrderShippedイベントを保存し、再生して状態を再構築します |
パイプラインの設計や、信頼できるデータの保存先からのデータ派生については、references/batch-stream.mdを参照してください。データフローエンジン、CDCの接続、ウィンドウ処理、Exactly-onceを実現する手法が追加されています。
7. 信頼性とフォールトトレランス
核心となる概念: 障害の発生は避けられませんが、システム全体の停止は防げます。信頼性の高いシステムは、個々のコンポーネントに障害が発生しても、正しく動作し続けます。障害に対抗するのではなく、障害を前提に設計します。
主なポイント:
- フォールトとは、個々のコンポーネントが仕様から逸脱することです。フェイリアとは、システム全体が停止することです。フォールトトレランスは、前者が後者になるのを防ぎます
- ハードウェア障害はランダムかつ独立して発生しますが、ソフトウェア障害は相関性があり、体系的です(より危険です)
- サービス停止の主な原因は人為的ミスです。ミスの機会を減らし、復旧能力を高めます
- タイムアウトは基本的な障害検出手段ですが、調整は困難です。短すぎると誤検知が発生し、長すぎると復旧が遅れます
- 安全性の性質(悪いことが起きないこと)は常に保たなければなりません。一方、生存性の性質(良いことが最終的に起きること)は一時的に損なわれる場合があります
- ブロックチェーン以外では、ビザンチンフォールトトレランスが必要となることはほとんどありません。クラッシュストップまたはクラッシュリカバリーを前提とします
コードへの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| サービス間通信 | タイムアウト+バックオフ付きリトライ | ジッターを加えたretry(max=3, backoff=exponential(base=1s, max=30s)) |
| リーダー選出 | 合意アルゴリズム(Raft/Paxos) | 分散ロックとリーダー選出にetcdまたはZooKeeperを使用します |
| 段階的な縮退 | サーキットブレーカー | Resilience4j:10秒間のウィンドウ内で失敗率が50%を超えた場合、回路を開きます |
タイムアウト/リトライの調整や合意形成の導入については、references/fault-tolerance.mdを参照してください。障害の分類、タイムアウト調整の計算、Raft/Paxosの仕組み、安全性/生存性の保証が追加されています。
よくある間違い
| 間違い | 失敗する理由 | 修正方法 |
|---|---|---|
| 人気でデータベースを選ぶ | エンジンごとにトレードオフが根本的に異なります | 実際の読み書きパターンに合ったストレージエンジンを選びます |
| レプリケーションラグを無視する | 古いデータの読み取り、ファントムリード、更新の消失が発生します | 書き込み後の読み取りと単調読み取りの保証を実装します |
| あらゆる場面で分散トランザクションを使う | 2PCは低速で壊れやすく、コーディネーターが単一障害点になります | 単一パーティションでの操作を設計し、サービス間ではサーガを使います |
| すべてをハッシュパーティショニングする | 範囲クエリができなくなります | 時系列データにはキー範囲パーティショニングを、局所性のためには複合キーを使います |
| 直列化可能分離を前提にする | デフォルトの分離レベルはより弱く、本番環境でライトスキューが発生します | 実際のデフォルトを確認し、必要な箇所では明示的なロックを使います |
| バッチ処理とストリーム処理を混同する | 適切でないツールを選ぶと、レイテンシや不要な複雑さが生じます | データの有界性とレイテンシ要件に合った処理モデルを選びます |
| すべての障害は復旧可能だと考える | 破損やビザンチン障害には異なる対処が必要です | 障害を分類し、それぞれに復旧戦略を設計します |
簡易診断
| 質問 | いいえの場合 | 対応 |
|---|---|---|
| 他の選択肢ではなく、このデータベースを選んだ理由を説明できますか? | 要件ではなく、慣れで選んでいます | データモデルとの適合性、読み書き比率、一貫性の要件、スケーリング方法を評価します |
| データベースのデフォルトの分離レベルを把握していますか? | 潜在的な並行性バグがあります | ドキュメントを確認し、ライトスキューとファントムリードをテストします |
| レプリケーション戦略を明示的に選択していますか? | 一貫性や耐久性に関する前提が暗黙的です | 同期か非同期か、フェイルオーバー時の挙動、許容できるラグを文書化します |
| システムはホットパーティションキーに対応できますか? | 人気の高いエンティティがクラスター全体を停止させる可能性があります | ホットキーに対してキー分割や負荷抑制を追加します |
| システム・オブ・レコードと派生データを分離していますか? | 変更のたびにすべてを移行する必要があります | CDCやイベントソーシングを導入して分離します |
| タイムアウトと再試行はデフォルトのままではなく、適切に調整されていますか? | 障害の連鎖や不要な遅延が発生します | p99を測定し、タイムアウトをp99より長く、障害の連鎖が起きるしきい値より短く設定します |
| 本番環境の条件でフェイルオーバーをテストしましたか? | 復旧計画が机上のものにとどまっています | リーダーの停止、ネットワークの分断、ディスクの容量不足などのカオス実験を実施します |
さらに読む
詳細な図と研究資料への参照を含む完全版については、以下をご覧ください。
- "Designing Data-Intensive Applications" Martin Kleppmann 著
著者について
Martin Kleppmann はケンブリッジ大学の分散システム研究者で、LinkedIn と Rapportive の元エンジニアです。CRDT とローカルファーストソフトウェアに関する研究で知られています。著書 Designing Data-Intensive Applications(2017年)は、データシステムを構築するエンジニアにとって決定版の参考書であり、分散システムの概念をわかりやすく実践的に解説していると高く評価されています。