開発プロセス●●●○○

フィーチャーフラグ

Feature Flag ・ フィーチャーフラグ

コードを残したまま機能のON/OFFを実行時に切り替える仕組み。デプロイとリリースを分離する。

✦ 30秒で試す — 航海コードは本番にあるのに、機能はまだ誰にも見えていない新しい画面を一度でも見る人は、どうなる?予想してみる

概要

フィーチャーフラグ(フィーチャートグルとも呼ばれます)は、コードの中に「この機能を有効にするか」という分岐を仕込み、その ON/OFF をデプロイし直すことなく実行時に切り替えられるようにする仕組みです。新機能のコードは本番に配置済みだがフラグは OFF、準備が整った瞬間にフラグを ON にして公開する — つまり「コードを本番に置くこと(デプロイ)」と「機能をユーザーに見せること(リリース)」を別々のタイミングで行えるようになります。

この「デプロイとリリースの分離」が本質です。エンジニアは好きなタイミングで安全にデプロイし、プロダクトマネージャーはビジネス上最適なタイミングでリリースする。障害が起きても、デプロイのロールバックではなくフラグを OFF にするだけで機能を引っ込められる。さらに「全ユーザーの10%だけ ON」「社内ユーザーだけ ON」といった段階的な公開や A/B テストにも使えるため、単なる if 文の工夫を超えて、リリース戦略そのものを支えるインフラになっています。

なぜ生まれたか

背景にあるのは、ブランチ運用の苦しみです。かつては大きな機能を数週間かけて専用のブランチ(Git の枝)で開発し、完成してから main に統合するのが普通でした。しかし長生きしたブランチ同士の統合は衝突だらけの「マージ地獄」になり、CI/CD(継続的インテグレーション)が目指す「小さく頻繁に統合する」という理想と真っ向から対立します。かといって未完成の機能を main に混ぜれば、次のデプロイで作りかけの画面がユーザーに見えてしまう。「統合は頻繁にしたい、公開はまだしたくない」— この矛盾を解いたのがフィーチャーフラグです。未完成コードをフラグで隠したまま main に統合すれば、ブランチを長生きさせずに済みます。全員が main に直接小さくコミットしていくトランクベース開発は、フィーチャーフラグがあって初めて現実的に回る開発スタイルです。フラグで出し分ける考え方自体は古くからありますが、2009年ごろに Flickr が「ブランチを切らずに毎日何十回もデプロイする」運用の要としてフラグを紹介したことが、この組み合わせを広く知らしめました。

もう一つの背景は、リリースの意思決定を巻き戻し可能にしたいという運用上の要請です。デプロイと公開が一体だった時代、機能に問題があれば再デプロイという重い手続きでしか撤回できませんでした。ビルドし直し、テストを回し、全サーバへ配り直すまで、壊れた機能は出たままです。フラグならスイッチ一つ、数秒で撤回できます。

詳細

仕組み — 分岐とフラグ管理の分離

コード側の実装は素朴で、if (flags.isEnabled("new-checkout", user)) のような分岐を書くだけです。仕組みの中心はむしろ、この判定の答えをコードの外から与えるフラグ管理側にあります。フラグの状態は設定ファイル・データベース・専用の管理サービス(LaunchDarkly や Unleash など)に置かれ、管理画面から変更すると、デプロイなしで全サーバの挙動が変わります。製品ごとに異なる SDK の呼び出し方を揃える標準 API として、CNCF の OpenFeature も使われています。

ユーザーリクエストアプリケーションデプロイ済みコード新機能の分岐を内包flag を毎回評価フラグ管理サービスON/OFF・対象条件を配信flag ONflag OFF新機能の画面対象10%のユーザー従来の画面残り90%のユーザーコードは全ユーザー共通 — 出し分けはデプロイではなくフラグ設定の変更で行う
デプロイとリリースの分離 — 同じコードが本番にあっても、見せるかどうかはフラグ側で決める

判定は単純な真偽値にとどまりません。「全ユーザーの10%だけ」「特定の組織・地域・プランのユーザーだけ ON」といったターゲティングルールを持てるのが実用上の要点で、同じユーザーには常に同じ結果が返るよう決定的に評価されます。毎回サイコロを振って決めると、同じ人がページを開くたびに新しい画面と古い画面が入れ替わってしまい、体験としても A/B テストの計測としても使いものになりません。

