
Feature Flagの設計と運用:本番環境での安全なリリース管理を実現する実装パターン
お疲れ様です!IT業界で働くアライグマです!
「新機能をリリースしたら本番で障害が発生して、深夜に緊急ロールバック…」という経験、エンジニアなら一度はあるのではないでしょうか。
私がPjMとして関わったプロジェクトでも、リリース直後のトラブルは何度も経験しました。特に大規模なシステムでは、一度デプロイしてしまうと切り戻しに時間がかかり、その間ずっとユーザーに影響が出続けるという状況に陥りがちです。
- Feature Flagの基本概念と導入メリット
- 段階的ロールアウトとカナリアリリースの実装パターン
- 運用時の注意点とフラグ管理のベストプラクティス
この記事では、Feature Flag(機能フラグ)を活用して、本番環境での安全なリリース管理を実現する方法を解説します。
Feature Flagとは何か:リリースとデプロイを分離する考え方
Feature Flagとは、コード内に条件分岐を設けて、特定の機能のON/OFFを動的に切り替えられる仕組みです。循環的複雑度を活用したコード品質改善でも触れましたが、コードの複雑性を管理しながら柔軟なリリース戦略を実現できます。
従来のリリース方法では、「デプロイ=機能公開」でした。つまり、コードを本番環境にデプロイした瞬間から、すべてのユーザーが新機能を使えるようになります。この方法の問題点は明らかです。
- 問題が発生した場合、ロールバック(再デプロイ)が必要
- ロールバックには時間がかかり、その間ユーザーに影響が出続ける
- 大規模な変更ほどリスクが高く、リリースが慎重になりすぎる
Feature Flagを導入すると、「デプロイ」と「機能公開」を分離できます。コードは本番環境にデプロイされていても、フラグがOFFなら機能は動作しません。問題が発生したら、フラグをOFFにするだけで即座に機能を無効化できます。ソフトウェアアーキテクチャの基礎では、このような設計パターンがシステムの柔軟性と保守性を高めることが解説されています。
私のチームでは、Feature Flag導入後、リリース起因の障害対応時間が平均45分から5分に短縮されました。ロールバックのためのデプロイを待つ必要がなくなったからです。

Feature Flagの種類と使い分け
Feature Flagには複数の種類があり、目的に応じて使い分けることが重要です。Uber CacheFront設計解説のような大規模システムでも、段階的なロールアウトにFeature Flagが活用されています。
リリースフラグ(Release Flag)
新機能のリリース制御に使用します。開発中は全ユーザーに対してOFF、リリース時にONにします。最も一般的なFeature Flagです。
実験フラグ(Experiment Flag)
A/Bテストに使用します。ユーザーをグループ分けして、異なる機能やUIを表示し、どちらが効果的かを検証します。
運用フラグ(Ops Flag)
システムの動作を制御するために使用します。負荷が高いときに特定の機能を一時的に無効化したり、メンテナンス時に特定の処理をスキップしたりします。
権限フラグ(Permission Flag)
特定のユーザーやグループにのみ機能を公開するために使用します。ベータテスターや社内ユーザーに先行公開する場合などに活用します。
チームトポロジーでも触れられていますが、チーム構成によってフラグの管理責任を明確にすることが運用の鍵になります。私のプロジェクトでは、リリースフラグは開発チーム、運用フラグはSREチームが管理するという役割分担をしています。

