ユーザーはいつ新機能を実際に使えるのか?

新機能を本番環境にデプロイした。チームはひと安心。チケットは完了とマークされた。しかし、そこで質問が始まる。「もうアナウンスしていい?」「マーケティングチームを待つべき?」「見落としたエッジケースが本番トラフィックで顕在化したらどうしよう?」

この瞬間は、多くのチームが認める以上に頻繁に発生する。コードはサーバー上にあるが、ユーザーに実際に見せるべきかどうか確信が持てない。そして、その不確実性は、何か問題が起きるまで多くのチームが気づかないギャップを露呈する。

デプロイとリリースは別物

ソフトウェアを出荷する際の考え方が変わる重要な区別がある。コードをデプロイすることと、機能をリリースすることは、まったく別の行為だ。

デプロイとは、新しいコードをサーバーに配置する行為である。リリースとは、ユーザーがその機能を実際に目にし、利用できる瞬間のことだ。多くのチームでは、これら二つのイベントがデフォルトで同時に発生する。コードが上がれば、機能が現れる。しかし、両者を結びつける必要はない。

何千人ものユーザーが使うアプリケーションを運用していると想像してほしい。高速な検索機能を追加した。コードはすべてのサーバーにデプロイされた。しかし、稼働後、その機能が原因でサーバー負荷が急上昇した。ユーザーはページの表示が遅くなり、中にはトップページすら開けなくなる。深刻な問題だ。たった一つの機能が壊れただけなのに、アプリケーション全体をロールバックしなければならない。あるいは修正を書いて、デプロイプロセス全体を再度実行し、今度はうまくいくことを願うしかない。その間ずっと、ユーザーは影響を受け続ける。

コードがサーバーに配置されるタイミングと、機能がユーザーに見えるタイミングを分離できれば、この状況は回避可能だ。新しい機能をすでに含んだコードをデプロイしておき、その機能をオフにしたままにできる。安全性を確認できた後にのみ、スイッチをオンにするのだ。

次のシーケンス図は、フィーチャーフラグがコードのデプロイとユーザーへのリリースの間に安全なギャップを生み出す仕組みを示している。

sequenceDiagram participant Dev as 開発者 participant CI as CI/CD participant Prod as 本番環境 participant Flag as フィーチャーフラグ participant User as ユーザー Dev->>CI: コードをコミット CI->>Prod: ビルド & デプロイ Note over Prod: コードは稼働中、フラグはOFF Prod->>Flag: フラグを確認 Flag-->>Prod: 無効 Prod->>User: 旧動作 Note over Dev,Flag: 観察 & テスト Dev->>Flag: フラグを有効化 Flag-->>Prod: 有効 Prod->>User: 新機能が表示される

フィーチャーフラグ:コード内のスイッチ

この分離を実現するメカニズムがフィーチャーフラグだ。フィーチャーフラグとは、コード内の条件分岐スイッチであり、特定の機能を実行するかどうかを決定する。新しいコードをデプロイすることなく、そのスイッチの値を変更できる。設定を更新するだけで、実行中のアプリケーションで機能のオン/オフを切り替えられる。

以下はシンプルな例だ。新しい検索ロジックを直接呼び出す代わりに、チェックでラップする。

if feature_flags.is_enabled("fast_search"):
    results = fast_search(query)
else:
    results = old_search(query)

fast_search が無効の場合、アプリケーションは古いコードパスを実行する。有効にすると、新しいロジックが動作する。スイッチのためにデプロイは不要だ。

これにより、ユーザーが機能を使い始めるタイミングを制御できる。未完成の作業によってユーザーがすぐに影響を受けることを心配せずに、好きなときにデプロイできる。まずは限られたユーザーセットで本番環境で機能をテストできる。問題のある機能を、アプリケーション全体をロールバックすることなく、数秒で無効化できる。

フィーチャーフラグで可能になること

フィーチャーフラグを導入すれば、いくつかの実用的なパターンが可能になる。

