2年目エンジニアの試行錯誤 / LT

Complexity
Budget?

設計のとき、自分は何を考えれば
いいんだろう?…という話です。
— はじめに

SREには Error Budget があるらしい。
……じゃあ設計のときは?

SREで聞いたやつ
Error Budget
  • SLO 99.9% なら、0.1% は "壊れてもいい" 余白
  • 信頼性を予算として扱うらしい
  • 速度と安定のバランスを数字で見る
これ、設計にも応用できる…?
Complexity
Budget
  • たぶん複雑性も無限には増やせない
  • 「この複雑性、今払う必要ある?」と問う
  • シンプル vs 強力 を意識できそう
— きっかけ

チームでこういう議論になりました

管理画面で、対象顧客にキャンペーンを一括付与したい。
対象は最大で数千人。 …これ、どう実装するのがいいんだろう?
案A
ActiveJob
非同期 / バックグラウンド
vs
案B
Batch loop
同期 / 200件ずつ
先輩たちで意見が分かれていて、自分はどう判断したらいいのか分からなかった…
— 調べてみた

判断に使えそうな 14 個の観点を集めてみました

これが正解、というわけではなくて、「今回はどれを重く見るか」を考える材料として、自分なりに本やブログから集めたものです。
01
Cognitive Load
認知負荷
02
Accidental Complexity
偶発的複雑性
03
YAGNI
今要らないものは作らない
04
Operational Cost
運用コスト
05
Observability
可観測性
06
Idempotency
冪等性
07
Coupling
結合度
08
Changeability
変更容易性
09
Time to Market
市場投入速度
10
Team Skill Fit
チーム適合性
11
Failure Isolation
障害分離
12
Consistency Req.
整合性要求
13
Scalability Horizon
限界が来るタイミング
14
Reversibility
可逆性
— 01 / 14

Cognitive Load

認知負荷
問い
その仕組み、人間が理解できる範囲に収まってる?
「新メンバーに 30 分で説明できるか?」で考えてみる
  • 処理の流れを口頭でなぞって、何分くらいかかりそう?
  • 登場人物(worker / queue / Redis…)は何種類?
  • 本筋を理解するのに、他の概念をいくつ前提にしてる?
「説明に出てくる固有名詞の数」がそのまま複雑性の重さになるのかも、と思いました。
REFERENCES
Cognitive Load TheoryJohn Sweller, 1988
Team TopologiesSkelton & Pais, 2019
A Philosophy of Software DesignJohn Ousterhout, 2018
— 02 / 14

Accidental Complexity

偶発的複雑性
問い
それって問題の本質?それとも"道具"が連れてきた複雑性?
「この機能を消すとき、何を一緒に消すことになる?」と考えてみる
  • app コード 1 ファイル → これは本質寄りだと思う
  • + Sidekiq の worker 定義
  • + Redis の queue 設定
  • + retry / dead letter の監視 dashboard
消すときに一緒に剥がすものが多いほど、"道具"が連れてきた複雑性が混ざってるんじゃないか、と感じました。
REFERENCES
No Silver BulletFred Brooks, 1986
The Mythical Man-MonthFred Brooks, 1975
Out of the Tar PitMoseley & Marks, 2006
— 03 / 14

YAGNI

今要らないものは作らない / You Aren't Gonna Need It
問い
"将来必要そう"だけで、今、複雑性を払ってないかな?
"将来必要"って言葉、自分で問い直してみる
  • "将来スケールする" → いつ?何件で?誰が見積もった?
  • "将来分割する" → 分割候補のドメイン境界は今ある?
  • "将来 worker 増やす" → 直近半年の負荷予測は?
自分自身、つい「念のため」で先回りしがちなので、要件なのか願望なのか、ちゃんと分けて考えたいなと思いました。
REFERENCES
Extreme Programming ExplainedKent Beck, 1999
Yagni (bliki)Martin Fowler, 2015
The Pragmatic ProgrammerHunt & Thomas, 1999
— 04 / 14

Operational Cost

運用コスト
問い
これ、誰が運用する?壊れた夜中、誰が直すんだろう?
「土曜の深夜 2 時に障害が出たら?」を想像してみる
  • 誰が一次対応する?その人は仕組みを知ってる?
  • 復旧 runbook は何ページくらい必要そう?
  • 「対象 1 人だけ再実行」がワンコマンドでできる?
