data-storage について
データストア、データモデル、スケーリング方式を選ぶシステム設計スキルです。アクセスパターンと一貫性要件を基に構成を選び、レプリカやシャーディングの前にシンプルな手段を検討します。
主な機能
- 01リレーショナルと NoSQL の比較
- 02キー・クエリ・インデックスの設計
- 03レプリカとフェデレーションの評価
- 04パーティショニングとシャーディングの計画
- 05ストレージ障害シナリオの評価
活用シーン
- 01新規システムのストア選定
- 02クエリに応じたキーとインデックスの設計
- 03レプリカやシャーディングの導入判断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill data-storageインストール後、Claude Code で/data-storageを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md詳細
データストレージ
レコードをどこに保存するか、どのようにキー付けしてクエリするか、単一マシンを超えてストアをどう拡張するかを選択します。ストレージ層は後から変更するのが最も難しい層です。誤ったデータモデルやシャードキーは拡張の上限を固定化し、レプリケーションを誤ると、古いデータや失われたデータが気付かれないまま提供されます。
これを選ぶタイミング
状態を永続化するあらゆるシステムが対象です。SQL と NoSQL の選択、スキーマとアクセスパスの設計、インデックスの追加、ホットテーブルの分割、ノード間でのデータ分散(シャーディング/パーティショニング)、読み取りレプリカの追加、非正規化するデータの判断などが含まれます。「データを保存する」ために具体的なキーとクエリ形式が必要になった時点で、ここを参照してください。
選ばないタイミング
数値的な必要性が生じる前に、シャーディングやレプリカの追加、NoSQL の採用をしないでください(YAGNI)。適切にインデックスを設定した単一のリレーショナルノードで、約 1k QPS、数十 GB から数 TB 未満まで、十分に対応できます。ほとんどのシステムはこれを超えることがありません。シャーディングは運用コストを何倍にも増やし、結合やトランザクションを壊します。1 台のノードの書き込みスループットまたはデータセットサイズが本当に限界を超えた場合にのみ追加してください(→ back-of-the-envelope)。読み取りのキャッシュ(→ caching)や読み取りレプリカの追加は、シャーディングよりも先に検討できる、低コストな手段です。
最初に確認すること
ストアやトポロジーを選ぶ前に、次の質問に答えてください。これらが設計を左右します。
- データの形状と関係性 — 単純なキー・バリューですか?結合が必要な複雑な関係ですか?ドキュメント形式のデータですか?つながりのグラフですか?SQL と NoSQL の選択を左右します。
- アクセスパターン — データがどこに保存されるかだけでなく、どのように読み書きされますか。キーによるポイントルックアップ、範囲スキャン、アドホッククエリ、集計のどれですか?提供する必要があるクエリを中心にストアをモデル化してください。
- 読み取りと書き込みの比率と規模 — 読み取りと書き込みそれぞれの QPS、現在および保持期間終了時点での総サイズ。(QPS、ストレージ、シャード数の見積もりには →
back-of-the-envelope。) - 一貫性の要件 — 読み取りで最新の書き込みを確認する必要がありますか、それとも結果整合性で問題ありませんか?複数レコードにまたがるトランザクションは必要ですか?(CAP/一貫性の理論については →
consistency-coordination。) - レイテンシーと耐久性の目標 — p99 の読み取り/書き込みの予算と、ノード障害時にシステムが許容できる直近データの損失量。
選択肢
リレーショナル(SQL — Postgres、MySQL): 厳格なスキーマ、結合、ACID トランザクション。適した用途 データがリレーショナルで、整合性が重要であり、クエリが多様またはアドホックな場合です。数値的な必要性が明らかになるまでは、安全なデフォルトの選択肢です。
ドキュメント(MongoDB など): 柔軟なスキーマを持ち、構造に基づいてクエリできる自己完結型の JSON 風ドキュメントです。適した用途 レコード全体を読み書きし、スキーマが変化していく場合です。関係性は少ないことが前提です。
キー・バリュー(Redis、DynamoDB、Riak): キーによる O(1) の get/put が可能で、複雑なクエリには対応しません。適した用途 既知のキーだけでアクセスでき、非常に高いスループットが必要な場合です。
ワイドカラム(Cassandra、Bigtable、HBase): 行はパーティションキーで識別され、列は疎であり、範囲スキャンに備えてキーはソートされた状態で保持されます。適した用途 書き込み量が非常に大きく、既知の少数のクエリパターンを中心にテーブルを設計できる場合です。
グラフ(Neo4j): ノードとエッジで構成されます。適した用途 多対多の関係をたどるクエリが中心となる場合です(ソーシャルグラフ、レコメンデーションなど)。
スケーリングの手段(あらゆるストアに適用):
- インデックス作成 — クエリが全件スキャンせずに済むよう、二次構造を追加します。最初に検討する手段です。
- 読み取りレプリカ(リーダー・フォロワー) — 書き込みをフォロワーにコピーし、読み取りを処理させます。適した用途 読み取りが多く、多少の古さが許容される場合です。
- フェデレーション — データベースを機能別に分割します(ユーザー/製品/フォーラム)。適した用途 機能ドメインが独立してスケールし、結合することがまれな場合です。
- シャーディング/パーティショニング — シャードキーを使って、1 つの論理テーブルを複数のノードに分割します。適した用途 1 台のノードの書き込み能力またはデータセットサイズを超えた場合です。(このスキルの対象です。)
- 非正規化 — 結合を省くために、冗長なコピーを保存します。適した用途 読み取りが書き込みを大幅に上回り、結合がボトルネックとなる場合です。
ポリグロット永続化: 上記のうち複数を使い、それぞれを得意な用途に用います(例:注文には Postgres、セッションには Redis、全文検索には検索インデックス)。複数のストアを運用し、データを同期するコストが発生します。
トレードオフ
| 選択肢 | 解決すること | 悪化すること | 切り替えるタイミング |
|---|---|---|---|
| リレーショナル/SQL | 結合、ACID、アドホッククエリ、整合性 | 単一ノードの書き込み上限、スキーマ移行、水平スケールの難しさ | 書き込み量やサイズが単一ノードの限界を超える、またはスキーマが本当に流動的 → NoSQL/シャーディング |
| ドキュメント | スキーマの柔軟性、オブジェクト全体の読み取り | 結合ができない、ドキュメント間の整合性を手動で管理する必要がある、クエリエンジンが弱い | データがリレーショナルになる、または複数ドキュメントのトランザクションが必要 → SQL |
| キー・バリュー | 極めて高いスループット、シンプルな運用 | キーによるアクセスのみ、範囲クエリやセカンダリクエリができない | キー以外のクエリが必要 → ドキュメント/ワイドカラム |
| ワイドカラム | 書き込み負荷の高い環境でのスケール、ソート済みキーによる範囲スキャン | クエリを事前に把握する必要がある、キー設定後は柔軟性が低い、デフォルトで結果整合性 | アクセスパターンが不明、または多様 → リレーショナル |
| グラフ | 深い関係性のたどりやすさ | 専用ツールが少ない、シャーディングが難しい、大量スキャンに弱い | 関係性が浅い → リレーショナル/ドキュメント |
| インデックス作成 | スキャンを検索に変える | 書き込みが遅くなる、ストレージが増える、インデックスが肥大化する | 書き込み増幅による負荷が読み取りの利点を上回る |
| 読み取りレプリカ | 読み取り負荷の分散、冗長性 | レプリケーション遅延による古い読み取り、フェイルオーバー/昇格のロジック | 古い読み取りが許容できない → リーダーから読み取る/consistency-coordination |
| フェデレーション | ドメインごとのスケール、小さなワーキングセット | ドメインをまたぐ結合が機能しなくなる、アプリケーションのルーティングロジックが必要 | 単一ドメインにまだ収まらない → そのドメインをシャーディング |
| シャーディング | 書き込みとストレージの水平スケール | シャード間の結合/トランザクションが難しい、再シャーディングが大変、ホットシャード | 単一ノードに問題なく収まる、またはホットシャードが支配的 → 統合/キーの再設定 |
| 非正規化 | 読み取り処理でコストの高い結合をなくす | データの重複、書き込み時のファンアウト、整合性のずれ | 書き込み負荷によりファンアウトが新たなボトルネックになる → 正規化/キャッシュ |
高負荷時の挙動
ストレージでは、負荷や障害が増幅され、停止につながります。
- ホットシャード/有名人キー: 偏ったシャードキーにより、(話題になったユーザーや単一のテナントなどの)不均衡なトラフィックが 1つのノードに集中し、ほかのノードはアイドル状態になります。クラスター全体の負荷は低く見えても、1つのシャードは過負荷になります。対策: カーディナリティが高く、均等に分散される シャードキーを使います。ホットキーを分離するか、サブパーティションに分割します。
- レプリケーション遅延: 書き込みが急増すると、フォロワーがリーダーに遅れ、読み取り結果が古くなります。また、フェイルオーバー時に、直近の書き込みを失ったフォロワーが昇格する可能性もあります。 対策: 書き込み後の読み取りにはリーダーから読み取ります。遅延に上限を設け、アラートを設定します。
- 接続の枯渇: DB 接続ごとにメモリとバックエンドのプロセス/スレッドが必要です。トラフィックの急増(またはリトライストーム)により、DB が処理できる数を超える接続が開かれると、 すべてのクエリが遅くなるか、エラーになります。DB の稼働を保つのは、前段に置く(上限付きのプールを持つ)プーラーです。これがないと、データベースは負荷に耐えられず停止する SPOF になります。
- 書き込みの集中/ロック競合: ホット行や単一のシーケンス/カウンターによって書き込みが直列化され、インデックスの更新とロック待ちが積み重なります。
- 負荷が高い状況での再シャーディング: すでに過負荷の状態でリバランスを行うと、大量のデータ移動が発生し、クラスターが限界を超える可能性があります。限界に達する前に、容量を計画してください。
監視項目: レプリケーション遅延、シャードごとの QPS とサイズ(偏り)、接続プールの飽和度/待ち時間、低速クエリの割合、ロック待ち、ディスク/IOPS の余力。
適用方法
- 入力を明確にする — データの形、アクセスパターン、読み取りと書き込みの比率、一貫性の要件、レイテンシと耐久性の目標を確定します(最初に明確化を参照)。アクセスパターンが分からないままでは、どのストアを選んでも適切とは限りません。
- トレードオフの表からストアを選ぶ — データの形に合う選択肢を選びます。数値やリレーションシップのパターンによって除外されない限り、リレーショナルをデフォルトにします。解決できることだけでなく、各候補によって悪化することも明記します。
- インターフェースを確定する — スケーリングの仕組みを追加する前に、各クエリのプライマリーキー、パーティションキー/ソートキー、セカンダリーインデックスを書き出します(インターフェースの概要を参照)。重要なのはキーの決定です。
- コストの低い順にスケーリングの調整項目を設定する — インデックス、次にキャッシュ(→
caching)、次にリードレプリカ、次にフェデレーション、最後にシャーディングの順です。目標を満たす最初の段階で止めます。 - 負荷をかけて選択を検証する — ホットシャード、レプリケーションラグ、接続枯渇、再シャーディングを検討し(高負荷時の動作を参照)、偏りのあるキーや書き込みバーストによって単一ノードが過負荷にならないことを確認します。
- 規模を見積もってからプロバイダーを選ぶ —
back-of-the-envelopeを使ってシャード数とノードあたりの負荷を計算します。1ノードで済むなら、シャーディングはしません。一般的なレシピをデフォルトとし、クラウドが指定されている場合にのみプロバイダーファイルを読みます。 - 回答にトレードオフを示す — ここで行った選択ごとに1行を設けた
Decision | Solves | Worsens | Change it when…テーブルを提示します(例:選択肢、そのストア、障害時のポリシー)。最後の列には具体的なトリガー(数値または観測可能なシグナル)を記載します。そのうえで、最初に何が破綻するかを示します。
やるべきこと、避けるべきこと
やるべきこと
- クエリから始めます。アクセスパスを中心にストアをモデル化し、図を描く前に具体的なキーを決めます。
- 適切にインデックスを設定した単一のリレーショナルノードをデフォルトとし、シャーディングの前にインデックス+キャッシュ+レプリカを使い切ります。
- カーディナリティが高く、均等に分散するシャードキーを選びます。既知のホットキーは分離するか、サブパーティションに分割します。
- データベースの前段に上限付きのコネクションプーラーを配置し、レプリケーションラグとシャードごとの偏りを監視してアラートを設定します。
- ピーク時の書き込みQPSとデータセットのサイズからシャード数を計算し、大きい方を採用します。
避けるべきこと
- 数値によって必要性が明らかになる前に、NoSQL、レプリカ、シャードに手を伸ばしてはいけません(YAGNI)。
- データの形と一貫性の要件が分かる前に、流行を理由にストアを選んではいけません。
- レプリカが最新の読み取りを保証すると考えてはいけません。レプリケーションラグによって古いデータが返され、フェイルオーバーで直近の書き込みが失われることがあります。
- カーディナリティが低いキーや単調増加するキーでシャーディングしてはいけません。ホットシャードや書き込みの集中が発生します。
- すでに過負荷のクラスターを再シャーディングしてはいけません。限界に達する前に容量を計画します。
重要な数値
表の内容を繰り返すのではなく、back-of-the-envelope から数値を取り出します。ストレージの判断を左右する数値は、単一のRDBMSノードで約 1k QPS、キー・バリューノードで約 10k QPS、10 GBはRAMに収まり、10 TBには分散ストレージが必要です。これらを使って シャード数 = ピーク時の書き込みQPS ÷ ノードあたりのQPS(さらにサイズでも計算し、総バイト数 ÷ ノードあたりの容量とする)を求め、大きい方を採用します。1ノードで済む結果なら、シャーディングはしません。
インターフェースの概要
データモデルそのものが契約です(GUIDEの障害モード #8)。キーが書き出されるまでは、「NoSQLの箱」を選んでも何も決まりません。エンティティごとに次の点を確定します。
- プライマリーキー — 行/アイテムを一意に識別するものと、その検索方法。
- パーティション(シャード)キー+ソートキー — 例:ワイドカラム/DynamoDBの場合、
PK = user_id(ハッシュ、負荷を分散)、SK = created_at(ソート、「最新のN件の投稿」のような範囲スキャンを可能にする)。PKはカーディナリティが高く、均等に分散する必要があります。 - セカンダリーインデックス — サポートが必要なキー以外のアクセスパスと、それぞれに対応するクエリ。
- リレーショナル — テーブル、型付きの列、外部キー、各クエリを支えるインデックスを記載し、意図的に非正規化した箇所も示します。
具体的なキーと、それに対応するクエリがなければ、残りの設計は推測にすぎません。
プロバイダーの選択
まずは上記の汎用レシピを使用してください。ユーザーがクラウドを指定した場合は、
マネージドサービスの対応関係、クォータ/上限、プロバイダー固有のトレードオフについて、
references/providers/<provider>.md を確認してください。そのプロバイダーのファイルが存在しない場合は、
汎用レシピが回答となります。
図
データ層を可視化するには、リーダーと読み取りレプリカ、ルーターを備えたシャードクラスタ、
またはポリグロット分割など、プラグイン内の architecture-diagram スキルを使用します。
ルーティング矢印にシャードキーを示し、レプリケーションの方向を明示してください。
ここに Mermaid を埋め込まないでください。
関連する構成要素
caching— 読み取り負荷のオフロードに組み合わせて使うもので、手軽に始められる最初の手段です。 シャーディングの前に読み取りレプリカを導入する場合の代替手段です。consistency-coordination— CAP/整合性モデル、コンシステントハッシュ、クォーラム、 分散トランザクション/Saga というこのブロックが担当する概念は、ここで扱います。 古い読み取りやシャード間の原子性を許容できない場合は、このブロックと組み合わせて使います。back-of-the-envelope— このブロックに入力を提供します。ここで紹介する各手段を導入する(または不要と判断する) 根拠となる QPS、ストレージ、シャード数を算出します。scaling-evolution— このブロックに依存します。システムの成長に応じて、各ストレージ手段を導入する時期を順序立てて示します。system-design— このブロックへ誘導するオーケストレーターです。
参考資料
references/deep-dive.md— パーティショニング方式(レンジ/ハッシュ/ディレクトリ)、 レプリケーションの仕組みと競合解決、インデックスの内部構造(B-tree 対 LSM)、 正規化と非正規化、コネクションプーリング、再シャーディングを扱います。 データ層を詳細に設計する際にお読みください。references/providers/{generic,aws,azure,gcp}.md— サービスの対応関係、判断を左右する制限事項、 環境ごとの落とし穴を扱います。