フィーチャーフラグ設計 - 安全なリリース戦略

15分 で読める | 2025.01.10

公式ドキュメント

フィーチャーフラグとは

フィーチャーフラグ(機能フラグ)は、コードをデプロイした後でも機能のオン/オフを切り替えられる仕組みです。リスクを最小化しながら新機能をリリースできます

flowchart TB
 subgraph Deploy["デプロイ済みコード"]
 Code["新機能コード"]
 Flag["フラグ判定"]
 Code --> Flag
 end

 Flag -->|ON| NewFeature["新機能表示"]
 Flag -->|OFF| OldFeature["既存機能表示"]

 Config["フラグ設定<br/>(管理画面)"] -.->|制御| Flag

フラグの種類

種類用途寿命
リリースフラグ新機能の段階的公開短期(数週間)
実験フラグA/Bテスト中期(数ヶ月)
運用フラグ機能の緊急停止長期
権限フラグユーザー種別による制御永続

実装パターン

シンプルな実装

// フラグ設定
const featureFlags = {
 newCheckout: true,
 darkMode: false,
 betaFeatures: process.env.NODE_ENV === 'development',
};

// 使用箇所
function CheckoutPage() {
 if (featureFlags.newCheckout) {
 return <NewCheckout />;
 }
 return <LegacyCheckout />;
}

コンテキストベース実装

interface FeatureFlagContext {
 userId: string;
 userRole: 'free' | 'premium' | 'enterprise';
 country: string;
 percentage?: number;
}

class FeatureFlagService {
 private flags: Map<string, FlagRule[]> = new Map();

 isEnabled(flagName: string, context: FeatureFlagContext): boolean {
 const rules = this.flags.get(flagName);
 if (!rules) return false;

 for (const rule of rules) {
 if (this.evaluateRule(rule, context)) {
 return true;
 }
 }
 return false;
 }

 private evaluateRule(rule: FlagRule, context: FeatureFlagContext): boolean {
 // ユーザーロールチェック
 if (rule.roles && !rule.roles.includes(context.userRole)) {
 return false;
 }

 // パーセンテージロールアウト
 if (rule.percentage !== undefined) {
 const hash = this.hashUserId(context.userId);
 return hash < rule.percentage;
 }

 return true;
 }

 private hashUserId(userId: string): number {
 // 0-100の一貫したハッシュ値を生成
 let hash = 0;
 for (let i = 0; i < userId.length; i++) {
 hash = ((hash << 5) - hash) + userId.charCodeAt(i);
 hash |= 0;
 }
 return Math.abs(hash) % 100;
 }
}

Reactでの使用

// フラグプロバイダー
const FeatureFlagContext = createContext<FeatureFlagService | null>(null);

export function useFeatureFlag(flagName: string): boolean {
 const service = useContext(FeatureFlagContext);
 const user = useCurrentUser();

 if (!service || !user) return false;

 return service.isEnabled(flagName, {
 userId: user.id,
 userRole: user.role,
 country: user.country,
 });
}

// コンポーネントで使用
function Dashboard() {
 const showNewAnalytics = useFeatureFlag('new-analytics');

 return (
 <div>
 {showNewAnalytics ? <NewAnalytics /> : <LegacyAnalytics />}
 </div>
 );
}

ポイント: 段階的ロールアウトでは、まず社内ユーザー(dogfooding)→ ベータユーザー → 一般ユーザーの順に展開し、各段階でメトリクスを確認します。

段階的ロールアウト

// 1% → 5% → 25% → 50% → 100% と段階的に公開
const rolloutConfig = {
 'new-payment': {
 stages: [
 { percentage: 1, startDate: '2025-01-10' },
 { percentage: 5, startDate: '2025-01-12' },
 { percentage: 25, startDate: '2025-01-15' },
 { percentage: 50, startDate: '2025-01-20' },
 { percentage: 100, startDate: '2025-01-25' },
 ],
 },
};

注意: 不要になったフィーチャーフラグを放置すると技術的負債になります。フラグには必ず有効期限を設定し、リリース完了後は速やかに削除しましょう。

実践メモ: フラグの命名規則を統一しましょう。feature_プレフィックスと説明的な名前(例: feature_new_checkout_flow)を使うと管理しやすくなります。

運用ベストプラクティス

1. フラグの命名規則

// 良い例
'checkout-new-payment-form'
'experiment-pricing-page-v2'
'ops-maintenance-mode'

// 悪い例
'flag1'
'new_feature'
'test'

2. 技術的負債の管理

// フラグに期限を設定
interface FeatureFlag {
 name: string;
 enabled: boolean;
 expiresAt: Date; // 期限切れアラート
 owner: string; // 責任者
}

// 定期的なクリーンアップ
function auditFlags(flags: FeatureFlag[]) {
 const expiredFlags = flags.filter(f => f.expiresAt < new Date());
 if (expiredFlags.length > 0) {
 notifyOwners(expiredFlags);
 }
}

3. 緊急停止(キルスイッチ)

// 即座に機能を停止できる仕組み
async function killSwitch(flagName: string) {
 await flagService.disable(flagName);
 await invalidateCache(flagName);
 await notifyTeam(`${flagName} disabled via kill switch`);
}

現場での失敗例と対策

  1. フラグの削除忘れ → 期限設定と定期レビュー
  2. テスト環境との不整合 → 環境別設定の明確化
  3. パフォーマンス低下 → フラグ評価のキャッシュ

関連記事

まとめ

フィーチャーフラグは、リリースリスクを軽減する強力なツールです。適切な設計と運用ルールを設けることで、安全かつ高速なリリースサイクルを実現できます

参考リソース

← 一覧に戻る
PR
PR
PR
PR