
Feature Flag による段階的リリース実践ガイド
「新機能をリリースしたら本番で予期しないエラーが…」「全ユーザーに影響が出る前に戻したい」。こうした事態を避けるため、Feature Flag(機能フラグ) による段階的リリースが注目されています。
本記事では、受託開発・自社開発の現場で即使える Feature Flag の実装方法を、ツール比較・実装パターン・運用設計まで具体例とともに解説します。
1. Feature Flag とは何か
1-1. 基本概念
Feature Flag(フィーチャーフラグ)は、コードをデプロイせずに機能の ON/OFF を切り替える仕組みです。
従来のリリース
- コード変更 → デプロイ → 全ユーザーに即座に公開
- 問題発生時は再デプロイが必要
Feature Flag を使ったリリース
- コードは先にデプロイ(フラグは OFF)
- 管理画面でフラグを ON にして段階的に公開
- 問題発生時はフラグを OFF にするだけ(即座にロールバック)
1-2. 主なユースケース
| ユースケース | 説明 | 適用場面 | |------------|------|--------| | カナリアリリース | 一部ユーザーに先行公開してリスクを検証 | 大規模機能の本番投入前 | | A/Bテスト | 異なるUIや機能を比較して効果測定 | 新UI/UX の検証 | | 段階的ロールアウト | 1% → 10% → 50% → 100% と徐々に公開範囲を拡大 | リスクを最小化したい重要機能 | | Kill Switch | 問題発生時に即座に機能を無効化 | 決済・外部API連携など | | 環境別有効化 | 開発/ステージング環境でのみ機能を有効化 | 開発中機能の検証 |
2. Feature Flag サービスの比較
2-1. 主要サービスの特徴
| サービス | 料金 | 主な特徴 | 適した規模 | |---------|------|---------|----------| | LaunchDarkly | $10/seat〜 | 大規模向け、豊富な機能、SDKが充実 | エンタープライズ | | Unleash | OSS/有料 | セルフホスト可能、コスト抑制 | 中小〜大規模 | | Flagsmith | OSS/有料 | UI/UXが優れている、手軽に開始 | スタートアップ〜中規模 | | PostHog | OSS/有料 | アナリティクス統合、A/Bテスト機能 | プロダクト分析も必要な場合 | | 自前実装 | 無料(開発コスト) | 完全カスタマイズ可能 | 小規模MVP、学習目的 |
2-2. 選定基準チェックリスト
必須要件
- [ ] SDK が使用言語・フレームワークに対応しているか
- [ ] 段階的ロールアウト(パーセンテージベース)をサポートしているか
- [ ] 管理画面で非エンジニアも操作可能か
- [ ] 監査ログ・変更履歴が記録されるか
推奨要件
- [ ] ターゲティング(ユーザー属性による出し分け)が可能か
- [ ] A/Bテスト機能があるか
- [ ] 既存の分析ツール(GA4、Amplitudeなど)と連携できるか
- [ ] セルフホストできるか(セキュリティ要件が厳しい場合)
3. 実装パターン:Next.js での例
3-1. 環境構築
本記事では OSS の Unleash を例に実装を進めます。
# Unleash サーバーを Docker で起動
docker run -d \
-p 4242:4242 \
-e DATABASE_URL=postgres://unleash:password@postgres/unleash \
unleashorg/unleash-server:latest
# Next.js プロジェクトに SDK をインストール
npm install @unleash/nextjs @unleash/proxy-client-react
3-2. サーバーサイドでのフラグ評価
// app/api/unleash/route.ts
import { getDefinitions } from '@unleash/nextjs';
export async function GET() {
const definitions = await getDefinitions({
unleashUrl: process.env.UNLEASH_SERVER_URL!,
unleashApiToken: process.env.UNLEASH_API_TOKEN!,
appName: 'my-app',
});
return Response.json(definitions);
}
// app/products/page.tsx (Server Component)
import { evaluateFlags } from '@unleash/nextjs';
export default async function ProductsPage() {
const flags = await evaluateFlags({
unleashUrl: process.env.UNLEASH_SERVER_URL!,
unleashApiToken: process.env.UNLEASH_API_TOKEN!,
appName: 'my-app',
context: {
userId: 'anonymous',
},
});
const showNewProductUI = flags.isEnabled('new-product-ui');
if (showNewProductUI) {
return <NewProductList />;
}
return <LegacyProductList />;
}
3-3. クライアントサイドでのフラグ評価
// app/providers.tsx
'use client';
import { FlagProvider } from '@unleash/proxy-client-react';
const config = {
url: '/api/unleash',
clientKey: process.env.NEXT_PUBLIC_UNLEASH_CLIENT_KEY!,
refreshInterval: 30, // 30秒ごとにフラグを再取得
appName: 'my-app',
};
export function Providers({ children }: { children: React.ReactNode }) {
return (
<FlagProvider config={config}>
{children}
</FlagProvider>
);
}
// app/checkout/page.tsx (Client Component)
'use client';
import { useFlag } from '@unleash/proxy-client-react';
export default function CheckoutPage() {
const showNewCheckout = useFlag('new-checkout-flow');
if (showNewCheckout) {
return <NewCheckoutFlow />;
}
return <LegacyCheckoutFlow />;
}
3-4. ユーザー属性によるターゲティング
// 特定のユーザーにだけ新機能を公開
const flags = await evaluateFlags({
unleashUrl: process.env.UNLEASH_SERVER_URL!,
unleashApiToken: process.env.UNLEASH_API_TOKEN!,
appName: 'my-app',
context: {
userId: session.user.id,
properties: {
email: session.user.email,
plan: session.user.plan, // 'free' | 'pro' | 'enterprise'
betaTester: session.user.betaTester,
},
},
});
const showPremiumFeature = flags.isEnabled('premium-analytics');
Unleash 管理画面での設定例
- Strategy:
userWithId(特定ユーザーのみ) - Strategy:
gradualRollout(10%のユーザーに公開) - Strategy:
flexibleRollout+ Constraint(plan = 'pro'のユーザーのみ)
4. 段階的ロールアウトの実践手順
4-1. フェーズ設計
| フェーズ | 公開範囲 | 期間 | 判断基準 | |---------|---------|------|--------| | 1. 内部テスト | 開発チーム(5名) | 1日 | 致命的なバグがないか | | 2. ベータユーザー | ベータテスター(50名) | 3日 | エラー率 < 0.1%、主要機能が動作 | | 3. カナリア | 全体の 5% | 2日 | エラー率・パフォーマンス監視 | | 4. 段階拡大 | 10% → 25% → 50% | 各2日 | ビジネス指標(CVRなど)を確認 | | 5. 全体公開 | 100% | - | 問題なければ完全移行 |
4-2. 監視指標の設定
// フラグ評価時にメトリクスを送信
import { track } from '@/lib/analytics';
const flags = await evaluateFlags(context);
const showNewUI = flags.isEnabled('new-ui');
track('feature_flag_evaluated', {
flagName: 'new-ui',
enabled: showNewUI,
userId: context.userId,
timestamp: new Date().toISOString(),
});
監視すべき指標
- エラー率(フラグ ON グループ vs OFF グループ)
- レスポンスタイム(P50、P95、P99)
- ビジネス指標(CVR、離脱率、DAUなど)
- ユーザーフィードバック(サポート問い合わせ数)
4-3. ロールバック手順
# 問題発生時の緊急対応フロー
# 1. Unleash 管理画面でフラグを即座に OFF
# → 全ユーザーが旧機能に戻る(数秒〜数十秒)
# 2. エラーログ・メトリクスを確認
# → どの条件で問題が発生したかを特定
# 3. 修正版をデプロイ
# → フラグは OFF のまま(影響なし)
# 4. 内部テストで検証後、再度段階的にロールアウト
5. A/Bテストの実装
5-1. バリアント(Variant)の定義
// Unleash でバリアントを設定
const flags = await evaluateFlags(context);
const variant = flags.getVariant('checkout-button-color');
const buttonColor = {
blue: '#3B82F6',
green: '#10B981',
red: '#EF4444',
}[variant.name] || '#3B82F6'; // デフォルトは青
return (
<button
style={{ backgroundColor: buttonColor }}
onClick={handleCheckout}
>
購入する
</button>
);
5-2. 結果の計測
// app/api/track/route.ts
import { NextRequest } from 'next/server';
import { db } from '@/lib/db';
export async function POST(req: NextRequest) {
const { event, userId, variant, properties } = await req.json();
await db.insert('analytics_events').values({
event_name: event,
user_id: userId,
variant_name: variant,
properties: properties,
created_at: new Date(),
});
return Response.json({ success: true });
}
-- 購入率の比較クエリ
SELECT
variant_name,
COUNT(DISTINCT CASE WHEN event_name = 'checkout_viewed' THEN user_id END) AS views,
COUNT(DISTINCT CASE WHEN event_name = 'purchase_completed' THEN user_id END) AS conversions,
ROUND(
100.0 * COUNT(DISTINCT CASE WHEN event_name = 'purchase_completed' THEN user_id END) /
NULLIF(COUNT(DISTINCT CASE WHEN event_name = 'checkout_viewed' THEN user_id END), 0),
2
) AS conversion_rate
FROM analytics_events
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY variant_name;
6. 運用設計のポイント
6-1. フラグのライフサイクル管理
| フェーズ | 期間目安 | 対応 | |---------|---------|------| | 開発中 | 1〜2週間 | フラグ OFF、開発環境でのみ ON | | ロールアウト中 | 1〜2週間 | 段階的に公開範囲を拡大 | | 安定稼働 | 2週間 | 100% 公開、問題なければ次フェーズへ | | クリーンアップ | - | フラグを削除、コードから条件分岐を除去 |
重要:古いフラグの削除
// ❌ 悪い例:フラグが残り続ける
if (flags.isEnabled('new-ui-2023')) {
// 2年前の実験が残ったまま…
}
// ✅ 良い例:定期的にクリーンアップ
// 1. Unleash で使用されていないフラグを確認
// 2. コードから該当の条件分岐を削除
// 3. Unleash からフラグを削除
6-2. チームでの運用ルール
フラグ命名規則
// ✅ 良い例
'checkout-redesign-2024-q2' // 機能名-年度-四半期
'payment-provider-stripe' // 機能名-具体的な識別子
'experiment-button-color-ab' // 実験であることを明示
// ❌ 悪い例
'new-feature' // 何の機能か不明
'test123' // 一時的なテスト用?
'flag1' // 意味不明
ドキュメント化テンプレート
## フラグ名: `checkout-redesign-2024-q2`
### 目的
決済フローを簡素化し、カート放棄率を 20% 削減する
### 対象ユーザー
- フェーズ1: ベータテスター(betaTester = true)
- フェーズ2: 全ユーザーの 5% → 10% → 50% → 100%
### 監視指標
- 決済完了率(目標: 70% 以上)
- エラー率(許容: 0.1% 以下)
- P95 レスポンスタイム(許容: 1秒以内)
### ロールバック基準
- 決済完了率が 60% を下回る
- エラー率が 0.5% を超える
- 決済関連の問い合わせが急増
### クリーンアップ予定
2024年8月末(安定稼働後1ヶ月)
6-3. セキュリティ考慮事項
フラグ情報の扱い
// ❌ 悪い例:機密情報をフラグ名に含める
'premium-pricing-50percent-off' // 割引率が露出
'beta-feature-credit-card-token' // 機密用語
// ✅ 良い例:抽象的な名前にする
'premium-pricing-experiment'
'payment-method-alternative'
API トークンの管理
# 環境変数で管理(.env.local)
UNLEASH_SERVER_URL=https://unleash.example.com
UNLEASH_API_TOKEN=*:production.xxxxxxxxxxxxx
NEXT_PUBLIC_UNLEASH_CLIENT_KEY=your-client-key
# .env.local は .gitignore に追加
echo ".env.local" >> .gitignore
7. トラブルシューティング
7-1. よくある問題と対処法
| 問題 | 原因 | 対処法 |
|------|------|--------|
| フラグの変更が反映されない | キャッシュが残っている | refreshInterval を短くする、手動で updateContext() を呼ぶ |
| ユーザーごとに挙動がバラバラ | userId が毎回変わる | セッション ID を使う、Cookie に保存 |
| A/Bテストで片方に偏る | バリアント配分が不均等 | Unleash で stickiness を userId に設定 |
| 本番で意図しない機能が有効 | 環境別設定の誤り | 環境ごとに Unleash プロジェクトを分ける |
7-2. デバッグ用コード
// 開発環境でフラグの状態を確認
if (process.env.NODE_ENV === 'development') {
console.table({
'new-ui': flags.isEnabled('new-ui'),
'premium-feature': flags.isEnabled('premium-feature'),
'experiment-variant': flags.getVariant('experiment').name,
});
}
まとめ
Feature Flag による段階的リリースは、本番環境でのリスクを最小化しながら、迅速に価値を届けるための強力な手法です。
本記事で解説した内容
- Feature Flag の基本概念とユースケース
- LaunchDarkly・Unleash などのサービス比較
- Next.js での実装パターン(Server/Client Components)
- 段階的ロールアウトの実践手順
- A/Bテストの実装と計測
- 運用設計(フラグのライフサイクル、命名規則、セキュリティ)
- トラブルシューティング
導入のステップ
- 小さく始める:1つの機能でフラグを試す
- 監視体制を整える:エラー率・パフォーマンスを計測
- チームで運用ルールを決める:命名規則・クリーンアップ基準
- 段階的に適用範囲を広げる:重要な機能から順に
Feature Flag は、技術的負債になりやすい側面もあります。定期的なクリーンアップと明確な運用ルールを設けることで、健全な開発サイクルを維持できます。
Yureate では、Feature Flag を活用した安全なリリース戦略の設計・実装を支援しています。 段階的ロールアウトの導入や、A/Bテスト基盤の構築でお困りの際は、お気軽にご相談ください。
