読者が抱える課題
新機能のリリース時、本番環境にデプロイした直後に予期せぬバグが発生し、システム全体をロールバックせざるを得なくなった経験はないでしょうか。また、特定のユーザーグループだけに先行して機能を公開したい場合や、コードのデプロイと機能の有効化(リリース)のタイミングを分離したいという課題を抱える開発チームは少なくありません。
この記事で分かること
- Feature Flag(機能フラグ)を導入するメリットと基本的な設計思想
- TypeScript/Node.js を用いたシンプルなFeature Flagの実装例
- 段階的リリース(カナリアリリース)やユーザー属性による制御方法
- 運用フェーズにおけるフラグの管理・削除のチェックリスト
対象読者・前提条件
- 実務でWebアプリケーションの開発・運用に携わっているエンジニア
- デプロイの頻度を上げつつ、本番障害のリスクを最小限に抑えたい方
- ※本記事の実装例は TypeScript / Node.js を想定していますが、考え方は他の言語やフレームワークでも応用可能です。
Feature Flagの基本設計と「良い例・悪い例」
Feature Flagは、コードを変更することなくシステムの挙動を動的に切り替えるための仕組みです。しかし、無計画に導入するとコードの可読性が低下し、技術負債の温床になります。
悪い設計例
// 悪い例: 条件分岐がネストし、フラグの用途が不明瞭
if (flags.newFeature) {
if (flags.betaUsers && user.isBeta) {
executeNewFeatureV2();
} else {
executeNewFeatureV1();
}
} else {
executeOldFeature();
}
良い設計例
// 良い例: インターフェースの裏側にフラグ判定を閉じ込め、呼び出し側をシンプルに保つ
const featureExecutor = FeatureFactory.create(user, flags);
featureExecutor.execute();
具体的な実装例:ユーザー属性に応じた段階的公開
以下は、ユーザーIDのハッシュ値を利用して、特定の割合(例: 20%)のユーザーにのみ新機能を段階的に公開するシンプルな実装例です。外部のFeature Flagサービス(LaunchDarklyやSplitなど)を導入せず、自前でロジックを構築する場合の基礎となります。
※このコードは動作確認用の簡易実装です。実際のプロダクション環境に導入する際は、要件に合わせてエラーハンドリングやテストを追加してください。
import * as crypto from 'crypto';
interface User {
id: string;
email: string;
}
interface FlagConfig {
enabled: boolean;
rolloutPercentage: number; // 0 ~ 100
internalEmails: string[];
}
export class FeatureFlagEvaluator {
/**
* ユーザーが機能の対象であるかを判定する
*/
public static isEnabledForUser(user: User, config: FlagConfig): boolean {
// 1. フラグ自体が無効化されている場合は即座にfalse
if (!config.enabled) {
return false;
}
// 2. 社内ユーザーなどの特定リストに含まれる場合は先行公開
if (config.internalEmails.includes(user.email)) {
return true;
}
// 3. ユーザーIDのハッシュ値を用いて一貫性のある段階的公開を行う
const bucket = this.getUserBucket(user.id);
return bucket < config.rolloutPercentage;
}
/**
* ユーザーIDから0〜99のバケット値を生成する(同一IDは常に同じ値を返す)
*/
private static getUserBucket(userId: string): number {
const hash = crypto.createHash('sha256').update(userId).digest('hex');
// ハッシュ値の先頭8文字を数値に変換し、100の剰余を取る
const intValue = parseInt(hash.substring(0, 8), 16);
return intValue % 100;
}
}
// --- 使用例 ---
const config: FlagConfig = {
enabled: true,
rolloutPercentage: 20, // 20%のユーザーに公開
internalEmails: ['tester@example.com']
};
const userA = { id: 'user-12345', email: 'userA@example.com' };
const tester = { id: 'user-99999', email: 'tester@example.com' };
console.log(`User A: ${FeatureFlagEvaluator.isEnabledForUser(userA, config)}`);
console.log(`Tester: ${FeatureFlagEvaluator.isEnabledForUser(tester, config)}`);
運用上の判断基準と移行フロー
Feature Flagを安全に本番環境へ適用するための一般的な移行プロセスです。
| フェーズ | 公開対象 | 目的・確認事項 |
|---|---|---|
| 1. 開発・検証 | 開発者・QAのみ | ステージング環境での動作確認、基本機能の検証 |
| 2. ドッグフーディング | 社内アカウント | 本番環境での実データを用いた検証、予期せぬエラーの検知 |
| 3. カナリアリリース | 一般ユーザーの1%〜5% | パフォーマンス監視、エラーレートの監視、インフラ負荷の確認 |
| 4. 段階的拡大 | 10% → 50% → 100% | 段階的に公開範囲を広げ、問題がなければ全公開へ |
実務用:Feature Flag運用チェックリスト
Feature Flagは「作成すること」よりも「安全に削除すること」の方が難易度が高い傾向にあります。以下のチェックリストを運用ルールに組み込むことで、コードの形骸化を防ぎます。
1. フラグ作成時のチェックリスト
-
フラグの命名規則が統一されているか(例:
temp_202411_new_checkout_flowのように、一時的フラグであることが分かる接頭辞や日付を含める) - フラグの「寿命(ライフサイクル)」が定義されているか(例: 全公開後2週間以内に削除する)
- フラグのデフォルト値(フォールバック値)が安全に設定されているか(外部サービスがダウンした場合にシステムが停止しない設計になっているか)
2. リリース・監視時のチェックリスト
- フラグの切り替え手順書が用意されているか(誰が、どのタイミングで、どの値を変更するか)
- フラグ変更時の監視ダッシュボード(エラーレート、レイテンシ)が開かれているか
- 万が一の際の「即時ロールバック手順(フラグをOFFにする手順)」がチーム内で共有されているか
- クライアントサイド(フロントエンド)でフラグを扱う場合、不要な内部情報や未公開機能のロジックがソースコードから容易に解析されない対策がなされているか
3. クリーンアップ(削除)のチェックリスト
- 機能が100%公開され、安定稼働してから一定期間(例: 1〜2週間)が経過したか
- フラグを削除するためのリファクタリングタスク(チケット)がバックログに起票されているか
- コード内からフラグ判定ロジックおよび古いコードパスが完全に削除されたか
注意点とよくある失敗
1. 「一時的なフラグ」と「永続的なフラグ」の混同
新機能リリースのためのフラグ(一時的)と、システムのメンテナンスモードやマルチテナントの機能制限のためのフラグ(永続的)は、管理方法を明確に分ける必要があります。一時的なフラグは、役割を終えたら速やかに削除しなければ、コードベースが複雑化しバグの原因になります。
2. パフォーマンスへの影響
フラグの評価をリクエストごとに行う場合、外部のフラグ管理サーバーへの問い合わせがボトルネックになることがあります。インメモリキャッシュの活用や、ローカルでのルール評価が可能なSDKの選定を検討してください。
まとめ
Feature Flagは、デプロイとリリースのタイミングを分離し、本番環境でのリスクを最小限に抑えるための強力な手法です。しかし、その効果を最大限に発揮するためには、実装の工夫だけでなく「作成したフラグを確実に削除する」という運用ルールの徹底が不可欠です。本記事のコード例やチェックリストを参考に、安全なリリースサイクルを構築してください。
注意
Feature Flagの外部サービスやライブラリを導入する際は、最新の公式ドキュメントを参照し、API仕様やセキュリティ要件(特にクライアントサイドでのキーの露出など)を確認した上で実装してください。