そこで割合での公開は、ユーザーIDをハッシュ化して決めるのが定石です。「フラグ名+ユーザーID」をハッシュ関数に通し、出てきた値を 0〜1 の数(あるいは 0〜99 のバケット番号)に写して、それが公開率より小さければ ON にします。ハッシュは同じ入力に必ず同じ値を返すので、同じ人は何度来ても同じ側に入ります。公開率を 10% から 30% に上げたときも、しきい値が右へ動くだけなので、すでに ON だった 10% の人は ON のまま、新たに 20% の人が加わります。フラグ名を混ぜるのは、フラグごとに別の人たちを選ぶためです。ユーザーIDだけで決めると、どの実験でも同じ「先頭10%」の人が毎回新機能の実験台になってしまいます。

下の部品で確かめてください。粒の1つ1つがユーザーで、横の位置がそのハッシュ値です。しきい値の線を右へドラッグすると公開が広がり、全員がリロードしても粒は動きません。判定を「毎回ランダム」に切り替えると、リロードのたびに粒が飛び回って表示が入れ替わります。最後にキルスイッチを押すと、デプロイなしで全員が OFF に戻ります。

⚡ 体験: ハッシュで決める段階的公開
判定のしかた
ON: 新しい画面OFF: 従来の画面00.250.50.751hash("new-checkout:" + ユーザーID) を 0〜1 に写した値user-8305: hash = 0.421 → OFFuser-7397: hash = 0.792 → OFFuser-6854: hash = 0.702 → OFFuser-7167: hash = 0.462 → OFFuser-5670: hash = 0.773 → OFFuser-6990: hash = 0.469 → OFFuser-4243: hash = 0.002 → ONuser-2429: hash = 0.593 → OFFuser-5141: hash = 0.895 → OFFuser-7319: hash = 0.866 → OFFuser-6908: hash = 0.557 → OFFuser-8172: hash = 0.994 → OFFuser-5305: hash = 0.034 → ONuser-1776: hash = 0.727 → OFFuser-5221: hash = 0.223 → OFFuser-4975: hash = 0.505 → OFFuser-9340: hash = 0.650 → OFFuser-2445: hash = 0.209 → OFFuser-1038: hash = 0.358 → OFFuser-6247: hash = 0.292 → OFFuser-9996: hash = 0.807 → OFFuser-9164: hash = 0.878 → OFFuser-3553: hash = 0.388 → OFFuser-9074: hash = 0.418 → OFFuser-1115: hash = 0.142 → OFFuser-4150: hash = 0.670 → OFFuser-4422: hash = 0.121 → OFFuser-5688: hash = 0.119 → OFFuser-4377: hash = 0.453 → OFFuser-7630: hash = 0.715 → OFFuser-1771: hash = 0.620 → OFFuser-6834: hash = 0.944 → OFFuser-6985: hash = 0.935 → OFFuser-7665: hash = 0.346 → OFFuser-3929: hash = 0.424 → OFFuser-2303: hash = 0.100 → OFFuser-3499: hash = 0.071 → ONuser-4401: hash = 0.729 → OFFuser-7131: hash = 0.848 → OFFuser-5972: hash = 0.313 → OFFuser-4870: hash = 0.795 → OFFuser-7389: hash = 0.493 → OFFuser-2696: hash = 0.115 → OFFuser-5147: hash = 0.482 → OFFuser-5137: hash = 0.248 → OFFuser-8364: hash = 0.283 → OFFuser-4311: hash = 0.310 → OFFuser-4889: hash = 0.159 → OFFuser-9480: hash = 0.196 → OFFuser-2043: hash = 0.361 → OFFuser-8235: hash = 0.316 → OFFuser-2046: hash = 0.131 → OFFuser-2687: hash = 0.172 → OFFuser-5971: hash = 0.474 → OFFuser-9012: hash = 0.280 → OFFuser-9657: hash = 0.175 → OFFuser-9256: hash = 0.086 → ONuser-4403: hash = 0.947 → OFFuser-7080: hash = 0.361 → OFFuser-1046: hash = 0.743 → OFFuser-5308: hash = 0.990 → OFFuser-6011: hash = 0.404 → OFFuser-3826: hash = 0.456 → OFFuser-4310: hash = 0.909 → OFFuser-2951: hash = 0.418 → OFFuser-7107: hash = 0.270 → OFFuser-3946: hash = 0.735 → OFFuser-7734: hash = 0.479 → OFFuser-4223: hash = 0.721 → OFFuser-6473: hash = 0.817 → OFFuser-8258: hash = 0.405 → OFFuser-2807: hash = 0.087 → ONあなた: OFF公開率 10%
新しい画面の人5 / 72人リロードで入れ替わった人0人リロード0回デプロイ0回

粒は1人ずつのユーザーで、横の位置は「フラグ名+ユーザーID」のハッシュ値(0〜1)です。金のしきい値の線を右へドラッグして、新しい決済画面の公開率を上げてみてください。