作る時のコストだけじゃなくて、動かし続けるコスト・直すコストまで含めて考えないと、たぶん痛い目を見るんだろうな…と感じました。
REFERENCES
Site Reliability EngineeringBeyer et al. (Google), 2016
The SRE WorkbookBeyer et al. (Google), 2018
AccelerateForsgren, Humble, Kim, 2018
— 05 / 14

Observability

可観測性
問い
障害が起きたとき、その経路を自分で追える?
「ユーザー A だけクーポンが届かなかった」と言われたら…と想像してみる
同期 :  log を user_id で grep → 終わり

非同期 :
  どの request だった?
   ↓
  どの job だった?(job_id は紐付いてる?)
   ↓
  どの worker が拾った?
   ↓
  retry されてる? dead letter?
問い合わせから原因特定まで何ステップ踏むかで、運用の難易度がだいぶ変わりそうだなと思いました。
REFERENCES
Observability EngineeringMajors, Fong-Jones, Miranda, 2022
Distributed Systems ObservabilityCindy Sridharan, 2018
Dapper (Distributed Tracing)Sigelman et al. (Google), 2010
— 06 / 14

Idempotency

冪等性
問い
同じ処理が複数回走っても、結果は安全?
「同じ処理が 2 回走ったら何が起きる?」を想像してみる
  • クーポンが 2 枚配られる → 事故…
  • メール通知が 2 通飛ぶ → 信頼を失いそう
  • 外部 API への課金 call が 2 回 → 金銭被害
非同期にした瞬間、「job_id で重複排除する」みたいな責任がコード側に増えるのか…と気づきました。その実装とテストのコストも、見えない予算として払うことになりそうです。
REFERENCES
Designing Data-Intensive ApplicationsMartin Kleppmann, 2017
Enterprise Integration PatternsHohpe & Woolf, 2003
Exactly-Once Semantics (Kafka)Confluent, 2017
— 07 / 14

Coupling

結合度
問い
変更したとき、どこまで影響が波及する?
「クーポン付与のロジックを少し変えたい」と言われたら…
  • 同期処理 → controller か service 1 ファイルで済みそう
  • 非同期 → controller + job + worker + retry + テスト…
  • event chain → 誰が subscribe してるか grep するところから
「1 つの仕様変更で触るファイル数」って結合度のものさしになりそうだなと。「疎結合のために」って言って非同期化したのに、暗黙の前提で逆に強結合してた、みたいな話も本で読みました。
REFERENCES
Structured DesignYourdon & Constantine, 1979
Domain-Driven DesignEric Evans, 2003
Clean ArchitectureRobert C. Martin, 2017
— 08 / 14

Changeability

変更容易性
問い
半年後、これを変更できるかな?
「次に似た機能を作るとき、何ファイル変える?」で考えてみる
  • "ポイント一括付与"を追加するなら、何ファイル新規作成?
  • 同じ抽象を使い回せそう?それとも丸ごとコピペ?
  • 抽象化しすぎて、1 箇所変えるのに 5 ファイル開いてないかな?
未来の自分が grep で辿れるか、で考えると分かりやすそう。抽象化って便利だけど、やりすぎると逆に読めなくなるんだなと、最近自分のコード見て思いました…。
REFERENCES
RefactoringMartin Fowler, 1999 / 2018
Building Evolutionary ArchitecturesFord, Parsons, Kua, 2017
A Philosophy of Software DesignJohn Ousterhout, 2018
— 09 / 14

Time to Market

市場投入速度
問い
今、一番価値があるのは"完成度"?それとも"速度"?
「今、何日遅れると、ビジネス的に何を失う?」を聞いてみる
  • 木曜のキャンペーン開始に間に合わない → 機会損失◯円
  • 非同期化を入れるなら +3 日、それは見合うかな?
  • 後で非同期化に差し替えるのは現実的?
「綺麗な設計のために遅れる」のも、「雑なまま出す」のもどっちもリスクで、自分はつい設計に時間をかけすぎがちなので気をつけたいなと思いました。
REFERENCES
The Lean StartupEric Ries, 2011
Continuous DeliveryHumble & Farley, 2010
Worse Is BetterRichard Gabriel, 1991
— 10 / 14

Team Skill Fit

チーム適合性
問い
うちのチームが、理解・保守・拡張できる選択?
「PRレビューで詰まる箇所」が、チームの上限を教えてくれそう
  • そのライブラリ、レビューできる人は何人いる?
  • 新人がオンボーディングで詰まりそうなのはどこ?
  • その人が休んだとき、誰が代わる?
