フィーチャーフラグだけがリリース制御ではない

あるチームが新しいチェックアウトフローの構築に3ヶ月を費やした。コードは完成し、ステージングでテストされ、フィーチャーフラグで隠した状態でメインブランチにマージされた。いざフラグを有効にして5%のユーザーに公開したとき、決済プロバイダからステージングでは一度も見たことのないエラーが返ってきた。フラグのおかげで数秒で無効にできた。しかし、本当の問題は「そもそもそのコードを本番環境に置くべきだったのか」という点にある。

フィーチャーフラグは強力だ。まだ有効化していないコードをデプロイでき、本番トラフィックでテストでき、段階的にロールアウトできる。しかし、万能ではない。ブランチのほうが適している場合もある。別の環境を使うべき場合もある。そして、3つすべてを組み合わせる必要がある場合もある。

最初に考えるべきことは「新しい機能へのアクセスを遅らせたり制限したりすることで、何を達成しようとしているのか」だ。

ブランチを使うべきとき

ブランチは進行中の作業を分離するために存在する。機能が未完成で、アプリケーションを壊さずに実行できないなら、ブランチに置くべきだ。コードはメインブランチに一切入らない。誰も誤ってデプロイしない。フラグの存在を覚えておく必要もない。

これは最もシンプルな制御方法だ。特に複数の開発者が別々の部分を担当している場合、まだ構築中の機能に適している。メインブランチはクリーンな状態を保てる。チームは機能が完成しレビューが終わったときだけマージする。

ただしブランチには限界がある。コードをマージした時点で、その有効化を制御する手段を失う。機能はメインブランチにあるか、ないかの二択だ。中間は存在しない。そこでフィーチャーフラグの出番となる。

フィーチャーフラグを使うべきとき

フィーチャーフラグはコードをマージした後の振る舞いを制御する。コードはメインブランチにあり、本番環境にデプロイされているが、すべてのユーザーに対して有効化されてはいない。一部の割合、内部テスター、特定の条件でのみ有効にできる。

これは機能が機能的に完成しているが、全員に公開する準備ができていない場合に有用だ。実際のトラフィックで安定性を検証したいかもしれない。フルロールアウト前にエラー率を監視したいかもしれない。数日かけて段階的に増やしたいかもしれない。

フィーチャーフラグはロールバックにも役立つ。問題が発生した場合、デプロイを巻き戻す代わりにフラグをオフにする。コードのロールバックよりも高速で安全だ。特にデータベースマイグレーションやその他の不可逆的な変更を含むデプロイでは顕著だ。

ただしフィーチャーフラグにはコストがかかる。コードベースに複雑さを追加する。すべてのフラグはif-elseの分岐であり、保守、テスト、そして最終的には削除が必要だ。長期間残りすぎたフラグは技術的負債を生む。何百もの古いフラグを抱えたチームは、コードが読みづらくデバッグが困難になる。

別環境を使うべきとき

ステージング環境は本番前にテストする場所を提供する。実際のユーザーから隔離されている。統合テスト、手動QA、探索的テストを誰にも影響を与えずに実行できる。

問題は、ステージングが本番と完全に同一になることは決してない点だ。トラフィックパターンが異なる。データ量が少ない。実際のユーザーの振る舞いは再現できない。本番負荷、本番データ、正確なインフラ設定でのみ発生する問題がある。

だからこそ、フィーチャーフラグと環境は補完関係にある。早期テストにはステージングを使う。本番検証にはフィーチャーフラグを使う。これらは代替手段ではなく、2層の安全策だ。

決済システムの置き換えやログインページの再設計など、コアフローを変更する大規模な機能は、まずステージングを通すべきだ。そこで合格したら、本番でフラグの背後にデプロイし、徐々に露出を増やす。

ブランチ、環境、フラグの組み合わせ