これにより、カナリアリリースがインフラ層(新しいバージョンのサーバへ送る通信の割合)で行う段階的公開を、機能単位・アプリケーション層で実現できます。評価の場所にも選択肢があります。サーバ側で評価すれば判定ルールを利用者に見せずに済み、ブラウザやスマホアプリ側で評価すれば通信なしで即座に分岐できますが、ルールや未公開機能の存在が利用者の手元から読めてしまいます。どちらの場合も、フラグ管理サービスに届かないときに使う既定値を決めておくことが大切です。既定値は「新機能を OFF」にしておけば、管理サービスが落ちても従来の挙動に倒れます。

フラグの4分類と使いどころ

フィーチャーフラグと一口に言っても寿命と役割はさまざまで、Pete Hodgson の整理では4種類に分けられます。未完成機能を隠して公開タイミングを制御する「リリーストグル」(寿命は数週間、リリース後に削除する)。A/B テストで2案をユーザー群に出し分け、メトリクスで効果を比較する「エクスペリメントトグル」(実験期間だけ生きる)。高負荷時に重い機能を落として本体を守る、いわば運用のスイッチである「オプストグル」(長寿命で、サーキットブレーカーのように手動・自動で機能を切り離すのに使う。グレースフルデグラデーションの実装手段でもある)。有料プランにだけ機能を出すといった認可の一種である「パーミッショントグル」(事実上恒久的)です。この分類が重要なのは、寿命と変更頻度が違うものを同じ仕組みで雑に扱うと後述の負債化を招くからです。

A/B テストの文脈では、フラグは実験基盤の中核になります。ユーザーを無作為に2群に分けて別の体験を与え、コンバージョン率などを統計的に比較する — 出し分けの機構はリリーストグルと同一で、違いは「効果測定とセットで運用されること」だけです。計測にはテストとはまた別の、行動ログの収集・分析の仕組みが必要になります。

負債化 — フラグの消し忘れという古典的な病

フィーチャーフラグ最大の落とし穴は、役目を終えたフラグがコードに残り続けることです。リリーストグルは全ユーザーに ON になった時点で用済みですが、削除は誰の緊急タスクでもないため放置されがちです。フラグが溜まると、分岐の組み合わせが爆発してテストすべき状態数が増え、「この分岐はもう通らないのか、誰も分からない」というデッドコードが蓄積します。フラグ管理の失敗例としてよく引かれるのが、2012年8月に米国の証券会社 Knight Capital が約45分間で4億6000万ドル以上を失った事故です。同社の注文システムには、2003年に使われなくなった「Power Peg」という古い機能のコードが9年間残っていました。新機能を出すとき、その古い機能が使っていたフラグを新機能用に再利用し、しかも新しいコードの配布が8台のサーバのうち1台だけ漏れていました。フラグを ON にした瞬間、新しいコードが入った7台では新機能が、配布漏れの1台では眠っていた Power Peg が動き出し、大量の誤発注を出し続けたのです。本質は、消さずに残した死んだコードと、意味の違う用途へのフラグの再利用、そしてデプロイの確認漏れが重なったことにあります。

対策は運用の規律です。フラグ作成時に削除期限とオーナーを決める、管理サービスの「最終評価日時」で使われていないフラグを検出する、全公開から一定期間たったフラグの削除をスクラムのスプリントの定常タスクに組み込む、といった仕組みで「作ったら消すまでがフラグ」を徹底します。フラグ名は用途ごとに使い捨てにし、別の意味に使い回さないことも Knight Capital の教訓です。また、フラグの評価結果はデバッグの重要な手がかりになるため、ログや分散トレーシングのスパンに「このリクエストはどのフラグがどう評価されたか」を記録しておくと、「一部のユーザーだけ挙動が違う」という調査が格段に楽になります。

デプロイ戦略の中での位置づけ

ブルーグリーンデプロイやカナリアリリースがバージョン全体をインフラ層で切り替えるのに対し、フィーチャーフラグは個々の機能をコード内で切り替えます。粒度が細かいぶん制御は柔軟ですが、分岐がコードに増える複雑さのコストを払います。実務では対立ではなく併用が基本です。デプロイの安全性はカナリアで、機能公開のタイミングと対象はフラグで制御する、という二段構えが、リリース頻度と安全性を両立させる現代的な構成です。

試問

新しい決済画面を一部のユーザーにだけ見せる画面です。粒の1つ1つがユーザー(水色の輪があなた)で、横の位置がその人の ON/OFF を決める値(0〜1)。金のしきい値の線より左の粒に新しい画面が出て、線はドラッグで動かせます。判定のしかたは「ハッシュ(フラグ名+ユーザーIDから計算する決まった値)」と「毎回ランダム」から選べ、「全員がリロード」で全員がページを開き直します。いまは「毎回ランダム」です。

