system-design
新規サービスをゼロから設計する場合、技術仕様書やRFCを作成する場合、データベースや通信パターンを選定する場合、キャパシティを見積もる場合、またはスケーラビリティと信頼性のギャップについて設計をレビューする場合に使用します。
description の原文を見る
Use when designing a new service from scratch, writing a tech spec or RFC, selecting a database or communication pattern, estimating capacity, or reviewing a design for scalability and reliability gaps.
SKILL.md 本文
システム設計
スクラッチからシステムを設計し、アーキテクチャの意思決定を行い、キャパシティを推定し、スケーラビリティと信頼性のために構築するための体系的なエンドツーエンドガイドです。
利用するタイミング
- 新しいサービス、プラットフォーム、またはシステムをスクラッチから設計する
- 設計ドキュメント、技術仕様書、またはRFCを作成する
- 技術選定またはアーキテクチャの意思決定を行う
- アーキテクチャ決定レコード(ADR)を作成する
- スケーラビリティ、信頼性、またはセキュリティの問題がないか設計をレビューする
- 大きな負荷を処理することが予想される機能のキャパシティプランを立てる
設計プロセス
以下の6つのステップを順番に実行します。各ステップが次のステップに情報を提供します。ステップをスキップすると手戻りが発生します。
- 要件を明確化する — 機能要件(何ができるか)、非機能要件(スケール、レイテンシ、可用性、耐久性)、制約(予算、既存スタック、チームサイズ、タイムライン)を収集します。
- スケールを推定する — QPS、ストレージ、帯域幅。キャパシティ推定セクションを参照してください。
- APIコントラクトを定義する — システムが公開するAPIは何か、リクエスト/レスポンスの形状、認証メカニズム、バージョニング戦略は何か。
api-designスキルでREST/gRPCの規約を参照してください。 - データモデルを設計する — エンティティ、リレーションシップ、アクセスパターン、ストレージ技術を定義します。技術選定マトリックスに基づいてSQLかNoSQLかを選択します。
- コンポーネントを設計する — 高レベル設計(HLD)図を作成し、サービスの責務を割り当て、通信パターン(同期と非同期)を定義します。
- ボトルネックを特定する — 単一障害点、スケーリングの限界、ホットパーティション、カスケード障害のリスクを設計をロックする前に見つけます。
意思決定フレームワーク
| 非機能要件 | 設計への影響 |
|---|---|
| 高い読み取りスループット | 読み取りレプリカ、キャッシュレイヤー |
| 高い書き込みスループット | シャーディング、非同期書き込み、CQRS |
| 低レイテンシ | CDN、インプロセスキャッシュ、コロケーション |
| 高い可用性 | マルチAZ、ロードバランシング、サーキットブレーカー |
| 強い一貫性 | シングルリーダーDB、分散トランザクション(慎重に使用) |
| 結果整合性 | イベント駆動、CQRS + イベントソーシング可能 |
キャパシティ推定
設計作業の前の概算計算です。これらの数値を使ってコンポーネントをサイズし、明らかなスケーリング問題を早期に捕捉します。
概算テンプレート
# トラフィック
日次アクティブユーザー(DAU): X
ユーザーあたり1日のリクエスト数: Y
QPS (平均) = X * Y / 86400
QPS (ピーク) = 平均 * 3x
# ストレージ
リクエストあたりのデータ: Z バイト
日次新規データ = QPS_平均 * 86400 * Z
1年分のストレージ = 日次 * 365
レプリケーション(3x)含む: 合計 * 3
# 帯域幅
インバウンド = QPS * 平均リクエストサイズ
アウトバウンド = QPS * 平均レスポンスサイズ
推定例
# Twitterスケールの書き込みパス例
DAU = 100M
ユーザーあたり1日のツイート数 = 0.5
QPS (平均) = 100M * 0.5 / 86400 ≈ 580 QPS
QPS (ピーク) = 580 * 3 ≈ 1,750 QPS
ツイートサイズ = 300 バイト
日次新規データ = 580 * 86400 * 300 ≈ 15 GB/日
1年 = 15 * 365 ≈ 5.5 TB
3xレプリケーション含む ≈ 16.5 TB/年
レイテンシ参考値
| 操作 | レイテンシ |
|---|---|
| L1キャッシュ | ~1 ns |
| L2キャッシュ | ~10 ns |
| RAM読み取り | ~100 ns |
| SSD読み取り | ~100 µs |
| ネットワークラウンドトリップ(同一DC) | ~500 µs |
| ネットワークラウンドトリップ(クロスリージョン) | ~30–100 ms |
| HDD シーク | ~10 ms |
これらの数値を頭に入れてください。誰かが「DBコールを追加すればいい」と言ったら、それはSSDで最小でも~100 µsかかります。クエリが複雑またはDBがクロスリージョンの場合はもっとかかります。
高レベル設計
コンポーネントタイプ
| コンポーネント | 責務 | いつ追加するか |
|---|---|---|
| ロードバランサー | トラフィック分散、ヘルスチェック、SSL終了 | 複数のアプリインスタンス |
| APIゲートウェイ | 認証、レート制限、ルーティング、プロトコル変換 | 公開API、マイクロサービス |
| アプリケーションサーバー | ビジネスロジック | 常に |
| キャッシュ(Redis/Memcached) | DB読み取り削減、セッションストレージ | ホットデータ、セッション状態 |
| リレーショナルDB | ACIDトランザクション、構造化データ | ほとんどのワークロード |
| NoSQL DB | 柔軟なスキーマ、高い書き込みスループット、時系列 | 特定のアクセスパターン |
| メッセージキュー | 非同期処理、疎結合、ファンアウト | バックグラウンドジョブ、イベント駆動フロー |
| CDN | 静的アセット配信、エッジキャッシング | Webアプリ、高読み取り・グローバルコンテンツ |
| オブジェクトストレージ(S3) | ファイル、画像、バックアップ | バイナリデータ、大規模ファイル |
| 検索エンジン(Elasticsearch) | フルテキスト検索、複雑なクエリ | 検索、ログ分析 |
ダイアグラム規約(テキストベース)
Mermaidが利用不可の場合、ASCIIブロックダイアグラムを使用します。縦線は主要なリクエストパスを示し、横線は非同期またはセカンダリフローを示します。
クライアント
│
▼
[CDN]──────────────────────────────────┐
│ │
▼ 静的
[ロードバランサー] アセット
│
├──► [アプリサーバー 1]
├──► [アプリサーバー 2] ──► [キャッシュ (Redis)]
└──► [アプリサーバー N]
│
▼
[プライマリDB] ──► [読み取りレプリカ 1]
──► [読み取りレプリカ 2]
メッセージキューの追加
[アプリサーバー]
│
▼
[メッセージキュー (Kafka/SQS)]
│
├──► [ワーカー 1: メール通知]
├──► [ワーカー 2: 分析パイプライン]
└──► [ワーカー 3: 検索インデックス]
プロデューサーとコンシューマーを疎結合にします。アプリサーバーはエンキューして即座に返却し、ワーカーはリクエストパスをブロックしないで非同期に処理します。
低レベル設計
シーケンス図(Mermaid)
複数サービス間のインタラクションを説明するためにMermaidシーケンス図を使用します。常に正常系を最初に示し、エラーケースは別に示します。
例: 認証フロー
sequenceDiagram
participant Client
participant API
participant AuthService
participant DB
Client->>API: POST /auth/login {email, password}
API->>AuthService: validate(email, password)
AuthService->>DB: SELECT user WHERE email=?
DB-->>AuthService: user record
AuthService->>AuthService: bcrypt.verify(password, hash)
AuthService-->>API: {userId, roles}
API-->>Client: {access_token, refresh_token}
例: トークンリフレッシュのエラーパス
sequenceDiagram
participant Client
participant API
participant AuthService
Client->>API: POST /auth/refresh {refresh_token}
API->>AuthService: validate_refresh(token)
AuthService-->>API: TokenExpiredError
API-->>Client: 401 Unauthorized {error: "token_expired"}
クラス/インターフェース設計
実装の境界ではなく、サービスの境界でインターフェースを定義します。抽象化に依存し、モジュールの境界を超えて具象クラスに依存しないでください。
UserRepository インターフェース
from abc import ABC, abstractmethod
from typing import Optional
from uuid import UUID
class UserRepository(ABC):
@abstractmethod
def find_by_id(self, user_id: UUID) -> Optional["User"]:
...
@abstractmethod
def find_by_email(self, email: str) -> Optional["User"]:
...
@abstractmethod
def save(self, user: "User") -> "User":
...
@abstractmethod
def delete(self, user_id: UUID) -> None:
...
PostgresUserRepository の実装
class PostgresUserRepository(UserRepository):
def __init__(self, db: Session):
self._db = db
def find_by_id(self, user_id: UUID) -> Optional[User]:
return self._db.query(UserModel).filter_by(id=user_id).first()
def find_by_email(self, email: str) -> Optional[User]:
return self._db.query(UserModel).filter_by(email=email).first()
def save(self, user: User) -> User:
self._db.merge(user)
self._db.commit()
return user
def delete(self, user_id: UUID) -> None:
self._db.query(UserModel).filter_by(id=user_id).delete()
self._db.commit()
サービスレイヤーはUserRepositoryのみをインポートします。PostgresをDynamoDBに切り替えるには、新しい実装クラスを追加するだけで、サービスの変更は不要です。
ステートマシン
エンティティが明確に定義されたライフサイクルを持ち、離散状態と遷移を持つ場合、ステートマシンとしてモデル化します。一般的な例: 注文、サブスクリプション、支払い、オンボーディングフロー。
ステートマシンを使用すべき場合:
- エンティティが3つ以上の状態を持つ
- 遷移にサイドエフェクト(メール送信、カード決済、レコード作成)がある
- 無効な遷移は拒否する必要がある
注文ライフサイクルの例
| 現在の状態 | イベント | 次の状態 | アクション |
|---|---|---|---|
| PENDING | payment_confirmed | PAID | 注文確認メールを送信 |
| PAID | items_shipped | SHIPPED | 配送通知を送信 |
| SHIPPED | delivery_confirmed | DELIVERED | マーチャントに資金をリリース |
| PAID | cancellation_requested | CANCELLED | 払い戻しを実行 |
| SHIPPED | cancellation_requested | REFUND_PENDING | 返品プロセスを開始 |
| DELIVERED | refund_requested | REFUND_PENDING | 払い戻しレビューを開始 |
現在の状態をデータベースに保存します。現在の状態からの有効な遷移がないイベントは拒否します。タイムスタンプと実行者とともに、すべての遷移をログに記録します。
アーキテクチャ決定レコード(ADR)
ADRは、重要なアーキテクチャ選択のコンテキスト、決定、および結果をキャプチャします。逆転するのに費用がかかるまたは破壊的になる決定を行うときはいつでも、ADRを作成します。
テンプレート
# ADR-NNNN: [短いタイトル]
## ステータス
[Proposed | Accepted | Deprecated | Superseded by ADR-XXXX]
## コンテキスト
[問題は何か、どのような力が働いているか]
## 決定
[何をすることに決定したか]
## 結果
### ポジティブ
- ...
### ネガティブ
- ...
## 検討した代替案
| オプション | 利点 | 欠点 | 却下理由 |
|--------|-----|------|--------|
| ... | ... | ... | ... |
ADR規約
- ファイル名:
docs/adr/0001-use-postgres-for-primary-store.md - 番号付け: シーケンシャル、4桁にゼロパディング。既存のADRの番号を変更しないこと。
- ライフサイクル:
Proposed→Accepted→ (Deprecated|Superseded) - 廃止されたADR: ファイルを保持します。上部にメモを追加: 「Superseded by ADR-0012」。前に進むリンク、決して削除しないこと。
- ADRを書くべき場合: プライマリデータベースの変更、通信パターンの切り替え、新しいフレームワークの採用、認証戦略の変更、削除が難しい新しい外部依存の追加。
- ADRを書くべきでない場合: ライブラリバージョンのバンプ、細微なリファクタリング、アーキテクチャへの影響のないツール設定の好み。
ADR例
# ADR-0003: プライマリデータストアにPostgreSQLを使用
## ステータス
Accepted
## コンテキスト
プライマリリレーショナルストアが必要です。チームは強いSQL専門知識を持ちます。
アクセスパターンは主にリレーショナルで複雑なジョインクエリです。
金融操作のためにACIDトランザクションが必要です。
## 決定
プライマリリレーショナルデータベースとしてPostgreSQL 15を使用します。
## 結果
### ポジティブ
- 完全なACIDコンプライアンス
- リッチなクエリプランナーとインデックスタイプ(BRIN、GIN、部分)
- 強力なコミュニティおよびツールエコシステム
### ネガティブ
- 書き込みの垂直スケーリングのみ(読み取りレプリカで緩和)
- スキーマ移行は大規模で慎重が必要
## 検討した代替案
| オプション | 利点 | 欠点 | 却下理由 |
|-------------|-----------------------------|----------------------------------|-------------------------------|
| MySQL 8 | 広くサポート | JSONサポート弱し、ANSI準拠度低い | チーム未習熟、機能少ない |
| MongoDB | 柔軟なスキーマ | マルチドキュメントACIDなし、ジョイン弱し | アクセスパターンはリレーショナル |
| CockroachDB | 分散SQL、地理的ローカリティ | 運用複雑性、コスト | 現在のスケールには未熟 |
技術選定マトリックス
SQLとNoSQL
| 基準 | SQL (PostgreSQL) | ドキュメント (MongoDB) | キーバリュー (Redis) | カラム (Cassandra) |
|---|---|---|---|---|
| ACIDトランザクション | フル | 制限 | なし | ライトウェイト |
| クエリの柔軟性 | 高い(ジョイン、集約) | 中程度 | 低い | 低い |
| スキーマ | 厳密 | 柔軟 | なし | 柔軟 |
| スケールアウト | 垂直 + 読み取りレプリカ | 水平 | 水平 | 水平 |
| 最適用途 | ほとんどのアプリ、金融データ | 柔軟なドキュメント | キャッシュ、セッション | 高書き込み、時系列 |
デフォルト: PostgreSQLから始めてください。PostgreSQLがスケールで処理できない具体的なアクセスパターンがある場合のみNoSQLに移動します。
同期と非同期の通信
| パターン | レイテンシ | カップリング | 最適用途 |
|---|---|---|---|
| REST/gRPC (同期) | 低い | 密結合 | リクエスト/レスポンス、クエリ |
| メッセージキュー (非同期) | より高い | 疎結合 | バックグラウンドジョブ、ファンアウト、リトライ |
| イベントストリーミング (Kafka) | 中程度 | 非常に疎結合 | 監査ログ、リアルタイム分析、イベントソーシング |
経験則: 呼び出し元が続行するために結果が必要な場合は同期を使用します。呼び出し元が作業が受け入れられたことを知る必要があるだけの場合は非同期を使用します。
モノリスとマイクロサービス
| 要因 | モノリス | マイクロサービス |
|---|---|---|
| チームサイズ | < 10エンジニア | > 10エンジニア、明確なドメイン所有権 |
| デプロイの複雑性 | 低い | 高い(オーケストレーション必須) |
| データ分離 | 共有DB(シンプル) | サービスあたり1DB(複雑) |
| スケーラビリティ | アプリ全体をスケール | 個別サービスをスケール |
| 開始は | 常に | モノリスが実際の問題点を持つ場合のみ |
デフォルト: モジュラーモノリスから始めてください。特定の境界コンテキストがスケーリングまたはデプロイのニーズが著しく異なる場合のみサービスを抽出します。
スケーラビリティパターン
水平スケーリング
サービスをステートレスにして、任意のインスタンスが任意のリクエストを処理できるようにします。
- セッション状態をRedisに保存し、インプロセスメモリには保存しない
- アップロードされたファイルをオブジェクトストレージ(S3)に保存し、ローカルファイルシステムには保存しない
- 設定を環境変数または設定サービスから取得する
- ロードバランサーでスティッキーセッションを使用しない(絶対に必要な場合を除く)
# ステートレスサービスチェックリスト
- インプロセスセッション状態なし
- ローカルファイルシステム依存なし
- べき等リクエスト処理(リトライ可能)
- 環境からの設定、ハードコードなし
読み取りレプリカ
読み取りが多いクエリをレプリカにルーティングして、プライマリの負荷を軽減します。
[アプリサーバー]
│
├──[書き込み]──► [プライマリDB]
│
└──[読み取り]───► [読み取りレプリカ 1]
[読み取りレプリカ 2]
- レプリケーションラグを許容: レプリカからの読み取りはわずかに古い可能性があります
- 書き込み直後の読み取りにはプライマリを使用(読み取り時自分の書き込み確認の一貫性)
- レプリケーションラグを監視 — SLA許容値を超えたらアラート
シャーディング
単一ノードが書き込みスループットまたはストレージを処理できない場合、複数のデータベースノード間でデータを分割します。
- ハッシュシャーディング: シャードキー(例:
user_id % N)にハッシュ関数を適用。均等な分布を提供。一貫性ハッシュを使用してリバランスコストを削減。 - レンジシャーディング: 順序付きキー(例:
created_atを月ごと)でパーティション。時系列スキャンに効率的。リスク: 現在の時間範囲でホットパーティション。 - ディレクトリシャーディング: ルックアップテーブルはキーをシャードにマップ。柔軟だが、ルックアップテーブルがボトルネックになります。
シャーディングの問題点、計画すべきこと:
- クロスシャードクエリはスキャッター・ギャザーが必要 — 費用がかかる
- シャード追加時のリバランスは運用が複雑
- ユニークID生成はシャード対応である必要があります(UUIDまたはSnowflake IDを使用)
CQRS(コマンド・クエリ・レスポンシビリティ・セグリゲーション)
書き込みモデル(コマンド)を読み取りモデル(クエリ)から分離します。
┌────────────────────────┐
書き込みパス │ │ 読み取りパス
│ │
[コマンド] ──► [書き込みモデル] ──► [イベントバス] ──► [プロジェクション] ──► [読み取りモデル]
(正規化, (非正規化,
ACID DB) クエリ最適化)
- 書き込みモデル: 正規化、強力な一貫性、ACIDトランザクション
- 読み取りモデル: 特定のクエリ形状に合わせた非正規化プロジェクション
- 書き込み側でのイベントソーシング: 現在の状態ではなく、イベント(事実)を保存。イベントをリプレイして状態を導出
- 使用場合: 読み取りと書き込みのアクセスパターンが根本的に異なるか、監査履歴が必要
キャッシングレイヤー
| レイヤー | テクノロジー | スコープ | 無効化 |
|---|---|---|---|
| L1 | インプロセスLRU(例: functools.lru_cache) | 単一プロセス | TTLまたは再起動 |
| L2 | 分散キャッシュ(Redis、Memcached) | すべてのアプリインスタンス | TTL、イベント駆動、ライトスルー |
| L3 | CDN(Cloudflare、CloudFront) | 公開コンテンツ、グローバルエッジ | Cache-Controlヘッダ、パージAPI |
キャッシュ無効化戦略:
- TTL(Time-To-Live): シンプル、古いデータを許容。参照データに良い。
- ライトスルー: 同時にキャッシュとDBに書き込み。キャッシュは決して古くなりませんが、書き込みレイテンシが追加されます。
- イベント駆動無効化: 書き込みでイベントを発行。コンシューマーはキャッシュエントリを無効化。複雑ですが正確。
- キャッシュアサイド(遅延ロード): キャッシュから読み取り。ミスの場合、DBから読み取りキャッシュを入力。最も一般的なパターン。
信頼性パターン
サーキットブレーカー
ダウンストリーム依存がデグラデーションまたは障害する場合のカスケード障害を防止します。
状態:
- Closed(正常): リクエストが流れる。障害がカウントされます。
- Open(障害): 閾値障害の後、サーキットはトリップ。リクエストは即座に拒否(高速フェール)され、ダウンストリームを呼び出しません。
- Half-Open(プロービング): クールダウン期間の後、少数のリクエストが許可されます。成功すればサーキットはクローズ。失敗すれば、再度オープン。
[アプリ] ──► [サーキットブレーカー] ──► [ダウンストリームサービス]
│
└── OPENの場合: フォールバックを即座に返却
ライブラリ:
- Python:
circuitbreaker - Node.js:
opossum
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- kid-sid
- ライセンス
- MIT
- 最終更新
- 2026/5/11
Source: https://github.com/kid-sid/claude-spellbook / ライセンス: MIT