エピローグ — システム設計の思考法
ハルトの旅の終わり(と始まり)
2年が経った。
EchoTaskは月間アクティブユーザー100万人を突破した。1台のVPSから始まったシステムは、複数のAWSリージョンをまたぐマイクロサービス群へと変貌を遂げた。
ハルトは午後の会議室でコーヒーを飲んでいた。隣に座ったのは、入社3ヶ月の新人エンジニア、ミサキだ。
「先輩……来月、転職面接を受けようと思っているんです。システム設計の面接が怖くて。」
ミサキは小さな声で打ち明けた。ハルトはカップを置いて、静かに微笑んだ。
「それは——EchoTaskで僕が学んだことと同じだよ。一緒にやってみよう。」
設計面接ロールプレイ
「Twitterライクなシステムを設計してください」
ハルトが面接官役を引き受けた。
ハルト(面接官): 「Twitterのようなシステムを設計してください。時間は45分です。」
ミサキ(応答者): 「まず要件を確認させてください。ツイートの投稿・閲覧・フォロー機能が核となる機能でしょうか?」
ハルト: 「そうです。タイムライン、フォロー/フォロワー関係も含みます。」
ミサキ: 「ユーザー規模はどれくらいを想定しますか?」
ハルト: 「月間アクティブユーザー1億人で考えてください。」
ミサキ: 「分かりました。では規模を推定します。DAU(日次アクティブ)を1割と仮定すると1000万人。1人あたり1日20ツイート読む、3ツイート書くとすると……」
# ミサキの皮算用
monthly_users = 100_000_000
dau = monthly_users * 0.1 # 1000万人
tweet_reads_per_day = dau * 20 # 2億リクエスト
tweet_writes_per_day = dau * 3 # 3000万リクエスト
read_qps = tweet_reads_per_day / 86400.0 # 約2315 QPS
write_qps = tweet_writes_per_day / 86400.0 # 約347 QPS
# ピーク(平均の3倍)
peak_read_qps = (read_qps * 3).ceil # 約6946 QPS
peak_write_qps = (write_qps * 3).ceil # 約1042 QPS
puts "Read: avg #{read_qps.round} QPS / peak #{peak_read_qps} QPS"
puts "Write: avg #{write_qps.round} QPS / peak #{peak_write_qps} QPS"ミサキ: 「読み取りがピークで約7000 QPS、書き込みは約1000 QPSです。読み取り優位なシステムですね。」
ハルト: 「よいですね。では概略設計を描いてみてください。」
ミサキ: 「まずシンプルに3層で考えます。クライアント → ロードバランサー → アプリサーバー → DB。タイムラインは読み取りが多いのでキャッシュを分離します。」
ハルト: 「データベーススキーマはどう設計しますか?」
ミサキ: 「まず主要な3テーブルです。」
# Railsのmigrationで表現
create_table :users do |t|
t.string :handle, null: false # @username
t.string :display_name, null: false
t.integer :followers_count, default: 0
t.integer :following_count, default: 0
t.timestamps
end
create_table :tweets do |t|
t.references :user, null: false, foreign_key: true
t.text :content, null: false # 280文字以内
t.integer :likes_count, default: 0
t.integer :retweet_count, default: 0
t.timestamps
end
create_table :follows do |t|
t.references :follower, null: false, foreign_key: { to_table: :users }
t.references :followee, null: false, foreign_key: { to_table: :users }
t.timestamps
t.index [:follower_id, :followee_id], unique: true
endハルト: 「タイムラインはどう実装しますか?フォロワー1000万人のセレブユーザーのツイートをどう配信しますか?」
ミサキ: 「……難しいですね。2つのアプローチがあると思います。Pull型はユーザーがタイムラインを開くたびにフォロイーのツイートをDBから取得する。でも1000万フォロワーが同時にログインしたら?」
ハルト: 「1000万クエリが同時に発行されますね。」
ミサキ: 「なのでPush型も併用します。ツイート投稿時にフォロワー全員のタイムラインキャッシュ(RedisのList)に書き込む。ただしセレブユーザーはフォロワーが多すぎるので、セレブのツイートはPullのまま残してマージします。ハイブリッド方式です。」
ハルト: 「いいですね。それはFanout on Write + Fanout on Read のハイブリッドです。APIはどう設計しますか?」
ミサキ: 「RESTで3つのエンドポイントを基本に。」
POST /api/v1/tweets # ツイート投稿
GET /api/v1/timelines/home # ホームタイムライン取得
POST /api/v1/users/:id/follows # フォロー
DELETE /api/v1/users/:id/follows # フォロー解除
ハルト: 「最後に、このシステムのボトルネックと改善案を話してください。」
ミサキ: 「タイムラインの書き込み増幅です。フォロワー100万人のユーザーが1ツイートすると100万件のキャッシュ書き込みが発生します。非同期キューで処理し、優先度を下げてバックグラウンドで実行します。あとはホットスポット——人気ツイートの読み取り集中は、CDNとインメモリキャッシュの多段構成で対処します。」
ハルト: 「——合格ですよ、ミサキさん。」
INFO
面接のコツ
- 最初の5分は必ず要件確認。「何人のユーザー?読み書きの比率は?」を聞く
- シンプルな設計から始め、面接官の誘導に乗って深掘りする
- デメリットを自分から言うことで「トレードオフを理解している」と示せる
- 計算は桁が合えばよい。細かい正確さより思考プロセスを見せる
全章の振り返り
| 章 | テーマ | 学んだこと | EchoTaskでの適用例 |
|---|---|---|---|
| 1章 | プロローグ | 1台から始めることの価値 | VPS単体でMVPをリリース |
| 2章 | スケーリング | 垂直→水平スケールの判断基準 | ECSタスク数のAuto Scaling設定 |
| 3章 | ロードバランシング | ALBとヘルスチェックの仕組み | Blue/Greenデプロイの安全な切り替え |
| 4章 | DB設計 | インデックス・クエリ最適化 | タスク一覧のN+1問題を解消 |
| 5章 | DBスケーリング | Auroraリードレプリカ・シャーディング | 読み取りを3台のレプリカに分散 |
| 6章 | キャッシュ | Read-Through / Write-Through / TTL | ElastiCacheでAPIレイテンシを80%削減 |
| 7章 | CDN | エッジキャッシュとオリジン保護 | CloudFrontで静的アセットを全世界配信 |
| 8章 | 非同期処理 | Sidekiq + SQSのキュー設計 | メール通知をバックグラウンドに分離 |
| 9章 | マイクロサービス | 分割の判断基準とサービスメッシュ | 通知・課金・検索サービスを独立化 |
| 10章 | API Gateway | JWT認証・レートリミット・ルーティング | 全サービスへの統一エントリーポイント |
| 11章 | データ一貫性 | Sagaパターン・OutboxパターンとAT LEAST ONCE | タスク作成と通知の分散トランザクション |
| 12章 | 可用性 | Multi-AZ・Circuit Breaker・カオスエンジニアリング | AZ障害でもゼロダウンタイムを実現 |
| 13章 | オブザーバビリティ | ログ・メトリクス・トレーシングの三本柱 | X-Rayでマイクロサービスのボトルネック特定 |
| 14章 | セキュリティ | IAM最小権限・WAF・暗号化 | SQLインジェクション・SSRF攻撃をWAFで遮断 |
エンジニアとしてのキャリアパス
「ミサキ、システム設計を学んだ先に何があると思う?」
ハルトはホワイトボードに図を描いた。
「バックエンドエンジニアとしてシステム設計を学ぶと、インフラの判断ができるようになる。インフラを学ぶとSREの仕事が見えてくる。この本で学んだことは、そのすべての土台だよ。」
バックエンドエンジニア(1〜3年目)
- APIの設計・実装、DBスキーマ設計
- キャッシュ戦略、クエリ最適化
- 非同期処理・キューの実装
インフラエンジニア(3〜5年目)
- AWS/GCPのアーキテクチャ設計
- CI/CDパイプライン構築、Terraform によるIaC
- コスト最適化・セキュリティ強化
SRE / Platform Engineer(5年目〜)
- SLO/SLA設計、エラーバジェット管理
- カオスエンジニアリング・ゲームデー運営
- 開発チームの生産性向上(内部プラットフォーム設計)
Golang移行の判断基準
「先輩、うちのサービスってずっとRailsで行くんですか?」
「良い質問だ。」ハルトはRailsとGolangを比較した表を書いた。
| 判断基準 | Railsのまま | Golangへ移行 |
|---|---|---|
| チームのGo経験 | Goを知らない | 複数人がGoを書ける |
| 開発速度 | 機能開発が最優先 | パフォーマンスが最優先 |
| スループット要件 | ピーク500 QPS以下 | ピーク5000 QPS以上 |
| メモリ使用量 | 許容範囲内 | OOMでECSタスクが落ちる |
| コンパイル型の恩恵 | 不要 | 型安全・バイナリ配布が必要 |
| 移行コスト | 高い(書き直し) | ペイする(長期的に) |
// Goへ移行した場合の通知サービス例
// Railsのsidekiqワーカーをそのまま置き換えられる
package main
import (
"context"
"encoding/json"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
type NotifyJob struct {
UserID int64 `json:"user_id"`
Message string `json:"message"`
Channel string `json:"channel"` // "email" | "push"
}
func processMessage(ctx context.Context, body string) error {
var job NotifyJob
if err := json.Unmarshal([]byte(body), &job); err != nil {
return err
}
// チャネル別に通知ロジックを分岐
switch job.Channel {
case "email":
return sendEmail(ctx, job)
case "push":
return sendPush(ctx, job)
}
return nil
}
func main() {
// SQSからメッセージをポーリング
// Railsの Sidekiq と同じ役割だがメモリは1/10
log.Println("notification worker started")
_ = os.Getenv("SQS_QUEUE_URL")
// ... SQSポーリングループ
}「EchoTaskでは通知サービスだけGoに移行した。メモリ使用量が240MBから28MBに下がって、ECSタスク数を半分にできた。」
WARNING
Goへの移行は慎重に Railsの生産性(CoC、ActiveRecord、豊富なgem)を捨てるコストは大きい。まず「ボトルネックを計測」し、Goへの書き直しがペイする部分だけを移行するストラングラーパターンを使うこと。全体を一気に書き直すのはリスクが高い。
推薦リソース
「本も教えてもらえますか?」とミサキが聞いた。
システム設計
- 「Designing Data-Intensive Applications」(Martin Kleppmann) — データシステムの理論的基盤。分散システムを深く理解したいなら必読
- 「System Design Interview」(Alex Xu) — 設計面接の定番。具体的な事例が豊富
- AWS Well-Architected Framework — 無料。AWS上の設計原則を網羅
データベース
- 「PostgreSQL: Up and Running」 — RDBの実践書。インデックス設計・チューニングに強くなれる
- 「NoSQL Distilled」(Martin Fowler) — NoSQLの使い分けを理論から理解する
セキュリティ
- OWASP Top 10 — Webアプリの代表的脆弱性。毎年読み返す価値がある
- 「The Web Application Hacker's Handbook」 — 攻撃者視点でセキュリティを学ぶ
ブログ・エンジニアリングブログ
- High Scalability — 実際の大規模システムのアーキテクチャ事例
- AWS Architecture Blog — AWSを使った設計パターンの宝庫
- Netflix Tech Blog / Slack Engineering — 先進的なシステム設計の事例
次に学ぶべきトピック
ハルトはリストをメモアプリに打ち込んで見せた。
[ ] Kubernetes (k8s)
- ECSの次のステップ。コンテナオーケストレーションの標準
- EKSでRailsアプリをデプロイし、HPA(水平Pod自動スケール)を体験する
[ ] Apache Kafka
- SQSより強力なイベントストリーミング基盤
- イベントソーシング・CDC(Change Data Capture)のユースケース
[ ] Terraform
- AWSリソースをコードで管理(IaC)
- 「コンソールをポチポチする」から卒業できる
[ ] Service Mesh(Istio / AWS App Mesh)
- マイクロサービス間の通信を透過的に管理
- mTLS・サーキットブレーカーをアプリコードなしに実現
[ ] eBPF / Cilium
- Linuxカーネルレベルのネットワーク・セキュリティ観測
- 次世代のオブザーバビリティ基盤として注目
[ ] OpenTelemetry
- ベンダーロックインしないトレーシング・メトリクス標準
- Datadog / New Relic / Jaegerへのエクスポートが統一APIで可能
「どれから始めればいいですか?」
「転職後の業務で使うもの、でいいよ。学習は「今必要なもの」から始めるのが一番続く。」
学習ロードマップの目安
Year 1: バックエンドの基礎を固める
→ Rails / Go で API を作り、PostgreSQL をちゃんと使う
→ Docker でローカル環境を整え、ECRへイメージをpush できる状態に
Year 2: インフラとCI/CDを自分で触る
→ Terraform でVPC・RDS・ECSを自分で構築する
→ GitHub Actions でテスト→ビルド→デプロイのパイプラインを引く
Year 3: 可観測性とセキュリティに投資する
→ OpenTelemetryでトレーシングを導入、SLO/エラーバジェットを定義する
→ AWSのセキュリティベストプラクティス(SCPs、PrivateLink、GuardDuty)を体得する
Year 4+: スケールと設計の複雑さに挑む
→ Kubernetes / Service Mesh / Kafka に手を出す
→ チームをリードするアーキテクト・SREのロールに挑戦する
EchoTaskの未来 — グローバル展開
「EchoTaskは海外展開しないんですか?」
「実は来年、北米と欧州に展開する計画がある。」
グローバル展開に向けて、ハルトはマルチリージョン構成を設計していた。
東京リージョン(ap-northeast-1) ← アジア・日本ユーザー
└ Aurora Global Cluster(Primary)
└ ElastiCache
└ ECS Cluster
バージニア北部(us-east-1) ← 北米ユーザー
└ Aurora Global Cluster(Secondary / Read)
└ ElastiCache
└ ECS Cluster
フランクフルト(eu-central-1) ← 欧州ユーザー(GDPR対応)
└ Aurora Global Cluster(Secondary / Read)
└ ElastiCache
└ ECS Cluster
Route 53 → Latency-Based Routing → 最寄りリージョンへ誘導
CloudFront → グローバルエッジ(130+箇所)
GDPRへの対応 欧州ユーザーのデータをEUリージョン内に留めるデータレジデンシーが必須になる。Aurora Global Clusterでは書き込みは東京Primary経由になってしまうため、欧州ユーザーのWrite操作はフランクフルトのPrimary Clusterを別途立て、アプリ層でユーザーの地域に応じてルーティングする設計が必要だ。
「グローバル展開って、コードより法律と運用が大変なんだよ。」ハルトは苦笑した。
トレードオフの哲学
「結局、設計で一番大事なことは何ですか?」
ミサキの問いに、ハルトはしばらく黙った。
「コンテキストを読むこと、だよ。」
「え?」
「同じ問題でも、スタートアップと大企業では答えが違う。1万人のユーザーと1億人では違う。チームが3人と100人でも違う。
完璧なシステムはない。あるのはそのコンテキストでのベストなトレードオフだけ。
面接でも、「この会社が今何を最優先にしているか」を読んで答えるのが本当に上手い人のやり方だよ。」
# 良いシステム設計の兆候
GOOD_DESIGN_SIGNALS = [
"新人が2時間で全体を理解できる",
"障害が起きたとき、どこで何が起きているか分かる",
"機能追加が1つのサービスだけに閉じている",
"スケールが必要な部分だけスケールできる",
"朝3時にPagerDutyが鳴らない",
].freeze
# 悪いシステム設計の兆候
BAD_DESIGN_SIGNALS = [
"誰もシステム全体を把握していない",
"変更が怖くて手が出せない",
"新機能に3つのサービスの変更が必要",
"全体をスケールしないと動かない",
"ドキュメントが常に古い",
].freezeINFO
「計測してから最適化する」— Rob Pike 推測するな、計測せよ。最適化の順序は常に「1. 計測(どこが遅いか?)→ 2. ボトルネック特定 → 3. 最も効果的な改善を1つ → 4. 再計測」。体感でチューニングしたシステムは、たいてい間違った場所を最適化している。
ミサキへの言葉
会議室の窓から夕日が差し込んでいた。
「先輩は、エンジニアになって後悔したことはありますか?」
ミサキが不意に聞いた。
ハルトは少し考えて、首を横に振った。
「EchoTaskが最初に落ちた夜、ユーザーから怒りのメールが100件来た。そのときは死にたかったよ。」
「……」
「でも、その夜に直した。翌日、ユーザーから『すぐ直してくれてありがとう』ってメールが来た。そのとき思った——システムは生き物だって。作って終わりじゃない。育てるものだって。」
ハルトは続けた。
「設計面接の答えを暗記しようとしないこと。大切なのは、なぜそれを選ぶのかを自分の言葉で説明できること。
EchoTaskを作りながら、僕は何度も失敗した。1台のサーバーがダウンして、スロークエリで苦しんで、通知が重複して、セキュリティインシデントが起きて。
でも、その失敗から学んだことが、今の判断基準になっている。
本で読んだ知識より、手を動かして失敗した経験の方が、ずっと深く刻まれる。
自分でプロダクトを作り、スケールの問題にぶつかり、解決策を試してみること——それが最高の勉強法だ。」
EchoTaskの現在
月間アクティブユーザー100万人。エンジニア30人のチーム。最初の1台のVPSから始まったシステムは、今や:
- ECSタスク: 80台以上(Auto Scaling)
- Auroraクラスター: 3サービスそれぞれに独立
- ElastiCacheクラスター: 2つ
- CloudFrontディストリビューション: 2つ
- SQSキュー: 12本
- マイクロサービス: 6つ
しかし、ハルトが最も誇りに思うのはスペックではない。
「朝3時にPagerDutyが鳴らなくなったこと」
それが、システム設計を学んだ意味だ。
「ありがとうございました、先輩。面接、頑張ってみます。」
ミサキは立ち上がってお辞儀をした。
「うちの面接を受けてみる気はない?」
ミサキは顔を上げた。ハルトはニヤリと笑っていた。
「……え?」
「EchoTaskに必要なのは、こういう質問をちゃんとする人間だよ。スペックじゃなくて、考える姿勢がある人。」
窓の外、夕日がオレンジに染まっていた。
EchoTaskの物語は、まだ続いている。
INFO
この章のキーポイント
- 設計面接は「要件確認 → 規模推定 → 概略設計 → データスキーマ → API設計 → ボトルネック議論」の順で進める
- 「なぜそれを選ぶのか」をトレードオフとともに自分の言葉で説明できることが最重要
- シンプルな設計から始め、必要になったときに複雑にする(YAGNI)
- 計測してから最適化する。推測でチューニングしない
- コンテキストを読む——スタートアップと大企業では正解が違う
- 失敗から学ぶことが最高の教科書になる