プロローグ — 嬉しい悲鳴
運命の月曜日
午前10時17分。
アキラのSlackに通知が爆発した。
「Buzzのサーバーが落ちてます!」「ページが開きません!」「エラー500が大量発生中!」「ユーザーから問い合わせが殺到しています!」「投資家から電話が来てます!」
SNSアプリ「Buzz」のCTOであるアキラは、コーヒーを手に持ったまま固まった。昨夜まで快調に動いていたシステムが、今朝から応答を返せなくなっていた。デスクにいたエンジニアのユイが青い顔でやってきた。「アキラさん、全部落ちてます」
原因はすぐにわかった——嬉しい悲鳴だった。
有名インフルエンサーの「りか」が「Buzzが面白い!フォロワーみんな登録して!」とポストしたのだ。りかのフォロワーは300万人。それが拡散し、通常の100倍のトラフィックが押し寄せた。登録ユーザーは昨日の1,000人から一夜にして50,000人に膨れ上がり、アプリは悲鳴を上げて倒れた。
INFO
スタートアップにとって「スケーリング問題」は成功の証拠でもある。しかし、それを解決できなければチャンスは二度と戻らない。「サーバーが落ちた」という報告は最悪のニュースでもあり、最高のニュースでもある。
現場の惨状
モニタリングダッシュボードを開くと、惨状が広がっていた。
[ERROR] PG::ConnectionBad: could not connect to server
[ERROR] ActiveRecord::StatementInvalid: PGError: ERROR: too many connections
[ERROR] Sidekiq workers: 0/25 running (all crashed)
[WARN] Response time: 45,000ms (target: < 200ms)
[ALERT] CPU: 99% | Memory: 98% | DB connections: 100/100
[FATAL] Puma worker 0: Out of memory. Killed.
[FATAL] Puma worker 1: Out of memory. Killed.
Railsアプリは単一のt3.mediumインスタンスで動いていた。PostgreSQLも同じサーバー上。Sidekiqのワーカーが接続を食い尽くし、新規リクエストはすべてエラーになっていた。RailsとPostgreSQLとSidekiqが同居し、3者がリソースを奪い合っていた。
# 当時のPuma設定(問題あり)
# config/puma.rb
workers 4 # ワーカー4本
threads 10, 10 # スレッド最大10本
# 合計: 4 × 10 = 40スレッド = 40 DB接続
# DB接続上限: 100
# Sidekiq: 25接続
# 残り: 35接続(これが全リクエストで奪い合い)アキラは深呼吸した。パニックになっても何も解決しない。まず計測だ。
Buzzの現状把握
アキラはチームを集めてホワイトボードに現状を書き出した。
システム構成(現在)
- Webサーバー: Rails 7 on t3.medium(1台)— 2 vCPU / 4GB RAM
- DB: PostgreSQL 15 on 同一サーバー
- キャッシュ: なし
- CDN: なし
- バックグラウンドジョブ: Sidekiq(同一サーバー)
- デプロイ: Capistrano(手動)
- 監視: なし(!)
ユーザー数の推移
| 時期 | ユーザー数 | 状態 |
|---|---|---|
| 3ヶ月前 | 100人 | 快適 |
| 1ヶ月前 | 1,000人 | 少し遅い |
| 昨日 | 1,000人 | 普通 |
| 今朝 | 50,000人 | 完全停止 |
「このシステム、100人向けに作ったんだよな」とアキラは苦笑いした。「正確には、ユーザーが増えることを全く考えずに作った。今まで動いていたのが奇跡だ」
「でも動いていたのは本当の話です」とユイが言った。「1,000ユーザーまでは問題なかった。問題は50倍に一気に跳ね上がったことです」
スケーリングとは何か
チームのシニアエンジニア、ユイが口を開いた。
「スケーリングには大きく2種類あります。垂直スケーリングと水平スケーリングです」
垂直スケーリング(スケールアップ)
- サーバーのスペックを上げる(CPU・メモリ増強)
- 簡単だが、上限がある(最大でも数十倍)
- ダウンタイムが発生することも
- コストが急激に上がる(線形より高い)
水平スケーリング(スケールアウト)
- サーバーの台数を増やす
- 理論上は無限にスケール可能
- アーキテクチャの変更が必要
- コストは台数に比例(線形)
「でも、どれを先にやるべきかわからない」とアキラは言った。「闇雲に高いサーバーに変えても意味がないかもしれない」
「そうです」とユイが答えた。「だからまず計測するんです。ボトルネックがCPUなのかメモリなのかDBなのかによって、対処法が全然違います」
スケーリングの旅のロードマップ
アキラは今後の方針を整理した。スタートアップが陥りがちな罠は「全部一度に解決しようとすること」だ。段階的に、問題が起きたものから対処していく。
「いきなり100万ユーザー対応を目指すんじゃない」アキラは言った。「まず1万人、次に10万人、そして100万人——段階的に育てていく。各ステップで計測して、本当に必要なものだけを追加する」
WARNING
早すぎる最適化は諸悪の根源(premature optimization is the root of all evil)。計測なしに最適化を始めると、ボトルネックではない部分に時間を浪費する。100ユーザーのアプリにシャーディングを入れる必要はない。
今日の目標:まず生き返らせる
まず、今すぐシステムを復旧させなければならない。アキラは短期対策を決めた。
即時対応(今日中)
- インスタンスタイプをt3.medium → t3.xlargeに変更(スケールアップ)
- DB接続プール数を適切に設定
- Pumaのワーカー数とスレッド数を最適化
# 緊急対応: インスタンスタイプを一時的に変更
# AWS コンソールから t3.medium → t3.xlarge に変更
# t3.xlarge: 4 vCPU / 16GB RAM(t3.mediumの4倍)
# DB接続の計算式:
# (Pumaワーカー数 × スレッド数) + Sidekiqスレッド数 + 余裕
# = (2 × 5) + 10 + 5 = 25接続(上限100に対して適切)# config/database.yml
production:
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
connect_timeout: 10
checkout_timeout: 10
# 接続タイムアウトを設定(枯渇時に無限待ちを防ぐ)# config/puma.rb(修正版)
# ワーカー数 = CPUコア数(t3.xlarge: 4コア)
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# スレッド数 = DBコネクションプール数と一致させる
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
preload_app! # メモリ節約のためにpreloadを有効化
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
on_worker_boot do
# ワーカー起動時にDB接続を再確立
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end# config/sidekiq.yml(接続数を制限)
:concurrency: 10 # 25 → 10に削減(DB接続を節約)
:queues:
- [critical, 6]
- [default, 3]
- [low, 1]30分後、Buzzは復旧した。しかしアキラは知っていた。これは応急処置に過ぎない。次のトラフィックスパイクが来れば、また倒れる。
緊急対応の振り返り
「今日起きたことを整理しよう」アキラはチームに言った。
根本原因の分析:
問題1: リソース競合
Rails + PostgreSQL + Sidekiq が同一サーバー上で動いていた
→ CPU・メモリ・DB接続を3者が奪い合っていた
問題2: 接続プール設定ミス
Puma: 40スレッド × 2ワーカー = 80接続
Sidekiq: 25接続
合計: 105接続(上限100をオーバー!)
問題3: 水平スケール不可能な設計
セッションをサーバーメモリに保存
→ サーバーが複数になったらセッションが共有できない
問題4: 監視なし
ユーザーから報告されて初めて障害を知った
→ アラートがなければ障害を事前に検知できない
INFO
今日の「緊急対応」はあくまで延命措置だ。根本的な解決には、計測→ボトルネック特定→段階的なアーキテクチャ改善が必要。次週から本格的なスケーリング作業を始める。
監視の第一歩:New Relicを入れる
障害対応しながら、アキラはひとつ決断した。「まず何より、見えるようにする」
# Gemfile
gem 'newrelic_rpm'
gem 'rack-mini-profiler', require: false # 開発環境プロファイリング# config/newrelic.yml
production:
license_key: <%= ENV['NEW_RELIC_LICENSE_KEY'] %>
app_name: "Buzz Production"
transaction_tracer:
enabled: true
transaction_threshold: auto
record_sql: obfuscated # SQLをログ記録(マスキングあり)
stack_trace_threshold: 0.500
error_collector:
enabled: true
browser_monitoring:
auto_instrument: true# AWS CloudWatch でサーバーメトリクスを監視
# EC2 インスタンスに CloudWatch Agent をインストール
sudo yum install -y amazon-cloudwatch-agent
# 設定ファイル
cat > /opt/aws/amazon-cloudwatch-agent/bin/config.json << 'EOF'
{
"metrics": {
"metrics_collected": {
"mem": { "measurement": ["mem_used_percent"] },
"disk": { "measurement": ["disk_used_percent"] }
}
}
}
EOFスケーリングの哲学
アキラは手帳にメモを書いた。この1日で学んだことを整理するために。
スケーリングは技術ではなく、設計の哲学だ。
ユーザーが増えることを前提に設計する。 しかし、ユーザーがいないうちから複雑にしない。 計測し、ボトルネックを見つけ、最小限の変更で最大の効果を出す。
ユイが付け加えた。「スケーリングで大事なのはもう一つある。チームが理解できるシステムにすること。誰も理解できないシステムは、障害が起きたときに誰も直せない」
「それは今日、痛感した」アキラは言った。「システムが落ちたとき、最初の15分は何が起きているのかさえわからなかった。モニタリングがなかったから」
今日学んだ教訓
- 計測なしの開発は危険
- リソースの分離(Rails / DB / Sidekiqを別サーバーへ)
- ステートレス設計(水平スケールを可能にする)
- 監視・アラートは最初から入れる
「さあ」とアキラはチームに向かって言った。「本当のスケーリングの旅を始めよう。最初にやることは——計測だ」
技術的負債の棚卸し
その夜、アキラはひとりでコードを眺めた。スケーリング以前に、改善すべき点がいくつも目に入ってきた。
# 発見した問題 ①: N+1クエリ(あちこちに)
# app/controllers/timeline_controller.rb
def index
@users = User.limit(50) # 1クエリ
# ビューで user.posts を呼ぶたびに SELECT が走る = N+1
end
# 発見した問題 ②: インデックスがない
# posts テーブルに user_id のインデックスがない
# 200万行を全件スキャンしている
# 発見した問題 ③: セッションをメモリ保存
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store
# 複数台構成にしたらセッションが共有できない
# 発見した問題 ④: 画像処理を同期で実行
def create
@post = Post.create!(post_params)
ImageResizeService.call(@post) # 1.2秒!ユーザーが待つ
redirect_to @post
end「これだけの問題を一度に直そうとしてはいけない」アキラは自分に言い聞かせた。「計測して、最もインパクトの大きいものから直す」
次章では、アキラがBuzzのパフォーマンスを計測し、真のボトルネックを見つける方法を探っていく。N+1クエリ、スロークエリ、メモリリーク——問題は必ず数字の中に隠れている。
付録: Buzzの技術スタック
フロントエンド:
- Rails 7 + Hotwire/Turbo
- Tailwind CSS
- ActionCable(WebSocket)
バックエンド:
- Ruby 3.3
- Rails 7.1
- Puma(Webサーバー)
- Sidekiq(バックグラウンドジョブ)
データストア:
- PostgreSQL 15(メインDB)
※ Redis なし、キャッシュなし
インフラ:
- AWS EC2 t3.medium(1台)
- Capistrano デプロイ
今後追加予定:
- AWS ECS(コンテナ化)
- Amazon Aurora(マネージドDB)
- Amazon ElastiCache(Redis)
- Amazon S3 + CloudFront(CDN)
- GitHub Actions(CI/CD)
この構成からスタートして、100万ユーザーに耐えるシステムへと育てていく。それがアキラとユイの旅の始まりだ。