mybook

見積もりの技術 — バックオブエンベロープ計算

「数字は自信を生む」

面接の前日、ソウタはカフェでノートを広げていた。

Webエンジニア歴4年。Railsでのバックエンド開発ならそこそこ自信がある。でも「10万DAUのシステムを設計してください」という問いに、自分が即答できる気がしなかった。DAUが10万だとして、それが何を意味するのか——QPSに換算するといくらになるのか、ストレージは何TBか——頭の中でまったく像を結ばない。

数字が出てこないと、設計の話に自信を持って踏み込めない。面接官の前で「えーっと……」と詰まる自分が見えた。

翌日のセッション、レイカはいつものようにコーヒーを飲んでいた。ソウタが「計算が苦手で……」と切り出すと、彼女は少し笑った。

「安心して。ここで言う計算は小学生レベル。掛け算と割り算しか使わない。でも、これができるかできないかで面接官の目が変わる。」

レイカはホワイトボードにこう書いた。

「設計のアイデアを持っている人」は多い。
「数字で設計を語れる人」は少ない。

「元AWSのソリューションアーキテクトとして、何百人もの候補者の面接をしてきた。数字のない設計提案は、地図のない旅行計画と同じ。どこへ向かっているかわからない。」

「でも、正確な数字を出すのは難しくないですか?」

「正確さは要らない。オーダー(桁)が合っていれば十分。1万と10万は違う。でも8,642と10,000は同じ。それがバックオブエンベロープ計算の本質。」

ソウタは少し気が楽になった。桁が合えばいい。それならできるかもしれない。


覚えるべき基本値

「まず頭に入れる数字がある。これは完全に暗記していい。」

時間の単位