実際には、多くのチームが3つすべてを組み合わせて使っている。以下は一般的なパターンだ。

  1. 大規模な機能を別ブランチで作業する。
  2. フラグをオフにした状態でメインブランチにマージする。
  3. ステージングにデプロイし、ステージングでフラグを有効にしてテストする。
  4. フラグをオフにしたまま本番にデプロイする。
  5. 内部ユーザーまたは少数の割合でフラグを有効にする。
  6. 監視結果に基づいて割合を徐々に増やす。
  7. 機能が完全にロールアウトされ安定したらフラグを削除する。

このパターンはトランクベース開発を実践するチームで一般的だ。メインブランチは常にデプロイ可能な状態を保つ。大規模な機能は小さな単位に分割され、それぞれがフラグで制御される。チームは頻繁にマージし、頻繁にデプロイし、フラグを使ってユーザーに見えるものを制御する。

フィーチャーフラグが不適切なケース

フィーチャーフラグが常に最良のツールとは限らない。以下の状況を考えてみよう。

  • 機能が実行可能でない。 コードがコンパイルできない、テストに失敗する、起動時にクラッシュするなら、マージすべきではない。動作するまでブランチに置いておく。
  • 変更が大きすぎて単一のフラグで制御できない。 サブシステム全体を切り替えるフラグはテストが難しく、フリップするリスクが高い。機能を小さな単位に分割し、それぞれに個別のフラグを割り当てるか、初期検証には環境を使う。
  • チームが決断を先延ばしにするためにフラグを使っている。 機能の準備ができているか誰も決めたくないためにフラグが存在するなら、それはツールの問題ではなくプロセスの問題だ。フラグは迅速なフィードバックを可能にするためのものであり、難しい会話を遅らせるためのものではない。
  • フラグが永久に残る。 削除がリスクに感じられるためにフラグを無期限に残すチームがある。それはフラグの設計が不適切か、リリースプロセスに自信がない証拠だ。すべてのフラグには計画的な削除日を設定すべきだ。

リリース制御選択の実践的チェックリスト

状況 推奨される制御方法
機能が未完成で実行できない ブランチ
機能は完成しているが本番検証が必要 フィーチャーフラグ
本番前に隔離されたテストが必要 ステージング環境
機能が大規模でコア動作を変更する ステージング→フラグ
トランクベース開発を実践している ブランチ+フラグの組み合わせ
機能が小さくリスクが低い フィーチャーフラグまたは直接デプロイ

これは固定された表ではない。チームごとにリスク許容度やインフラは異なる。ルールブックではなく、議論の出発点として使ってほしい。

以下の判断ツリーは、主要な質問と推奨される制御方法をまとめたものだ。

flowchart TD A[機能は完成しているか?] -->|いいえ| B[ブランチを使う] A -->|はい| C[段階的ロールアウトが必要か?] C -->|いいえ| D[隔離されたテストが必要か?] C -->|はい| E[フィーチャーフラグを使う] D -->|いいえ| F[直接デプロイまたは小規模フラグ] D -->|はい| G[ステージング環境を使う] E --> H[ステージングでもテストするか?] H -->|はい| I[環境+フラグ] H -->|いいえ| J[フラグのみ] G --> K[段階的ロールアウトも必要か?] K -->|はい| L[環境+フラグ] K -->|いいえ| M[環境のみ]

本当の目標

フィーチャーフラグ、ブランチ、環境はツールだ。目標はこれらすべてを使うことではない。目標はソフトウェアを安全に出荷し、迅速にフィードバックを得ることだ。

優れたリリース戦略は、頻繁にデプロイし、リスクを制御しながら本番でテストし、問題が発生したらすぐに停止できるようにする。決断を先延ばしにしたり、未完成の機能を本番に溜め込んだりするためのものではない。

まず自分が何を制御しようとしているのかを理解しよう。そして適切なツールを選ぼう。フィーチャーフラグを使うときは、削除することを忘れないでほしい。最高のフラグとは、もはや存在しないフラグのことだ。