エピローグ — 意思決定の積み重ねがアーキテクチャになる
1年後の朝
シンジはコーヒーを飲みながら、docs/adr/ ディレクトリを開いた。
$ ls docs/adr/*.md | wc -l
5858本。1年間で書いたADRだ。
最初は5本から始まった。遡及ADRを書いてチームの過去を掘り起こした。そして新しい判断のたびに、1本ずつ積み上げてきた。
$ git log --oneline docs/adr/ | head -20
a4f8c21 docs: ADR-058 gRPCによるサービス間通信の検討(見送り)
b9e3d18 docs: ADR-057 Datadogの導入とNewRelic契約終了
c2a1f09 docs: ADR-056 フィーチャーフラグにFlipperを採用
d7b3e2a docs: ADR-055 PostgreSQL 16へのバージョンアップ
e8c4f1b docs: ADR-054 モバイルAPIのレートリミット設計
f1a2b3c docs: ADR-053 S3マルチリージョン対応(見送り)
...1年前、「なぜそうなっているのか誰も知らない」という問題から始まった。今は違う。
今では誰でも言える。「なぜそうなっているかはADRに書いてある」と。
転機となった3つの瞬間
1年を振り返って、シンジはADRが「本当に機能した」と感じた3つの瞬間を思い出した。
瞬間1: 新メンバーの即戦力化
8ヶ月前、シニアエンジニアのナオが入社した。前職はGoエンジニア。Railsは初めてという状況だった。
入社初日、シンジはナオに言った。「まず docs/adr/README.md を読んで。次に001から010まで読んでみて。それからコードを読むと構造が分かりやすいはず」
3日後、ナオから連絡が来た。
「ADRを読んで、なぜこのアーキテクチャなのかがほぼ分かりました。特にADR-008のPostgreSQLへの移行理由と、ADR-015のECS継続判断が明確で、インフラの意思決定の背景が分かった。あとは実際のコードを読んで細部を確認するだけです。一つ聞いていいですか。ADR-031でSidekiqに移行した後のGoodJobのコードはどう除去しましたか?」
「PRを探してみて。ADR-031にPR番号が書いてある」
「あ、ありました。完璧に理解できました」
1週間でナオは最初のPRを出した。2週間目には本番コードに変更をマージした。通常3ヶ月かかっていたキャッチアップが6週間になった。
「ADRがオンボーディングの資料として機能している」とシンジは確信した。
瞬間2: インシデントの教訓が残る
6ヶ月前、本番でRedisの接続数が上限に達するインシデントが起きた(INC-042)。
原因は2つ。Redis Clusterへのコネクション管理の設定ミスと、特定のキャッシュキーが想定より10倍多く生成されていたことだ。
インシデント対応後、チームはpost-mortemを書くだけでなく、ADRを更新した。
# ADR-029 更新: キャッシュTTLポリシーとコネクション上限設定
# (2024年3月15日インシデントINC-042を受けた更新)
## Status
Accepted(インシデント対応で更新)
## Context(追記)
2024年3月10日のインシデント(INC-042)でRedisコネクション数が
上限(1000)に達し、本番障害が発生した(ダウンタイム: 約15分)。
インシデントの根本原因:
1. キャッシュキーの命名規則が不統一で、想定の10倍のキーが生成されていた
- 原因: ECSのWorkerタスク追加時にキャッシュキーにプロセスIDを含めてしまった
- キャッシュキー数: 想定5,000 → 実際50,000
2. コネクションプールの上限設定がADRで記録されていなかった
- Workerタスク追加時に既存のコネクション数計算を考慮できなかった
- Web x10 * pool5 + Worker x15 * pool5 = 125 → 上限1000以内のはずが
実際のPoolizeが設定値の2倍になっていた(設定ミス)
## Decision(追記)
以下のポリシーを追加する:
1. キャッシュキー命名規則を強制する
- 形式: `{model}:{scope}:v{version}:{params_hash}` (プロセスID禁止)
- バリデーション: Rspecのcustom matcherで自動チェック
2. コネクション数の計算式をADRで明記し、変更時はADRを更新する
- Webタスク: pool_size = 5
- Workerタスク: pool_size = 2
- Batchタスク: pool_size = 1
- 計算式: (Web数 * 5) + (Worker数 * 2) + (Batch数 * 1) <= 700(上限1000の70%)
3. コネクション数の上限をCI/CDで自動チェックする
## Consequences(追記)
インシデント対応として追加したテスト:
```ruby
# spec/system/redis_connection_spec.rb
describe "Redisコネクション数の制限" do
it "全タスクのpool_sizeの合計が上限の70%以下" do
# ECS環境変数から最大タスク数を取得(Terraformと連動)
max_web_tasks = ENV.fetch("MAX_WEB_TASKS", "20").to_i
max_worker_tasks = ENV.fetch("MAX_WORKER_TASKS", "10").to_i
max_batch_tasks = ENV.fetch("MAX_BATCH_TASKS", "5").to_i
web_connections = 5 * max_web_tasks
worker_connections = 2 * max_worker_tasks
batch_connections = 1 * max_batch_tasks
total = web_connections + worker_connections + batch_connections
expect(total).to be <= 700,
"コネクション数の合計(#{total})が上限(700)を超えています。" \
"ECSタスク数またはpool_sizeの見直しが必要です。ADR-029を参照。"
end
endインシデントの教訓がADRに刻まれることで、「同じ間違いを繰り返さない」文化が生まれた。
「このADRを見れば、なぜこの制約があるかが分かる」とリョウが言った。「INC-042という辛い経験が、将来のチームへの贈り物になった」
瞬間3: 技術的方針の変更が自然にできた
4ヶ月前、SidekiqからGoodJobに戻すという話が出た。
「え、逆戻り?」とユウキが聞いた。
「状況が変わったんだ」とリョウが答えた。「ADR-031を見て。Sidekiqに変えた理由は『ジョブ量が増えてRedisのボトルネックが出た場合』だった。でも実際はジョブ量が増えなかった。それよりPostgreSQLをすでに使っていてRedisが余分なインフラになってきた」
「それって、ADRの再評価のトリガーに書いてあること?」とユウキ。
「そう。ADR-031のConsequencesに書いてある: 『ジョブ数が1日10万件以下なら GoodJobで十分。その場合はRedisの維持コストを再評価する』」
ユウキはADR-031を開いた。確かに書いてある。
「感情論ゼロで議論できた。ADRが根拠になっているから」とシンジ。「『Sidekiqにしたのに戻すのか』という感情ではなく、『当時の判断条件が変わったから見直す』という論理で話せた」
決定はすぐにできた。ADR-031 を Superseded by ADR-055 にして、新しいADRを書くだけ。
# ADR-055: SidekiqからGoodJobに戻す
## Status
Accepted
## Context
ADR-031(2024年1月)でGoodJobからSidekiqに移行した。
移行理由: 「ジョブ量が1日10万件を超えた場合を想定してSidekiqの高スループットが必要」
現在の実績(2024年9月時点):
- 1日のジョブ数: 平均8,000件(想定10万件に達していない)
- Sidekiq/Redisのパフォーマンス問題: 発生なし
- Redisの月額コスト: $35(ElastiCache cache.t3.medium)
- ADR-031に書いた再評価トリガー: 「ジョブ数が1日10万件以下なら見直す」に該当
ADR-031のConsequencesに書いた条件が成立したため、見直しを実施。
## Decision
GoodJobに戻す。Redisコストを削減する。
ADR-031の選択肢で「GoodJobを継続しない理由」として挙げた
「ジョブ量増加時のRedisボトルネック」は発生しなかった。
現在の規模(8,000ジョブ/日)ではGoodJobで十分。
PostgreSQLをすでに使っているため、追加インフラが不要。
## Consequences
良い影響:
- Redis (ElastiCache)が不要になり月額$35のコスト削減
- インフラ構成がシンプルになる
- Sidekiqのスレッドモデルへの配慮(スレッドセーフ設計)が不要になる
悪い影響:
- 将来ジョブ量が増えた場合は再びSidekiqへ移行が必要
- GoodJobのConcurrencyに関する知識がチームから薄れている
(SidekiqのWorkerとは設定が違う)
再評価のトリガー:
- ジョブ数が1日5万件を超えた場合(今度はより早めの対応)
- GoodJobのバージョンアップがRailsと非互換になった場合ADRが表すもの
シンジは1年分のADRを眺めながら、気づいたことがあった。
ADRの積み重ねは、チームの「判断の歴史」だ。
個々のADRは技術判断の記録。でもそれが58本集まると、別の意味を持ち始める。
- 最初の10本は基礎的な技術選定だった(Rails、PostgreSQL、ECS)
- 次の10本はスケールへの対応(キャッシュ、インデックス、オートスケーリング)
- その次はセキュリティと信頼性(認証、インシデント教訓)
- そして今は、次の段階への準備(マイクロサービス、ML)
チームが「どんな問題に直面してきたか」「どんな価値観で技術を選んできたか」が、ADRに凝縮されている。
シンジはある日、ADRディレクトリを眺めながら思った。
「これは技術の歴史書だ。コードは現在を動かす。ADRは過去の判断を語る。そして未来の判断の根拠になる」
新しいメンバーへの手紙
ナオが入社から8ヶ月。今度は自分が「ADRとは何か」を新入りに説明する番になった。
チームにサーバーサイドエンジニアのタケシが加わった。
ナオはタケシに言った。
「まずdocs/adr/README.mdを読んで」
「このMarkdownファイル群が何?」
「アーキテクチャ意思決定記録。このチームが1年間でどんな技術的判断をしてきたか、なぜそうしたかが書いてある」
「コードより先に読むの?」
「そう。コードは『何をするか』を語る。ADRは『なぜそうするか』を語る。両方読んで初めて、このシステムが分かる」
タケシは半信半疑でADRを読み始めた。3日後、彼はシンジに言った。
「ADR-015、EKS移行を見送った理由が詳しく書いてあって、なぜ今ECSなのかが分かりました。コードを見ただけでは絶対分からなかった。あと、ADR-029にRedisのインシデントが書いてあって、コネクション数の制限にこんな背景があったのかと。勉強になりました」
シンジはうなずいた。インシデントの痛みが、未来への贈り物になった。
INFO
ADRは「現在のチームのため」だけでなく「未来のチームのため」に書く。1年後、3年後のメンバーが「なぜこうなっているのか」を知るための贈り物だ。書くコストは小さく、受け取る価値は大きい。
ADR文化の定着に必要なこと
1年間を振り返り、シンジは「ADR文化の定着に必要なもの」をまとめた。
1. 最初の10本が最も重要
文化は最初の10本で決まる。最初の10本が「良いADR」であれば、後から書く人はそれを手本にする。シニアエンジニアが率先して良いADRを書くことが重要だ。
「自分が書かずに『書いて』とお願いするだけでは文化は生まれない」とシンジは言った。「リーダーが率先して、良い例を示す。これが全て」
2. ハードルを上げすぎない
完璧なADRより、書かれたADRの方が価値がある。最初は5行でいい。議論を経て補完されていく。
「書いた後でADRを充実させることはできる。でも書かれなかったADRは存在しない」
3. コードレビューでADRに言及する
「このPRに関連するADR番号を教えてください」「このADRのConsequencesに書いてあることが起きましたね」という言及を習慣化することで、ADRが「参照されるドキュメント」になる。
「参照されないADRは死ぬ。参照されることで生き続ける」
4. ADRが役立った瞬間を共有する
「ADRがあってよかった」という瞬間をSlackで共有する。これが一番の文化醸成だ。
シンジのチームでは「#adr-wins」というSlackチャンネルを作った。そこでADRが役立った事例を共有する。
(実際の投稿)
リョウ @#adr-wins:
「今日、ナオから『ElastiCacheのクラスター設定を変えていい?』と聞かれて
ADR-024を見てもらったら全部解決した。
『あー、なるほど。INC-042があったから制約があるんですね』って。
ADRがなかったら30分の説明が必要だった。3分で終わった」
ユウキ @#adr-wins:
「ADR-012のlocalStorage禁止の件、フロントのPRレビューで
すぐ指摘できた。理由も説明できた。
『なぜ?』と聞かれてADRのURLを貼るだけでよかった」
5. 四半期レビューを続ける
定期的に「古いADRのアップデート」をすることで、ADRが「生きているドキュメント」であり続ける。
「メンテされないドキュメントは誰にも信用されなくなる。ADRも同じだ」
シンジの振り返り
1年後のスプリントレトロスペクティブで、シンジはチームに言った。
「1年前、入社したとき、コードベースを見て途方に暮れた。なぜそうなっているのか誰も知らなかった」
「今はどうか?」
マイが答えた。「少なくとも、知らないことは『知らない』と分かる。どのADRを読めばいいかが分かる。そしてほとんどのことはADRに書いてある」
リョウが続けた。「設計の議論が速くなった。以前は毎回ゼロから比較してたけど、今は『ADR-015でEKS見送ったけど状況変わった?』という議論ができる。スタート地点が違う」
コウスケが言った。「新しいメンバーへのオンボーディングが楽になった。ADRを読んでもらうだけで、チームの技術観が伝わる。説明する時間が半分以下になった」
ユウキが最後に言った。「自分も判断に参加できるようになった。ADRを読めば、先輩の判断の根拠が見える。同じ視点で考えられる。ジュニアだからって遠慮しなくていいと感じた」
シンジはうなずいた。
「なぜそうなっているのか誰も知らない」から「なぜそうなっているかはADRに書いてある」へ。
それは技術的な問題の解決だけでなく、チームのあり方の変化だった。
あなたのチームで始める ADR
この物語を読んで、「自分のチームでも始めたい」と思ったなら、今日から始められる。
ステップ1: 最初の1本を書く(今日)
今のプロジェクトで「なぜこうなっているのか分からない」ことを1つ選ぶ。知っている人に聞いて、ADRに書く。完璧でなくていい。
# ADR-001: [あなたのチームの最初の判断]
## Status
Accepted
## Context
[なぜこの判断が必要だったか]
## Decision
[何を決めたか]
## Consequences
[良い面と悪い面]ステップ2: PRにADRを含める(来週から)
次のPRに関連するADRを含める。新しい判断ならADRを書く。既存の判断なら参照する。
# ADRを管理するディレクトリを作る
mkdir -p docs/adr
# 最初のADRを書く
touch docs/adr/001-first-decision.md
# コミットする
git add docs/adr/
git commit -m "docs: ADR-001 プロジェクト最初のアーキテクチャ判断を記録"ステップ3: チームで共有する(来月)
「ADRがあってよかった」という瞬間を共有する。チームの文化は事例と共に育つ。
ステップ4: 四半期レビューを設ける(3ヶ月後)
「今のADRは正確か」「新しいADRが必要な判断はないか」を定期的に見直す。
コードは「何をするか」、ADRは「なぜそうするか」
設計判断の記録は、技術的負債への最も優れた予防策だ。
コードが「今を動かす」のであれば、ADRは「未来を動かせるようにする」。
今日の意思決定が、明日のアーキテクチャになる。
シンジのチームが証明したように、記録する文化があれば、チームは過去から学び、自信を持って未来を作れる。
「なぜそうなっているのか誰も知らない」という状態は、技術的な問題ではない。それは文化の問題だ。ADRはその文化を変える最もシンプルな方法だ。
最初の1本を書くのに、チームの合意も、特別なツールも、長いドキュメントも必要ない。
必要なのは、一人の誰かが「今日の判断を記録しよう」と決めることだけだ。
あなたのチームの最初のADRを書く準備はできましたか?
# 始めるのは今日
mkdir -p docs/adr
cat > docs/adr/001-first-decision.md << 'EOF'
# ADR-001: [最初の重要な技術判断]
## Status
Accepted
## Context
[なぜこの判断が必要だったか]
## Decision
[何を決めたか]
## Consequences
[良い面と悪い面]
EOF
git add docs/adr/001-first-decision.md
git commit -m "docs: ADR文化を始める第一歩"
echo "ADRの旅が始まりました"アーキテクチャは意思決定の積み重ねだ。その積み重ねを記録することから、すべてが始まる。
ADR文化の先にあるもの
シンジのチームがADRを始めて1年後、彼は別の変化にも気づいた。
ADRを書く文化は、技術的意思決定の質そのものを上げていた。
「ADRに書く」という前提があることで、意思決定のプロセスが変わった。
ADR前の意思決定:
1. 誰かが「こうしよう」と言う
2. 反論がなければそれに決まる
3. 理由は誰かの頭の中にしかない
ADR後の意思決定:
1. 「ADRを書くとしたら何を書く?」という問いから始まる
2. Context(問題の背景)を言語化する → 問題の本質が明確になる
3. 選択肢を列挙する → 見落としていた選択肢が見つかる
4. 各選択肢のトレードオフを考える → 判断が深くなる
5. Consequencesを考える → 実装前にリスクが見える
6. ADRとして記録する
「ADRを書くプロセス自体が、思考を整理するツールになっている」とリョウが言った。
「ADRを書いているとき、何回か『あ、この選択肢を考えていなかった』と気づいた。書くことで盲点が見える」
これはADRの予期しない副次効果だった。記録するためのツールが、考えるためのツールになっていた。
次の世代へ
シンジはある日、タケシ(最新の新メンバー)からこんな質問を受けた。
「ADRって、いつかなくなるものですか? 将来もっといいやり方が出てくるんじゃないかと思って」
シンジは少し考えた。
「ADRの形式は変わるかもしれない。MarkdownじゃなくてNotionになるかもしれないし、AIが自動で書くようになるかもしれない。でも根本にある問いは変わらない」
「根本の問いって?」
「『なぜそうするか』を記録する。それだけだ。ツールが変わっても、その問いは変わらない。人間がソフトウェアを書き続ける限り、この問いは永遠に重要だ」
タケシはうなずいた。
「分かりました。じゃあ僕が将来テックリードになったとき、ADRじゃない何かで『なぜそうするか』を記録する仕組みを作れれば、それが正解ですね」
シンジは笑った。
「その通り。そして、どんな仕組みにしたか、ADRで記録しておいてくれ」
付録: ADRクイックリファレンス
ADRテンプレート(最小版)
# ADR-XXX: [タイトル]
## Status
Accepted
## Context
[問題の背景と制約。具体的な数字を含める]
## Decision
[何を決めたか。1文で明確に]
選ばなかった理由:
- [選択肢A]: [理由]
- [選択肢B]: [理由]
## Consequences
良い影響:
- [具体的なメリット]
悪い影響:
- [具体的なデメリット・リスク]
再評価のトリガー:
- [この判断を見直す条件]よく書くADRの種類(Rails + AWSプロジェクト)
| 判断の種類 | ADRが必要な判断例 |
|---|---|
| データベース | DB選定, インデックス戦略, マイグレーション方針 |
| 認証・認可 | 認証方式, セッション管理, ロール設計 |
| 非同期処理 | バックグラウンドジョブ, キュー選定 |
| キャッシュ | キャッシュ戦略, TTL設定, 整合性保証 |
| インフラ | AWS サービス選定, オートスケーリング |
| 外部サービス | 決済, メール, ストレージ, 監視ツール |
| API設計 | バージョニング, フォーマット, 認証方法 |
| パフォーマンス | N+1対策, クエリ最適化の方針 |
ADRのステータス早見表
| ステータス | 意味 | 次のアクション |
|---|---|---|
| Proposed | 議論中 | チームでレビュー・フィードバック |
| Accepted | 決定済み | 実装に進む |
| Rejected | 却下 | 理由を残して保存 |
| Deprecated | 非推奨 | 代替案を示す |
| Superseded by ADR-XXX | 新ADRに置換 | 新ADRへのリンクを追加 |