# 時間換算(覚えておくべき数字)
1分   = 60秒
1時間 = 3,600秒  ≒ 4,000秒(丸めてOK)
1日   = 86,400秒 ≒ 10万秒(最重要!)
1週間 = 約60万秒
1ヶ月 = 約260万秒
1年   = 約3,000万秒(3 × 10^7

「1日が約10万秒というのが一番よく使う。QPS計算の基本だ。」

データサイズの単位

# データ量の単位
1 KB = 1,024 バイト ≒ 10^3 バイト
1 MB = 1,024 KB10^6 バイト
1 GB = 1,024 MB10^9 バイト
1 TB = 1,024 GB10^12 バイト
1 PB = 1,024 TB10^15 バイト
 
# データ型別サイズ(概算)
文字1文字         = 1〜2バイト(ASCII/UTF-8
整数(int)        = 4バイト
Long型           = 8バイト
UUID/GUID        = 36バイト(文字列)/ 16バイト(バイナリ)
日時(timestamp)  = 8バイト
ユーザーレコード   = 約1KB
Twitterツイート   = 約300バイト
画像(サムネイル) = 約300KB
フルサイズ画像    = 約3MB
動画(1分/1080p) = 約150MB

レイテンシの数字(重要)

「これも覚えておく。面接でキャッシュが有効かどうか判断するときに使う。」

操作レイテンシ
L1キャッシュ参照0.5 ns
L2キャッシュ参照7 ns
メモリ参照(RAM)100 ns
SSD ランダム読み取り150 µs
HDD ランダム読み取り10 ms
同じリージョン内 RTT0.5 ms
Redis 読み取り< 1 ms
RDS クエリ(シンプル)1〜10 ms
別リージョン間 RTT150 ms

INFO

「メモリはSSDより1000倍速い、SSDはHDDより100倍速い」という大まかな感覚を持つだけでよい。具体的な数字が活きるのは「なぜキャッシュが必要か」を説明するときや、「リアルタイム性の要件にHDDが使えない理由」を語るとき。面接での決め台詞: 「DBの応答は10ms、Redisは1ms以下なので、頻繁にアクセスするデータはElastiCacheに乗せます」。

可用性の数字

# SLA(Service Level Agreement)
可用性99%    → 年間ダウン時間 約88時間
可用性99.9%  → 年間ダウン時間 約9時間  ("スリーナイン"
可用性99.99% → 年間ダウン時間 約1時間  ("フォーナイン"
可用性99.999%→ 年間ダウン時間 約5分   ("ファイブナイン")

INFO

「フォーナイン(99.99%)」は多くのエンタープライズシステムの標準目標。これ以上を目指すと費用が指数関数的に増加する。面接では最初の要件確認で「何ナイン必要ですか」と聞き、その答えに応じてアーキテクチャの冗長性を設計すること。99.9%ならシングルAZでも許容できる場合がある。99.99%ならマルチAZ必須。


QPS(Query Per Second)の計算

「最もよく使う計算がQPS。1秒あたりのリクエスト数だ。」

基本の公式

QPS = 1日のリクエスト数 / 1日の秒数(86,400)

計算の簡略化:
1日のリクエスト数 ÷ 10万 ≒ QPS

練習問題1: Instagram

前提:
- DAU: 5億(500M)
- 1人あたり1日の操作: 投稿2回、閲覧100回

投稿QPS(書き込み):
500M × 2 / 86,400 ≒ 500M × 2 / 100,000
= 1,000M / 100,000
= 10,000 posts/sec

閲覧QPS(読み込み):
500M × 100 / 86,400 ≒ 500,000 req/sec

ピーク時(2〜3倍を想定):
書き込みピーク: 20,000〜30,000 posts/sec
読み込みピーク: 1,000,000〜1,500,000 req/sec

INFO

ピーク係数は一般的に2〜3倍を使う。ただし「地震速報」「人気アーティストのライブ告知」「大みそかの年越しカウントダウン」など特定イベントのスパイクは10〜20倍になることも。システムの性質によって係数を変える。Instagramのようなソーシャル系は夜8〜10時にピークが来ることが多い。

練習問題2: Slack

前提:
- DAU: 1,000万(10M)
- 1人あたり1日のメッセージ: 50件
- 1メッセージ: 約500バイト

メッセージQPS:
10M × 50 / 86,400 ≒ 500M / 100,000 = 5,000 messages/sec

ストレージ/日:
10M × 50 × 500B = 250GB/day

5年間のストレージ:
250GB × 365 × 5 ≒ 456TB ≒ 約0.5PB

練習問題3: TikTok

「TikTokでやってみましょうか」とソウタが言った。「最近よく聞く題材だし……」

「いいね。じゃあ自分で仮定を置いてみて。」

前提(ソウタの仮定):
- DAU: 10億(1B)
- 1人あたり1日の視聴動画数: 30本
- 1人あたり1日の投稿: 0.1本(10人に1人が投稿)
- 1本の動画(60秒/720p): 約100MB

投稿QPS(書き込み):
1B × 0.1 / 86,400 ≒ 100M / 100,000 = 1,000 uploads/sec

再生QPS(読み込み):
1B × 30 / 86,400 ≒ 300,000 plays/sec

1日のストレージ増加量(元データ):
1B × 0.1 × 100MB = 10PB/day

「前提と計算は正しい。ただ10PB/dayは元データの話。実際は複数解像度にエンコードするから3〜5倍になる。それと古い動画はCold Storage(Amazon S3 Glacier)に移して費用を下げる。このような補足ができると面接官の印象がさらに上がる。」

練習問題4: Uber Eats

前提:
- DAU: 5,000万(50M)
- 注文率: DAUの10% → 5M orders/day
- 注文1件あたりのAPIリクエスト: 20回
- ドライバー: 50万人、位置情報更新: 5秒に1回

注文関連QPS:
5M × 20 / 86,400 ≒ 100M / 100,000 = 1,000 req/sec

ドライバー位置情報QPS:
500,000 / 5 = 100,000 updates/sec

→ 位置情報の更新量が桁違いに多い
→ このシステムの設計難点は「位置情報の高頻度書き込み」

「この洞察が大事。QPSを計算したら、どこがボトルネックかを言えるようにする。Uber Eatsなら位置情報の書き込みが支配的。それがわかると、通常のRDBMSではなくRedisやAmazon DynamoDBで位置情報を管理する設計が見えてくる。」

練習問題5: Zoom

前提:
- 同時接続ミーティング: 100万(1M meetings)
- 1ミーティング参加者: 平均5人
- 映像ストリーム: 720p / 2Mbps/人
- 音声: 64Kbps/人

同時接続ユーザー: 1M × 5 = 5M users

帯域(映像):
5M × 2Mbps = 10,000,000 Mbps = 10Pbps

帯域(音声):
5M × 64Kbps = 320,000,000 Kbps = 320Tbps

WARNING

Zoomで見落としやすいのが「受信の非対称性」。5人のミーティングでは1人が他の4人分のストリームを受信する。サーバー中継モデル(SFU: Selective Forwarding Unit)の場合、サーバーが各参加者に全員分を転送するため、帯域は「人数 × ストリーム数」で膨らむ。P2Pモデルと大きく異なる点を面接で指摘できると差がつく。


ストレージの計算

練習問題6: YouTube

前提:
- 1日のアップロード動画: 500,000本
- 1本の動画(元データ): 1GB
- エンコード後の形式: 3種(360p / 720p / 1080p)
  - 360p:  300MB
  - 720p:  600MB
  - 1080p: 1GB
- 保持期間: 10年

1日のストレージ増加量:
元データ: 500,000 × 1GB  = 500TB
エンコード:
  360p:  500,000 × 300MB = 150TB
  720p:  500,000 × 600MB = 300TB
  1080p: 500,000 × 1GB   = 500TB
合計: 500 + 150 + 300 + 500 = 1,450TB ≒ 1.5PB/day

10年間の総ストレージ:
1.5PB × 365 × 10 ≒ 5,475PB ≒ 5.5EB(エクサバイト)

「この規模になると、単一のデータセンターに収まらない。だからGoogleは世界中のデータセンターで分散保存している。AWSでやるなら、S3を複数リージョンにCross-Region Replicationする設計になる。」


帯域幅の計算

「ストレージの次は帯域。1秒あたりのデータ転送量を考える。」

計算の基本

帯域(bps) = QPS × 平均レスポンスサイズ(バイト)× 8

※ バイト → ビット変換に × 8 が必要

練習問題7: Netflix

前提:
- 同時視聴ユーザー: 3,000万(30M)
- 1080p 動画: 5Mbps(メガビット/秒)
- 4K 動画:   25Mbps
- ユーザーの70%が1080p、30%が4K

必要な帯域幅:
1080p: 30M × 0.7 × 5Mbps  = 105,000,000 Mbps = 105Tbps
4K:    30M × 0.3 × 25Mbps = 225,000,000 Mbps = 225Tbps
合計: 330Tbps

→ Netflixは実際にこの規模のCDNを世界中で運用している

WARNING

帯域はビット(b)とバイト(B)を混同しやすい。1Mbps(メガビット/秒)= 0.125MB/s(メガバイト/秒)。ネットワーク帯域はビット、ストレージ容量はバイトで表すのが一般的。計算後に「この帯域はbpsで表現しています」と一言添えるだけで信頼感が上がる。単位を間違えると桁が8倍ずれる。


AWSでのキャパシティ計画

「数字を計算したら、次は『何台のサーバーが必要か』に落とし込む。これがキャパシティ計画だ。」

AWSの主なサービスとスループット目安

# EC2 インスタンス別スループット(Railsアプリ)
c5.large:
  CPU: 2 vCPU
  メモリ: 4GB
  ネットワーク: 最大 10Gbps
  Railsアプリ目安: 500〜1,000 req/sec
 
c5.4xlarge:
  CPU: 16 vCPU
  メモリ: 32GB
  ネットワーク: 最大 10Gbps
  Railsアプリ目安: 4,000〜8,000 req/sec
 
c5.18xlarge:
  CPU: 72 vCPU
  メモリ: 144GB
  ネットワーク: 最大 25Gbps
  Railsアプリ目安: 20,000〜40,000 req/sec
 
# RDS PostgreSQL(db.r6g.large)
RDS_r6g_large:
  接続数: 最大 1,000
  読み込みQPS: 約 10,000
  書き込みQPS: 約 5,000
  ストレージ: 最大 64TB
 
# ElastiCache Redis(cache.r6g.large)
ElastiCache_r6g_large:
  操作: 100,000+ ops/sec
  レイテンシ: < 1ms
  メモリ: 13.07GB
 
# CloudFront(CDN)
CloudFront:
  エッジロケーション: 400+
  スループット: 数百Tbps
  レイテンシ低減: オリジンの1/10以下

必要サーバー台数の計算

「InstagramレベルのQPS(読み込み50万req/sec)をAWSで処理するには何台必要か計算してみよう。」

目標QPS: 500,000 req/sec

1台あたり処理能力:
- c5.4xlarge の Rails アプリ: 5,000 req/sec(安全マージン込み)

必要台数(通常時):
500,000 / 5,000 = 100台

ピーク時(3倍):
500,000 × 3 / 5,000 = 300台

→ Auto Scaling で 100〜300 台の間で伸縮する構成

RDS 設計:
- 書き込み: 10,000 req/sec
  → RDS Writer 1台(db.r6g.2xlarge)で対応可能
- 読み込み: 490,000 req/sec
  → ElastiCache でキャッシュヒット率90%を目標にすると
     DBに到達するのは49,000 req/sec
  → Read Replica 5台で対応可能
Loading diagram...

RailsアプリでのQPS計測例

「実際のRailsアプリでどう計測・対処するか、コードで見せよう。」

現状のQPSをRedisで計測する

# config/initializers/request_counter.rb
# Redis を使って1分間のリクエスト数をカウントする
 
class RequestCounter
  REDIS = Redis.new(url: ENV.fetch("REDIS_URL"))
 
  def self.increment
    key = "qps:#{Time.current.strftime('%Y%m%d%H%M')}"
    REDIS.incr(key)
    REDIS.expire(key, 120) # 2分後に期限切れ
  end
 
  def self.current_qps
    key = "qps:#{1.minute.ago.strftime('%Y%m%d%H%M')}"
    count = REDIS.get(key).to_i
    count / 60.0 # 1分間のカウントを秒換算
  end
end
 
# application_controller.rb に追記
before_action { RequestCounter.increment }

キャッシュによるDB負荷軽減

# app/models/article.rb
class Article < ApplicationRecord
  # ランキングは ElastiCache (Redis) にキャッシュ
  # TTL: 5分(300秒)
  def self.popular_cached
    Rails.cache.fetch("articles:popular", expires_in: 5.minutes) do
      # このブロックはキャッシュミス時だけ実行される(DBアクセス)
      order(view_count: :desc).limit(20).to_a
    end
  end
 
  # キャッシュヒット率をログに記録してモニタリング
  def self.popular_with_metrics
    hit = true
    result = Rails.cache.fetch("articles:popular", expires_in: 5.minutes) do
      hit = false
      order(view_count: :desc).limit(20).to_a
    end
 
    Rails.logger.info(
      event: "cache_access",
      key: "articles:popular",
      hit: hit
    )
    result
  end
end

ストレージ使用量を概算するRakeタスク

# lib/tasks/capacity.rake
namespace :capacity do
  desc "現在のストレージ使用量と増加ペースを概算する"
  task estimate: :environment do
    week_ago    = 7.days.ago
 
    posts_week  = Post.where(created_at: week_ago..)
    daily_posts = posts_week.count / 7.0
 
    avg_size_bytes = 500 # 1投稿の平均サイズ(バイト)
 
    daily_storage_gb = (daily_posts * avg_size_bytes) / 1.0e9
    yearly_storage_tb = daily_storage_gb * 365 / 1000
 
    puts "=== ストレージ概算レポート ==="
    puts "直近7日間の日次投稿数: #{daily_posts.round} posts/day"
    puts "1日のストレージ増加: #{daily_storage_gb.round(2)} GB/day"
    puts "1年間の見込み増加量: #{yearly_storage_tb.round(2)} TB/year"
    puts "5年間の見込み増加量: #{(yearly_storage_tb * 5).round(2)} TB"
  end
end

Golangでの概算ツール実装例

「GoでCLIの概算ツールを作るとするとこうなる。面接の準備や設計レビューで使える。」

// capacity/estimator.go
package capacity
 
import "fmt"
 
// Estimate はシステムの概算値を計算して出力する
type Estimate struct {
	DAU            int64   // 日次アクティブユーザー数
	ActionsPerUser float64 // 1ユーザーあたりの日次アクション数
	DataSizeBytes  int64   // 1アクションあたりのデータサイズ(バイト)
	ReadWriteRatio float64 // 読み書き比(読み込みが何倍多いか)
	PeakMultiplier float64 // ピーク係数(通常の何倍か)
	RetentionYears int     // データ保持年数
}
 
func (e *Estimate) Calculate() {
	const secondsPerDay = 86_400
 
	// QPS 計算
	writeQPS     := float64(e.DAU) * e.ActionsPerUser / secondsPerDay
	readQPS      := writeQPS * e.ReadWriteRatio
	peakWriteQPS := writeQPS * e.PeakMultiplier
	peakReadQPS  := readQPS * e.PeakMultiplier
 
	// ストレージ計算
	dailyWriteCount := float64(e.DAU) * e.ActionsPerUser
	dailyStorageGB  := dailyWriteCount * float64(e.DataSizeBytes) / 1e9
	totalStorageTB  := dailyStorageGB * 365 * float64(e.RetentionYears) / 1000
 
	// 帯域計算(レスポンスサイズはリクエストの5倍と仮定)
	bandwidthMbps := readQPS * float64(e.DataSizeBytes) * 5 * 8 / 1e6
 
	fmt.Println("=== システム概算 ===")
	fmt.Printf("書き込みQPS(通常):   %.0f req/sec\n", writeQPS)
	fmt.Printf("読み込みQPS(通常):   %.0f req/sec\n", readQPS)
	fmt.Printf("書き込みQPS(ピーク): %.0f req/sec\n", peakWriteQPS)
	fmt.Printf("読み込みQPS(ピーク): %.0f req/sec\n", peakReadQPS)
	fmt.Printf("1日のストレージ増加:   %.2f GB/day\n", dailyStorageGB)
	fmt.Printf("%d年後の総ストレージ:  %.2f TB\n", e.RetentionYears, totalStorageTB)
	fmt.Printf("必要帯域幅:            %.0f Mbps\n", bandwidthMbps)
}
// main.go での使用例
package main
 
import "myapp/capacity"
 
func main() {
	e := &capacity.Estimate{
		DAU:            10_000_000, // 1000万 DAU(Slackのイメージ)
		ActionsPerUser: 50,         // 1日50メッセージ
		DataSizeBytes:  500,        // 1メッセージ500バイト
		ReadWriteRatio: 10,         // 読み書き比 10:1
		PeakMultiplier: 3,          // ピーク3倍
		RetentionYears: 5,          // 5年保持
	}
	e.Calculate()
	// 出力例:
	// 書き込みQPS(通常):   5787 req/sec
	// 読み込みQPS(通常):   57870 req/sec
	// 書き込みQPS(ピーク): 17361 req/sec
	// 読み込みQPS(ピーク): 173611 req/sec
	// 1日のストレージ増加:   250.00 GB/day
	// 5年後の総ストレージ:   456.25 TB
	// 必要帯域幅:            11574 Mbps
}

計算ミスしやすいポイントと対策

「面接中によくある計算ミスをまとめた。事前に知っておくだけで防げる。」

WARNING

よくある計算ミス TOP 5

  1. ビットとバイトの混同 — 帯域はbit (b)、ストレージはByte (B)。変換を忘れると8倍ずれる
  2. 1日の秒数の間違い — 86,400を8,640や100,000と間違えやすい。「10万秒」と丸めて概算し、誤差が気になるときだけ補正する
  3. ピーク係数の忘れ — 平均QPSでサーバー台数を決めるとピーク時に障害になる
  4. 読み書き比の見落とし — 「書き込みQPS × 読み書き比」で読み込みQPSを算出する。Twitterは読み書き比100:1以上
  5. 単位の累乗ミス — 1TBは10^12バイト。概算では1000GBと覚えておけば十分

計算テンプレート(面接直前チェックリスト)

# 面接で使う概算テンプレート(Rubyコメント形式)
 
# == ステップ1: 前提の確認 ==
# DAU: X M(要件から引き出す)
# 1人あたり日次アクション数: N 回
# 読み書き比: R : 1
# データサイズ/件: Y bytes
# ピーク係数: P 倍(通常2〜3)
# 保持期間: T 年
 
# == ステップ2: QPS ==
# 書き込みQPS  = DAU × N / 86_400
# 読み込みQPS  = 書き込みQPS × R
# ピークQPS    = 読み込みQPS × P
 
# == ステップ3: ストレージ ==
# 1日の増加量 = DAU × N × Y bytes
# T年後の総量 = 1日の増加量 × 365 × T
 
# == ステップ4: 帯域 ==
# 帯域(bps) = 読み込みQPS × 平均レスポンスサイズ × 8
 
# == ステップ5: サーバー台数 ==
# 必要台数(通常)= 通常QPS / 1台あたりのQPS
# ピーク台数      = ピークQPS / 1台あたりのQPS
# Auto Scaling 範囲 = 通常台数 〜 ピーク台数

ソウタの練習: Uber

「じゃあ練習してみよう。Uberを設計する場合のQPS概算をやってみて。」

ソウタは深呼吸した。以前なら「えーっと……」と詰まっていたが、今回は違う。ステップを頭の中でトレースしながら、ホワイトボードに書き始めた。

「DAUを3,000万とします。Uberには乗客とドライバーがいて、ドライバーは全体の20%と仮定します。」

ドライバー数: 3,000万 × 20% = 600万

位置情報QPS(ドライバーが4秒に1回送信):
6,000,000 / 4 = 1,500,000 = 1.5M writes/sec

乗客の位置確認QPS(乗客も4秒に1回確認):
2,400万(乗客)/ 4 = 6,000,000 = 6M reads/sec

ライドリクエストQPS:
- DAU 3,000万のうち30%が1日に平均2回リクエスト
- 3,000万 × 0.3 × 2 / 86,400 ≒ 208 req/sec ≒ 約200 req/sec

設計のポイント:
- 位置情報の書き込みが 1.5M req/sec と支配的
- RDBMSでは到底捌けない → Redis や DynamoDB が候補

「良くなってる」とレイカが言った。「前提を仮定して、声に出しながら計算できた。しかも読み書きを分けて、ボトルネックを特定した。これが正しいプロセス。数字は多少ずれてもいい。考え方が正しければ面接官は評価する。」

INFO

面接では「正確な数字」より「計算のプロセス」が評価される。仮定を明示して、その仮定に基づいて計算する姿勢が重要。「1日のアクティブ率を30%と仮定して……」と声に出しながら進めること。黙って計算して結果だけ出すのは最もNGなパターン。面接官はプロセスを見ている。


面接での計算の見せ方テクニック

「数字が出せるようになったら、次は『どう見せるか』を磨く。」

実況中継スタイルで計算する

悪い例:
(しばらく沈黙)「……QPSは5,000です」

良い例:
「DAUを1,000万と仮定します。
 1人が1日50件投稿するとすると、書き込みは1日5億件。
 1日は約10万秒なので、5億 ÷ 10万 = 5,000 writes/sec です。
 読み書き比を10:1とすると読み込みは50,000 req/sec、
 ピーク3倍で150,000 req/sec が上限の目安になります。」

計算後に設計への橋渡しをする

「書き込みQPS 5,000 に対して、
 RDS(PostgreSQL)の書き込み上限は約5,000 QPS。
 ギリギリなので、書き込みをSQSキューに入れて
 非同期処理する設計を検討します。
 これでピーク時のDBへの直撃を防げます。」

単位を毎回確認して声に出す

「この数字はGB/dayです。」
「ここでTBに変換します。1TB = 1,000GBなので……」
「最終的な帯域はMbpsで表現します。」

まとめ: 概算の三原則

原則1: 丸める
  86,400秒 → 10万秒(誤差17%は許容範囲)
  1,024MB  → 1,000MB(誤差2.4%は無視してよい)

原則2: 仮定を明示する
  「ピーク係数を3倍と仮定すると……」
  「読み書き比を10:1と仮定すると……」
  「DAUの20%がドライバーと仮定すると……」

原則3: 計算しながら話す
  頭の中で計算して結果だけ出すのはNG
  プロセスを声に出して面接官に見せる
  途中で間違えても「仮定が正しければ」という前提で評価される

次の章から、実際のシステム設計問題に取り組む。最初は「URL短縮サービス」——面接でよく出る定番問題だ。

「URL短縮は、サーバー設計の基礎が全部詰まった良問です」とレイカは言った。「シンプルに見えて、考えるべきことが山ほどある。今日練習したQPS計算が、さっそく使える。」

ソウタはノートの数字を見直した。桁が正しい。プロセスが正しい。数字への恐怖が、少しずつ設計への自信に変わっていくのを感じた。