✦ 命題挑戦中

新しい決済画面の公開率を 30% 以上に広げ、全員がページを開き直しても、誰一人として新旧の画面が入れ替わらないようにせよ

  • 公開率を 30% 以上に広げる(キルスイッチは切ったまま)
  • その設定のまま全員がリロードしても、画面が入れ替わった人は 0 人
⚡ 体験: ハッシュで決める段階的公開
判定のしかた
ON: 新しい画面OFF: 従来の画面00.250.50.751ページを開くたびに振る乱数(0〜1)user-8305: 乱数 = 0.690 → OFFuser-7397: 乱数 = 0.742 → OFFuser-6854: 乱数 = 0.377 → OFFuser-7167: 乱数 = 0.808 → OFFuser-5670: 乱数 = 0.248 → OFFuser-6990: 乱数 = 0.687 → OFFuser-4243: 乱数 = 0.058 → ONuser-2429: 乱数 = 0.394 → OFFuser-5141: 乱数 = 0.620 → OFFuser-7319: 乱数 = 0.800 → OFFuser-6908: 乱数 = 0.327 → OFFuser-8172: 乱数 = 0.074 → ONuser-5305: 乱数 = 0.288 → OFFuser-1776: 乱数 = 0.479 → OFFuser-5221: 乱数 = 0.348 → OFFuser-4975: 乱数 = 0.527 → OFFuser-9340: 乱数 = 0.271 → OFFuser-2445: 乱数 = 0.470 → OFFuser-1038: 乱数 = 0.368 → OFFuser-6247: 乱数 = 0.014 → ONuser-9996: 乱数 = 0.756 → OFFuser-9164: 乱数 = 0.640 → OFFuser-3553: 乱数 = 0.461 → OFFuser-9074: 乱数 = 0.132 → OFFuser-1115: 乱数 = 0.797 → OFFuser-4150: 乱数 = 0.535 → OFFuser-4422: 乱数 = 0.751 → OFFuser-5688: 乱数 = 0.378 → OFFuser-4377: 乱数 = 0.875 → OFFuser-7630: 乱数 = 0.929 → OFFuser-1771: 乱数 = 0.074 → ONuser-6834: 乱数 = 0.195 → OFFuser-6985: 乱数 = 0.759 → OFFuser-7665: 乱数 = 0.815 → OFFuser-3929: 乱数 = 0.934 → OFFuser-2303: 乱数 = 0.372 → OFFuser-3499: 乱数 = 0.346 → OFFuser-4401: 乱数 = 0.767 → OFFuser-7131: 乱数 = 0.969 → OFFuser-5972: 乱数 = 0.266 → OFFuser-4870: 乱数 = 0.657 → OFFuser-7389: 乱数 = 0.849 → OFFuser-2696: 乱数 = 0.676 → OFFuser-5147: 乱数 = 0.413 → OFFuser-5137: 乱数 = 0.019 → ONuser-8364: 乱数 = 0.722 → OFFuser-4311: 乱数 = 0.632 → OFFuser-4889: 乱数 = 0.170 → OFFuser-9480: 乱数 = 0.060 → ONuser-2043: 乱数 = 0.904 → OFFuser-8235: 乱数 = 0.580 → OFFuser-2046: 乱数 = 0.584 → OFFuser-2687: 乱数 = 0.636 → OFFuser-5971: 乱数 = 0.762 → OFFuser-9012: 乱数 = 0.443 → OFFuser-9657: 乱数 = 0.409 → OFFuser-9256: 乱数 = 0.919 → OFFuser-4403: 乱数 = 0.432 → OFFuser-7080: 乱数 = 0.200 → OFFuser-1046: 乱数 = 0.059 → ONuser-5308: 乱数 = 0.024 → ONuser-6011: 乱数 = 0.048 → ONuser-3826: 乱数 = 0.597 → OFFuser-4310: 乱数 = 0.036 → ONuser-2951: 乱数 = 0.571 → OFFuser-7107: 乱数 = 0.893 → OFFuser-3946: 乱数 = 0.177 → OFFuser-7734: 乱数 = 0.852 → OFFuser-4223: 乱数 = 0.224 → OFFuser-6473: 乱数 = 0.229 → OFFuser-8258: 乱数 = 0.779 → OFFuser-2807: 乱数 = 0.925 → OFFあなた: OFF公開率 10%
新しい画面の人10 / 72人リロードで入れ替わった人0人リロード0回デプロイ0回

粒は1人ずつのユーザーで、横の位置はその人の ON/OFF を決める値(0〜1)です。金のしきい値の線より左の粒に、新しい決済画面が出ます。