段階的ロールアウト。 全ユーザーに一度にリリースする代わりに、まず1%のユーザー、次に10%、そして50%と有効化できる。10%の時点で問題が発生すれば、全員に影響が及ぶ前に気づける。これは、ステージング環境ではテストが難しく、実際のトラフィックパターンやデータ量に依存する機能に特に有効だ。

カナリアリリース。 少数のユーザーグループだけを新機能にルーティングし、他のユーザーは旧動作のままにする。エラー率、レイテンシ、ユーザー行動を監視する。カナリアグループに問題がなければロールアウトを拡大する。問題が発生したら即座にオフにする。

キルスイッチ。 機能が本番環境で予期せぬ問題を引き起こした場合、即座に無効化できる。コミットを元に戻す必要も、再ビルドや再デプロイも不要だ。フラグをオフにするだけだ。いつでもすぐにプラグを抜けると分かっていれば、チームのプレッシャーは軽減される。

本番環境でのテスト。 リスクに聞こえるかもしれないが、実際には代替手段より安全だ。フィーチャーフラグを使えば、まず社内ユーザーやベータテスター向けに機能を有効化できる。彼らは実際のデータと実際のワークフローで機能を試す。問題が広範なユーザー層に届く前に発見できる。

調整されたリリース。 ビジネス上の理由で、特定のタイミングで機能を公開する必要がある場合がある。マーケティングチームがキャンペーンを計画しているかもしれない。規制上の期限に関連しているかもしれない。フィーチャーフラグを使えば、コードを数日または数週間前にデプロイしておき、必要な瞬間に正確に有効化できる。

フィーチャーフラグのコスト

フィーチャーフラグは無料ではない。コードベースに複雑さをもたらす。すべてのフラグはテストが必要な条件分岐を導入する。フラグが時間とともに蓄積されると、コードの可読性と保守性が低下する。常に有効または常に無効になっている古いフラグは、将来の開発者を混乱させるデッドコードパスを生み出す。

運用コストもある。環境間でフラグ設定を管理する仕組みが必要だ。フラグをサーバーレベル、ユーザーレベル、または他の粒度で評価するかを決定する必要がある。フラグの変更が迅速かつ確実に伝播することを保証する必要がある。

チームが最もよく犯す間違いは、フィーチャーフラグを恒久的なものとして扱うことだ。フラグには明確なライフサイクルがあるべきだ。特定の目的のために導入され、機能のロールアウト中はアクティブであり、機能が完全にリリースされ安定したら削除される。コードに永久に残るフラグは技術負債となる。

フィーチャーフラグ使用のための実践的チェックリスト

フィーチャーフラグを採用する場合、管理を維持するための短いチェックリストを以下に示す。

  • シンプルな実装から始める。小規模チームにはJSON設定ファイルや環境変数で十分だ。ニーズを理解する前にフラグシステムを過剰に設計するのは避ける。
  • フラグ名は明確に。実装の詳細ではなく、機能を説明する名前を使う。search_v2_algorithm より fast_search の方が良い。
  • 機能が安定したらフラグを削除する。リマインダーを設定するか、ロールアウト完了後にフラグコードをクリーンアップするチケットを作成する。
  • 両方のフラグ状態をテストする。フラグがオンの場合とオフの場合の動作をテストがカバーしていることを確認する。古いコードパスを壊すフラグは、フラグがないより悪い。
  • フラグ評価をログに記録する。本番環境の問題をデバッグする際に、どのフラグがどのユーザーにいつアクティブだったかを知る必要がある。
  • アクティブなフラグの数を制限する。フラグが多すぎるとコードの推論が難しくなる。同時に数十のフラグがアクティブな場合、恒久的な設定にすべきものにフラグを使っていないか検討する。

まとめ

次にチームが機能を完了したとき、この質問をしてみよう。「コードはデプロイされたのか、それとも機能はリリースされたのか?」デフォルトで答えが同じなら、より安全でストレスの少ない出荷の機会を逃している。フィーチャーフラグは、これら二つのイベントを分離する力を与える。自分のスケジュールでデプロイし、準備ができたときにリリースできる。次のリスクの高い機能のために、まずは一つのフラグから始めてみよう。それがチームの出荷に対する考え方をどう変えるか、見てみよう。