基本的な実装パターン:シンプルなFeature Flag
Feature Flagの最もシンプルな実装は、設定ファイルや環境変数で制御する方法です。Terraform実践ガイドでインフラをコード化しているプロジェクトなら、環境変数での制御が親和性が高いでしょう。
環境変数による制御
import os
def is_feature_enabled(feature_name: str) -> bool:
"""環境変数でFeature Flagを制御"""
env_key = f"FEATURE_{feature_name.upper()}_ENABLED"
return os.getenv(env_key, "false").lower() == "true"
# 使用例
if is_feature_enabled("new_checkout"):
return new_checkout_flow(user)
else:
return legacy_checkout_flow(user)
設定ファイルによる制御
# config/feature_flags.yaml
features:
new_checkout:
enabled: true
rollout_percentage: 50
dark_mode:
enabled: false
allowed_users:
- "beta_tester_1"
- "beta_tester_2"
import yaml
import random
class FeatureFlagManager:
def __init__(self, config_path: str):
with open(config_path) as f:
self.config = yaml.safe_load(f)
def is_enabled(self, feature_name: str, user_id: str = None) -> bool:
feature = self.config.get("features", {}).get(feature_name, {})
if not feature.get("enabled", False):
return False
# 特定ユーザーのみ許可
allowed_users = feature.get("allowed_users", [])
if allowed_users and user_id in allowed_users:
return True
# ロールアウト率による制御
rollout = feature.get("rollout_percentage", 100)
return random.randint(1, 100) <= rollout
このシンプルな実装でも、基本的なFeature Flagの恩恵は受けられます。リファクタリング(第2版)で解説されているように、まずは動くものを作り、必要に応じて改善していくアプローチが有効です。

段階的ロールアウトとカナリアリリースの実装
Feature Flagの真価は、段階的ロールアウトで発揮されます。AWS設計ガイドラインでも推奨されているように、大規模システムでは一度に全ユーザーへ公開するのではなく、段階的に展開することでリスクを最小化できます。
ユーザーIDベースの段階的ロールアウト
import hashlib
class GradualRollout:
def __init__(self, feature_name: str, percentage: int):
self.feature_name = feature_name
self.percentage = percentage
def is_enabled_for_user(self, user_id: str) -> bool:
"""ユーザーIDのハッシュ値で一貫した判定を行う"""
hash_input = f"{self.feature_name}:{user_id}"
hash_value = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)
bucket = hash_value % 100
return bucket < self.percentage
# 使用例:10%のユーザーに新機能を公開
rollout = GradualRollout("new_dashboard", percentage=10)
# 同じユーザーには常に同じ結果が返る
print(rollout.is_enabled_for_user("user_123")) # True or False(一貫)
ポイントは、ユーザーIDのハッシュ値を使うことで、同じユーザーには常に同じ結果が返る点です。ランダムに判定すると、同じユーザーがリクエストごとに異なる機能を見ることになり、UXが悪化します。
カナリアリリースの実装
カナリアリリースでは、まず少数のユーザー(カナリア群)に新機能を公開し、問題がなければ徐々に対象を広げていきます。
from datetime import datetime, timedelta
class CanaryRelease:
def __init__(self, feature_name: str, stages: list):
"""
stages: [
{"percentage": 1, "duration_hours": 2},
{"percentage": 10, "duration_hours": 4},
{"percentage": 50, "duration_hours": 24},
{"percentage": 100, "duration_hours": None}
]
"""
self.feature_name = feature_name
self.stages = stages
self.start_time = datetime.now()
def get_current_percentage(self) -> int:
elapsed = datetime.now() - self.start_time
cumulative_hours = 0
for stage in self.stages:
if stage["duration_hours"] is None:
return stage["percentage"]
cumulative_hours += stage["duration_hours"]
if elapsed < timedelta(hours=cumulative_hours):
return stage["percentage"]
return self.stages[-1]["percentage"]
私のプロジェクトでは、1% → 10% → 50% → 100%という4段階のカナリアリリースを標準としています。各段階で最低2時間は様子を見て、エラー率やレイテンシに異常がないことを確認してから次の段階に進みます。達人プログラマーでも強調されていますが、「小さく試して、問題があれば素早く戻す」という姿勢が重要です。

まとめ
Feature Flagは、本番環境での安全なリリース管理を実現するための強力なツールです。
- デプロイと機能公開を分離することで、問題発生時の対応が劇的に速くなる
- 段階的ロールアウトにより、リスクを最小化しながら新機能を展開できる
- フラグの種類(リリース/実験/運用/権限)を理解し、目的に応じて使い分ける
- フラグの削除タイミングを決めておき、技術的負債の蓄積を防ぐ
まずは環境変数やYAMLファイルでのシンプルな実装から始めて、必要に応じてLaunchDarklyやUnleashなどの専用ツールへ移行するのがおすすめです。小さく始めて、チームの運用に合わせて育てていきましょう。










