| name | applying-isucon-arch-isunarabe |
| description | Applies the architecture redesign decided in docs/isucon-arch/DESIGN.md to the isunarabe-train-2026 (nrb2026) Go implementation in migrate/go/. Use when ready to execute the planned topology, schema, cache, and endpoint changes for this ISUCON practice problem. Assumes phase-0/1/2/3 of designing-isucon-architecture have already produced CONSTRAINTS.md, BASELINE.md, DESIGN.md, CONSTRAINT-MATRIX.md. |
Applying ISUCON Architecture: isunarabe-train-2026 (nrb2026)
docs/isucon-arch/DESIGN.md (改訂版 2026-05-09) の決定 — P0: GetCampaigns 1〜4 SQL 化 + MaxOpenConns 100 + 必要 index / P1: topology split / P2: 派生列化と endpoint 整理 / 撤回: 画像最適化 — を機械的に Go 実装 (migrate/go/) と ansible (ansible/) に適用する。
各 Phase は 検証ステップを passed させてから次へ進む。実態が DESIGN と矛盾したら止まり、docs/isucon-arch/DESIGN.md を直してから designing-isucon-architecture を再実行して本 skill を再生成する。現場で勝手に逸脱しない。
Pre-flight
適用順 (依存順、改訂版)
A. 計測 / 観測強化 (Grafana MCP / pprof / Pyroscope) ← 既存活用
↓
B. SCHEMA: index 追加 + 派生カラム化 + charges UNIQUE
↓
C. CACHE: tags + webhookURL warmup + /initialize 拡張 (派生列補正 + cache reset/warmup)
↓
D. ENDPOINTS:
#1 (P0) GET /api/campaigns ← 1〜4 SQL 化、最優先
#2 (P0) POST /api/campaigns/:id/join + GET /api/me ← credit_used 列化
#3 (P2) GET /api/campaigns/:id ← 派生列を使った hydrate
#4 (P3) GET /api/tags / POST /api/saved_searches / POST /api/campaigns ← cache 利用
↓
E. TOPOLOGY: ansible/hosts 振り直し → DSN を isu3 へ → DB remote-access → 多 app + nginx upstream
↓
F. DEPLOY: log rotation / isutools off (maji) / reboot test
撤回: 旧版にあった GET /api/campaigns/:id/image の静的化 + ETag 事前計算は **DESIGN.md §2「画像配信は据え置き」**で削除された。実測 count=4 / sum=0.002s でホットでないため。実走後に sum > 1s を観測したら 2 周目で再導入する Phase 4 ゲート方式。
A だけは「変えない」段階、B–F は順番が大事。A が終わるまで他の変更は入れない。
Phase A — 計測の確立 (1 commit)
目的: 改変ごとに pprof と Grafana MCP の PromQL で改善が確認できる土台を作る。
migrate/go/main.go の isutools 注入 (ansible 経由で deploy 時に isutools -fix . が走る) と :6060/debug/pprof は既設。Pyroscope に流し込む agent も monitor_local で動いている。
確認のみ:
ssh isu1 'curl -sS localhost:6060/debug/pprof/ | head -5'
ssh isu1 'sudo head -3 /var/log/nginx/access.log; sudo head -3 /var/log/mysql/slow-query.log'
make -C ansible bench
make -C ansible kataribe
make -C ansible slow
完了条件: BASELINE.md の実測表 (§1〜§5) が更新済。Grafana MCP のクエリで rate[15s] を使うこと (60s ベンチでは rate[2m] だとピークが平滑化される、memory feedback_isucon_grafana_window.md)。
Phase B — Schema (1 commit + /initialize 検証)
references/SCHEMA.md の DDL を migrate/go/sql/schema.sql に 追記する。POST /api/initialize が再現できることが要件。
実行:
make -C ansible bench
ssh isu1 'time curl -fsS -X POST -H "Content-Type: application/json" -d "{\"notification_webhook_url\":\"\"}" http://localhost:8080/api/initialize'
検証:
ssh isu3 'mysql -h 127.0.0.1 -uisucon -pisucon nrb2026 -e "EXPLAIN SELECT cp.user_id, u.name, cp.created_at FROM campaign_participants cp JOIN users u ON cp.user_id = u.id WHERE cp.campaign_id = \"100b73b1-c334-4231-89c2-0bca6ad9da55\" ORDER BY cp.created_at ASC"'
SCHEMA.md の "EXPLAIN expectations" 表で全クエリを通したら次へ。
Phase C — Cache (1 commit + reboot test 中継)
references/CACHE.md のとおり tagsByName, tagsAllSorted, webhookURL (atomic.Value[string]) を実装し、PostInitialize の末尾で 派生列補正 UPDATE 2 本 → cache reset → WarmCaches 再実行 を行う。warmup は main.go の e.Start の前に同期実行。
画像 / ETag cache は撤回。Caches 構造体に etagByCampaign も ImageDir も含めない。SetEtag/GetEtag/ResetEtags も無し。
検証:
ssh isu1 'sudo journalctl -u nrb2026-webapp.service --since "30 seconds ago" | grep -i warm'
curl -fsS -X POST -H "Content-Type: application/json" -d "{\"notification_webhook_url\":\"\"}" http://isu1/api/initialize
ssh isu1 'sudo journalctl -u nrb2026-webapp.service --since "5 seconds ago" | grep -i "warm\|reset"'
Phase D — Endpoints (3〜4 commits、各 commit ごとに make bench)
references/ENDPOINTS.md の順 (#1 GET /api/campaigns → #2 POST /api/campaigns/:id/join + GET /api/me → #3 GET /api/campaigns/:id → #4 cache consumers) で 1 トピック = 1 commit。各 commit 後に make bench でスコア比較。
GET /api/campaigns/:id/image は触らない。実測 count=4 / sum=0.002s で hot でない (DESIGN.md §4 #6)。Phase 4 ゲート (実走後に sum > 1s 観測) で 2 周目に再導入する。
ベンチ前の curl diff で JSON shape が前と同じ ことを必ず確認。
USER=f10a3eef-ff69-4cc5-a098-f225321d52e5
curl -sS -H "x-user-id: $USER" http://isu1/api/me | jq -S . > /tmp/me.after.json
diff /tmp/me.before.json /tmp/me.after.json
重要 invariant — Phase 4 で破ってはいけないもの (docs/isucon-arch/CONSTRAINTS.md §F):
- C1 二重課金、C2 通知重複、C3 goal 超過、C4 課金漏れ、C5/6/7 与信整合、C8 画像不一致。
- ロック順
users → campaigns → ... を保つ。
- error body 空、status code mapping、JSON snake_case + 宣言順、日時形式
2006-01-02T15:04:05.000Z、last_joined_at の null 表現。
Phase E — Topology (ansible 編集 + 1 deploy)
references/TOPOLOGY.md のとおり ansible/hosts を:
[app]
isu1
isu2
[mysql]
isu3
[nginx]
isu1
に書き換え、group_vars/all/var.yaml の mysql.connection.host を 192.168.0.13 (= isu3 private IP) へ。
make -C ansible server
ssh isu2 'systemctl is-active nrb2026-webapp.service'
ssh isu3 'systemctl is-active mysql.service'
ssh isu1 'curl -fsS http://localhost/healthz'
検証:
ssh isu1 'mysql -h 192.168.0.13 -uisucon -pisucon nrb2026 -e "SELECT COUNT(*) FROM campaigns"'
ssh isu2 'mysql -h 192.168.0.13 -uisucon -pisucon nrb2026 -e "SELECT COUNT(*) FROM campaigns"'
make -C ansible bench
ssh isu1 'sudo tail -50 /var/log/nginx/access.log | grep -c upstream'
Phase F — Deploy / log rotation / reboot test (最終)
references/DEPLOY.md のとおり:
- systemd unit の
Wants/After を network-online に。
make access-off / make slow-off で最終走行用に log を絞る (make maji のフローに含まれる)。
make maji を 1 回流して isutools オフで本走行スコアを取る。
sudo reboot × 3 → SSH 復帰を確認 → make bench。スコアが maji 直前と ±5% 以内なら合格。
reboot test を 必ず最後に 実施 (CONSTRAINT-MATRIX #8)。
Rollback
- 個別 commit は
git revert <sha> で戻せる粒度に保つ (Phase D は 1 トピック = 1 commit)。
- DDL は drop しないので前進方向のみ。
/initialize を 1 回叩けば campaigns.current_count 等は再計算される。
- ansible の topology 変更は
ansible/hosts を git revert で戻し make server。
Reference Files
- TOPOLOGY.md — 役割振り、env vars、nginx upstream、MySQL リモート access。
- SCHEMA.md — DDL delta、EXPLAIN 期待値、
/initialize 末尾の整合化 SQL。
- CACHE.md —
tagsByName / tagsAllSorted / webhookURL の Go コードスケッチ、warmup、reset。
- ENDPOINTS.md — エンドポイントごとの before/after コードスケッチ + curl 検証。
- DEPLOY.md — systemd unit、log rotation、isutools toggle、reboot test の最終手順。