#yamagoya2020 で 2020/11/25 に登壇させていただいたセッションの資料です。
FastlyとTypeScriptで実現するカナリアリリースとA / BテストSho Miyamoto (日本経済新聞社)
View Slide
- 名前Sho Miyamoto- 所属日本経済新聞社- 領域- web- front-end- {client,server,edge}-side- GitHubhttps://github.com/shqld自己紹介2
⚠ 今日の発表では実装の詳細に(なるべく)深入りしないようにします3代わりに、発表後に公開されるこの資料をご覧いただけると嬉しいです
(ダーク)カナリアリリースをご存知でしょうか?4
カナリアリリース5- 一部のユーザだけに新機能を見せる- ランダムに絞ることが多い- 問題がなければ、その後100%のユーザに展開
- 開発者・社内のユーザなど、特定の条件に合うユーザだけに新機能を見せる- 本番環境での確認やQAを通した上でユーザへ展開ダークカナリアリリース6
- 絞った数のユーザだけで、画面やシステムへの影響度を見ることができる- 問題があったときに全体に波及させない- ダークカナリアリリースの場合、そもそもユーザには見えないのでより安全- 公開前の機能・ページを予め本番に出しておいて社内で確認、指定日時に自動リリース、ということも可能カナリアリリースの利点7
A / B テスト8- いわずとしれた分析手法
Feature Toggles で実現できる9
Feature Toggles10
Feature Toggles11https://laptrinhx.com/1-minute-feature-toggle-1686812105/- 機能のオンオフ状態を示す- イメージはスイッチボタン
- 実際にコード内で行っているのは分岐を作ることだけ- コードを書き換えるのではなく、分岐させる- 分岐先はランタイムで判定Feature Toggles12
Feature Toggles- ここに全てが記されている- Feature Toggles (aka Feature Flags) by Martin Fowler13
14ポイントリリースとデプロイのタイミングをずらす→ ユーザからの見え方や挙動をより柔軟にコントロールできる
- 機能リリースが安全になる- 一度本番で確認してから全体へリリース、ということができる- QAテストが容易になる- 他部署・非エンジニアでも、スイッチを切り替えるだけで検証できる15Feature Toggles の利点- 非常時の対応が素早い- 何かあればスイッチを切り戻すだけでデプロイせずに対応できる- 非機能要件の検証がしやすい- パフォーマンス施策の効果検証など
16問題があったときに、いかに素早く切り戻せるかも重要Toggle経由ならデプロイも待たずに即反映できる
電子版とFeature Toggles17
- 新しい機能やページは基本的にダークカナリアリリース- 実際に開発者が本番で確認してから全てのユーザに展開- 指定の日時に自動リリース- e.g. 大統領選の始まる時間に合わせて特設ページをリリース18Feature Toggles の用途- マーケティング施策のA/Bテストを行う- e.g. ペイウォール(有料登録動線)の文言変更- 技術的な施策の効果検証を行う- e.g. Dynamic Critical CSSを本番に反映しても問題ないか検証、確認が取れるまでは反映しない
- JSONで管理- defaultValue を決めておく- A / B テストを行うときはcandidates を定義実際の Toggle 定義19
- 基本的に boolean なので、ただの分岐として書くだけダークカナリアリリース20
- カナリアリリースのときと特に変わらない- この例ではコンポーネントごと出し分けている- これらのTogglesは全て計測システムに送っているので、この機能を見たユーザを判別できるA / B テスト21
22社内の非開発者でも簡単にオンオフできるUIもある
Feature Toggles の実装23
Default / Static- 既定値- Toggle の定義時に人間が決定- リリース用- 例- 「このToggleはもうリリースできるのでオン」Override / Dynamic- 上書き値- 定義された条件から機械的に決定- スケジュール用、セグメンテーション用- 例- 「11/25になったからオン」- 「このユーザのセグメントが10%内だからオン」24Toggle の2つの側面
25Toggle の2つの側面
26Feature Toggles の材料
基盤の構成27
- dynamic toggles の計算のために別のサーバを必要としている- 運用・ケアが必要- Toggles サーバにリクエストするために VCL内で restart している- restart - static toggles の同期のためにサービス側で5分おきにAPIへポーリングしている- メモリに暗黙的な状態が存在する28
- HTTPヘッダに Toggles を詰めてオリジンへリクエスト- キャッシュが混らないように`Vary`ヘッダを付けてレスポンスメッセージのやりとり29
旧基盤 (Nikkei-Flags) についての詳しい解説はFastlyの記事でHow to solve anything in VCL, part 3:authentication and feature flags at the edgehttps://www.fastly.com/blog/how-solve-anything-vcl-part-3-authentication-and-feature-flags-edge30
Feature Toggles システムのリプレース31
リプレースの要件・ゴール (1)32- インフラ- 状態や計算も全てVCLで完結- 完全サーバーレス- 転送量- 各リクエスト毎に送られるデータはなるべく小さく- できるものは全て静的に解決しておく- 複雑性- 暗黙的・非透過的な状態はなるべく排除- 「いま何がメモリにあって何がどうなっているのか」という懸念がない
- 運用性- 更新反映が速い- 終了した・不要な toggle の分岐がコードに残らない- Toggleの定義- 定義が簡潔- 個々の存在目的が明確リプレースの要件・ゴール (2)33
34まずToggleの概念を再考した
35https://martinfowler.com/articles/feature-toggles.html
- Toggleの種類は4つ- Release: (ダーク)カナリーリリース- Experiment: ダークローンチやA/Bテスト- Ops: 状況に応じてインフラなどを切り替える- Permission 権限別に処理をスイッチ- → 本当に必要なのは Release と Experiment- それ以外はどちらも Longevity(寿命)が長く消しづらい- Opsはユースケースが限定的 で頻度が少ない- Permissionはアプリケーションの一部として 依存されてしまう可能性が高いToggle 再考36
- 存在意義・目的が明確- ある機能をスイッチ(オン /オフ)するだけ- → 値は True / False の2値のみ- 1つのToggleに複数の用途を持たせない- 寿命が短く疎結合- 短い役目を終えたら すぐに消せる- コードから剥がしやすい- 存在が透過的- Toggleの存在を前提としない- Toggleにアプリケーションの 実装が依存しないあるべき理想のToggle37
38色々な議論・紆余曲折あったが...最終的にできたもの(設計に半年以上かかった)
39Feature Toggles の材料
新基盤の構成40
- エッジで dynamic toggles の計算を行う- サーバレス、VCLで完結- restart / polling も消えた- 最初から bereq にtoggles が入っている- 各bereqごとに CDN へリクエストして static toggles を取得- 常に最新の toggles を同期できる- 暗黙的な状態を持たない41
- 誰にでも書ける簡単・簡潔なインターフェース- 名前と概要、各タイプのオプションを記述- JSONとは異なり、オプションのチェックや補完が効くトグルの定義42
- Fastify の `req` オブジェクトにAPIを生やす- TypeScriptで実装- できるだけ型で縛れるように型推論多め- 返る値は必ずBooleanアプリケーションコード43(文字列の位置が下がっているのはVSCodeのプラグインの関係)
デプロイフロー- Toggles を別のリポジトリの切り出した- npmの`prepublishOnly`フックでデプロイが実行される- npm publishとVCLのdeployが終わったあとに Fastly API でソフトパージ- toggles 周りのVCLは VCLSnippets として作っており、本体のVCLとは別軸・別レポでデプロイが可能44
45VCL Snippets- メインとなるCustom VCLとは別サイクルで更新ができる- サブルーチン単位でデプロイ可能- https://docs.fastly.com/en/guides/using-dynamic-vcl-snippets
46実装上の各ポイント
✏ Toggles API の処理をVCLで実装47
- JSで定義されたToggleを読んで、dynamic toggles 用のVCLを生成している- Scheduling- リクエストされた時間と定義ルールの時間を比較してオンオフを決める- Segmentation- 何かしらのリクエストIDを使いユーザのセグメントを決定し定義ルールの範囲と比較してオンオフを決める48
- 上の定義から、下の dynamictoggles 用の条件分岐を生成している- セグメンテーションで同じユーザが複数の Toggle で似たような判定にならないように、条件範囲にrandom offset を含めている49
50✏ 転送量を抑えた
51Toggleの数や名前によって数が重くなる
52長いキーを4 bytesのハッシュに変換
53ハッシュをToggle定義とマッピング
54リクエストごとに発生するこの部分を減らしたい
55先頭にversionをつけ、toggle コンテンツのversioningをする
56versioningされていることで、static toggles のメモ化ができる
57✏ TypeScriptでToggle の取り扱いを安全に
58型で toggle の名前を補完
59消した toggle が使われていたら型エラー
60‘ xxx‘ で検索できる- 抽象化されているとtoggleがどこで使われているか追えなくなる- toggle のキーは必ず ‘ ‘で始まるように強制(それ以外を弾いている)- string literal タイプなのでキーを抽象化できない- ✖ get(‘ ‘ + name)
- toggle を生成する関数を経由- ここで name を string literal タイプで受け取る- string literal の name を返すToggle オブジェクトの型引数に渡して保持型の推論61
- 集めた toggles を Object.freezeで readonly に- typeof で型に変えた toggles のname を ユニオンとして取り出す型の推論 (2)62
まとめ
- (ダーク)カナリアリリースは便利- リリースの安全性が高まる- Feature Toggles で実現できることが多い- デプロイとリリースのタイミングをずらすことで色々できる- 電子版では Feature Toggles を(結構)活用している- ダークカナリアリリース、スケジューリング、 A/Bテスト、...まとめ64
まとめ (2)- Feature Toggles の運用を見返してシンプルに- 何でもできてしまう toggles は禁止、制約を強めに- VCLを駆使すれば 外部のサーバは必要ない- VCL Snippets でデプロイフローも簡潔に- TypeScript でアプリケーションコードを安全に- 補完や検索、エラー検知も容易に65
ありがとうございました66
余談:restart_and_you_will_be_fired
VCL restart; 68
- 人類にはまだ早い- 使わずに済むなら使わない方が良い- これを消したかった、というのも今回のリプレースの動機の大きな一つ- 中々解決できない問題は大体 restart 起因であることが多い(個人の経験上)- 様々なパターンを網羅的に知っておかないと思わぬ事故に繋がってしまう- 一度目のリクエストで( restartまでに)何が行われたのか- どんなHTTP ヘッダが付いてどう影響するのか- 次のリクエストでどうなるか ...etc.- もちろん必要なとき、有用なときはある- e.g. フレンドリーなエラーページに差し替えるときVCL restart; 69