「技術的に正しい」と「うちで運用できる」が同じとは限らないんだな、と最近思うようになりました。自分しか分からない設計を入れちゃうのも怖い…。
REFERENCES
Team TopologiesSkelton & Pais, 2019
Conway's LawMelvin Conway, 1968
Choose Boring TechnologyDan McKinley, 2015
— 11 / 14

Failure Isolation

障害分離
問い
壊れたとき、被害をどこで止められそう?
「2,000 人中 1,500 人目で失敗した」を想像してみる
  • 巨大 transaction → 全員 rollback、最初からやり直し…
  • 200 件 chunk → 1,400 人分は確定、残りからリトライできる
  • 非同期 job → 失敗した job だけ手動 retry
壊れたあとの復旧手順まで含めて考えると、「全部 or 0」より「ちょっとずつ確定」のほうが事故時に楽そうだなと思いました。
REFERENCES
Release ItMichael Nygard, 2007 / 2018
Bulkhead PatternCloud Design Patterns
Designing Data-Intensive ApplicationsMartin Kleppmann, 2017
— 12 / 14

Consistency Requirement

整合性要求
問い
本当に"強整合性"が必要?結果整合で済まないかな?
「30 秒くらい状態がズレてたら、何が困る?」を考えてみる
  • 「1,500 人だけ付与済み」の状態が見える → 困らなさそう
  • 付与されてない人がアクセスする → 翌日でも OK
  • 金銭・在庫・予約の atomic 性 → 今回は関係なさそう
「強整合じゃないと困る」って、技術要件じゃなくて業務要件の話なんだなと整理して気づきました。今回は「最終的に全員に届けばよい」で充分そうです。
REFERENCES
Designing Data-Intensive ApplicationsMartin Kleppmann, 2017
CAP TheoremEric Brewer, 2000
PACELCDaniel Abadi, 2010
— 13 / 14

Scalability Horizon

いつ限界が来るか
問い
その規模、"今"本当に問題になってる?
"今の規模"を、まず数字で書き出してみる
  • 1 件あたりの処理時間は? 例: 50ms くらい?
  • 対象件数は? 例: 2,000 件 → 計 100 秒
  • 10 倍 (20,000 件 / 16 分) になるのは何ヶ月後?
"いつ壊れるか"を数字にしてみると、"今いくら払うか"も見えてくる気がしました。10 倍までは耐える設計なら、今は払わない選択肢もありかも、と。
REFERENCES
Structured Programming with go toDonald Knuth, 1974
Numbers Everyone Should KnowJeff Dean (Google), 2009
Choose Boring TechnologyDan McKinley, 2015
— 14 / 14

Reversibility

可逆性
問い
あとから「やっぱりやめた」ってできる選択?
「やっぱりやめます」のコストを想像してみる
  • 同期 → 非同期化 : あとからでも足せる(戻せる扉)
  • 非同期 → 同期化 : 呼び出し元全部が job 前提で書き換わってる…
  • Microservice 化 : DB 分割まで進むと、戻すのは数ヶ月仕事
「戻せる扉」は軽く決めていいけど、「戻せない扉」は慎重に。迷ったら、まず戻せる方を試してみる、というのが自分には合いそうだなと思いました。
REFERENCES
One-way / Two-way DoorsJeff Bezos, 2015 Letter
Architecture Decision RecordsMichael Nygard, 2011
The Pragmatic ProgrammerHunt & Thomas, 1999
— 皆さんに聞いてみたい

…で、皆さんならどうしますか?

— おさらい
管理画面で キャンペーンを一括付与、対象は最大数千人。
ActiveJob で非同期化する? それとも 200件ずつのバッチループで素直に回す?
案A
ActiveJob
こう考えてる人が多そう
  • Failure Isolation — UI から切り離せる
  • Scalability Horizon — 数万件への移行コストが小さい
  • Operational Cost — 既存の retry / 監視を流用できる
  • UX — ブラウザを閉じても継続する
案B
Batch loop
こう考えてる人が多そう
  • Cognitive Load — 上から下に読めば終わる
  • YAGNI — 数千件なら queue は今は要らないかも
  • Accidental Complexity — Redis / worker を巻き込まない
  • Time to Market — 今週リリースに間に合う
皆さんなら、どの観点を一番重く見ますか?
「対象 10 万人」なら、判断はどう変わる
過去に「払いすぎた複雑性」の事例ってありますか?
— 自分なりのチェックリスト

判断を 数字 にしてみたい

Error Budget みたいに、数字で測れたら相談しやすそうだなと思って、自分なりに 2 つの数字 を出すチェックリストを作ってみました。
(思いつきなので、もっと良い指標があれば教えてください!)
COST AXIS
Complexity Cost
「複雑にすると、どれくらい高くつく?」
登場人物が3種類以上増える?worker, queue, Redis…+15
新メンバーへの説明が30分で終わらない?+15
削除するとき5ファイル以上消す?+15
運用 runbook を新規に書く必要がある?+15
冪等性の実装責任がコード側に乗る?+10
1仕様変更で触るファイルが3つ以上?+10
レビューできる人がチームに2人以下?+10
後から戻すのが難しい選択?+10
MAX100点
RISK AXIS
Risk if Simple
「シンプルで行ったら、何がマズい?」
失敗の影響が金銭・SLAに直結する?+25
処理時間が同期では絶対に間に合わない?UIブロック1分超など+20
複数チームから呼ばれる共通基盤?+15
リアルタイム性が業務要件として強い?+15
1年で5〜10倍に伸びる予測がある?+10
失敗時の手動リカバリが現実的でない規模?+10
既にシンプル構成で事故が出ている?+5
MAX100点
— 2軸で判断する

2 つの数字で 象限 を見る

Cost / Risk の 合計点 ≥ 50 を "高" として、4 象限に分けてみました。
「最初からGoしたほうがいいケース」も、これでちゃんと拾えるはず…!
Risk if Simple →
Risk 高 × Cost 低
即 Go complex
最初から非同期化や分散化で投資する。
例: 決済バッチ、SLA直結処理
Risk 高 × Cost 高
慎重に Go complex
複雑性は必要。ただし設計レビュー必須。
段階的に進める。
Risk 低 × Cost 低
迷わず Stay simple
シンプルでGO。後から非同期化に拡張する余地を残す。
Risk 低 × Cost 高
YAGNI、削ろう
複雑性に見合うリスク回避がない。
"念のため"を疑う。
→ Complexity Cost
— 今回のケースに当てはめてみる
キャンペーン一括付与 (数千人)
COST
75/100
RISK
15/100
Risk 低 × Cost 高 → YAGNI、削ろう
ActiveJob 導入の Cost に見合うほどの Risk が、今のところ見えない。
— 自分なりの線引き

どこまでいったら
「やろう」って判断する?

「複雑性予算を払う / 払わない」って、感覚で決めると不安なので、シグナルが揃ったかどうかで考えてみようと思いました。
(あくまで自分の整理なので、皆さんの基準を教えてほしいです)
— GREEN
Stay simple.
シンプルなまま行く
全部 YES なら同期 / バッチで OK そう
  • 処理時間が許容範囲(数秒〜数十秒)
  • 失敗時の再実行が手動でも回せる規模
  • 強整合性が業務要件にない
  • 呼び出し元は 1 箇所だけ
  • 10 倍の規模でもまだ耐えられる
バッチループで素直に。
複雑性予算は 払わない
— AMBER
Consider it.
そろそろ検討してもいいかも
2 つ以上 YES なら非同期化を相談
  • UI ブロック時間が体感で重い(10 秒超)
  • 呼び出し元が複数に増えてきた
  • 失敗の再実行を運用が面倒だと言い始めた
  • 外部 API 依存でタイムアウトが頻発
  • 1 年で 5〜10 倍に伸びる予測がある
→ まずは 軽量な非同期化から。
戻せる範囲で予算を試し打ち。
— RED
Go complex.
払う価値がありそう
どれか YES なら本気で投資する場面かも
  • 同期では絶対に間に合わない処理時間
  • 失敗の影響が金銭 / SLA に直結する
  • 複数チームから呼ばれる共通基盤になっている
  • リアルタイム性が業務要件として強い
  • 運用で回す限界を超えた事故が出た
Job queue / event driven を正式に導入。
観測・冪等・運用ぜんぶ予算化。
自分ルール: 迷ったら、まず戻せる方から試す。シンプル → 非同期化は足し算でいけるけど、非同期 → シンプル化は引き算で済まないことが多そうなので…。
— 今のところの自分の結論
設計って、
「正しい技術を選ぶ」ことより、

どの複雑性に予算を使うか
決めることなのかも…?

…という話でした。ご意見ください!
1 / 21