トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

FeatureFlagの運用パターン -トレタのテックトーク

こんにちは、トレタ VPoEの北川です。 9月に入りましたが残暑が厳しすぎます…

テックトークとは

隔週に一回開催し、当番の発表者が最近の気になる技術情報であったり、業務する中での技術的な学びを社内に発信する場です。 全体30分の前半はLT(ライトニングトーク)、後半はその内容について参加者内で議論、という形式で行なっています。

今回のテックトーク

今回の発表は自分、北川の担当でした。

先日行なったエンジニアイベントでの質問の中にFeatureFlagをどう運用しているのか、という質問をいくつかいただいたので、今回の発表では弊社内でのFeatureFlagの使い方について発表しました。

FeatureFlagと一口にいってもその用途・使い方は様々なので、まずはそれらを整理しながら社内のいろいろなプロジェクト内での使われ方を分類して紹介しています。

FeatureFlagとは

まずFeatureFlag(フィーチャーフラグ)とは、特定の機能を動的にオン・オフするための仕組みです。アジャイル開発や継続的デリバリー(Continuous Delivery)のプラクティスとして、頻繁に機能をリリースしながらシステムの安定性を保つために、Facebook、Googleなどの企業で利用されてきた手法です。

FeatureFlagを使うユースケース

FeatureFlagを使う目的は様々です。いろいろなユースケース、目的を達成するための手段としてFeatureFlagが利用されます。一般的な利用目的を以下にまとめてみました。

  • 段階的リリース(Gradual Rollout) 新機能を一部のユーザーにのみ提供し、問題がないことを確認しながら徐々に全ユーザーに展開する。これにより、システムの安定性を保ちつつ、フィードバックを受けて調整することが可能です。

  • A/Bテスト 異なるバージョンの機能をユーザーごとに切り替えて提供し、どちらがより効果的かを比較するテストに利用できます。例えば、異なるUIデザインを一部のユーザーに試し、コンバージョン率を測定する場合などです。

  • カナリアリリース(Canary Release) 新機能をまず小規模なユーザーグループに提供し、そのパフォーマンスや安定性を観察して問題がないことを確認した後、全ユーザーにリリースする手法です。

  • リスク軽減 重要な機能を導入する際に、万が一不具合が発生した場合でも、即座にその機能を無効化できるようにして、システム全体に悪影響を与えるリスクを軽減します。

  • 特定のユーザー層への限定機能提供 プレミアムユーザーやベータテスターなど、特定のユーザー層だけに新しい機能を提供することができます。これにより、ユーザーに応じたカスタマイズされた体験を提供できます。

  • デプロイとリリースの分離 新機能のコードをあらかじめデプロイしておき、特定のタイミングで機能を有効化することで、デプロイとリリースを分離し、リリースのタイミングをより柔軟に管理することができます。

FeatureFlagの導入パターン

次に、特定の目的にFeatureFlagを使って解決しようとした際に、FeatureFlagの導入パターンも複数あります。こちらも一般的なパターンを以下にまとめてみました。

それぞれのパターンにはPros&Consがあり、どのパターンが適しているかは目的やサービスの規模、システムの非機能要件などによって変わってきます。

1. コード埋め込みパターン

概要

FeatureFlagのロジックをコード内に直接埋め込むパターンです。条件分岐を使用して、特定の機能を有効化または無効化します。

特徴

  • シンプルで導入が容易。
  • 開発者が直接コード内でフラグを制御できる。
  • 設定の変更にはコードの修正と再デプロイが必要。
  • 設定はコード内にあるため、外部依存がない。
  • 大規模なシステムではフラグが増えると管理が複雑になる。

ユースケース

  • 小規模なプロジェクトや、一度しか使用しない一時的なフラグ。

2. 環境変数パターン

概要

FeatureFlagの状態を環境変数として管理するパターンです。環境ごとに異なる値を設定でき、コードから環境変数を参照して機能を制御します。

特徴

  • デプロイ時に設定を変更可能。
  • 環境ごとに異なる設定を簡単に持てる(開発環境、本番環境など)。
  • 一時的なフラグなら良いが、長期間のフラグ管理には向いていない。

ユースケース

  • デプロイごとに簡単に設定を変更する必要がある場合。
  • 環境に応じて異なる設定を適用したい場合。

3. データベース連携パターン

概要

FeatureFlagの状態をデータベースに保存し、アプリケーションがその情報を参照してフラグを評価するパターンです。フラグの状態はデータベースから動的に取得されます。

特徴

  • フラグの状態を永続化できる。
  • フラグの状態変更が即座に反映され、アプリケーションの再デプロイが不要。
  • データベースに保存するため、設定変更の柔軟性が高い。
  • フラグの変更履歴や状態の追跡が可能。
  • 高度なフィーチャーフラグの管理が可能(例: ユーザーごとのカスタマイズ)。
  • データベースのパフォーマンスに依存するため、アクセス頻度が高い場合は最適化が必要。

ユースケース

  • ユーザーごとの個別設定や、複雑なフラグ管理が必要な大規模なアプリケーション。
  • フィーチャーフラグの状態や履歴を保持したい場合。

4. 分離パターン

概要

FeatureFlagを管理するコンポーネントを、アプリケーションコードとは別に管理するパターンです。設定ファイルや専用のコンフィグ管理システムを使ってフラグを管理します。

特徴

  • 設定ファイルを用いてフラグの状態を管理。
  • フラグはアプリケーションの外部に保存され、アプリケーションはその状態を参照して機能を制御。
  • コードベースから設定を切り離せるため、変更が柔軟。
  • 変更が即座に反映される。
  • 複数のアプリケーションやサービス間で統一したフラグ管理が可能。
  • 外部設定を管理するためのインフラが必要。
  • 複雑な設定管理が必要になることがある。

ユースケース

  • 大規模なシステムで、複数のアプリケーションやサービス間で統一的なFeatureFlag管理が必要な場合。
  • 設定変更を頻繁に行いたいが、アプリケーションの再デプロイを避けたい場合。

5. 外部サービスパターン

概要

FeatureFlag管理の外部サービス(LaunchDarkly、Flagsmith、Split.ioなど)を利用するパターンです。フラグの状態は外部サービス上で管理され、アプリケーションはAPIを通じてその状態を取得します。

特徴

  • 外部サービスがFeatureFlagの状態を一元管理。
  • 各種プラットフォームでフラグを統合管理可能。
  • フラグ管理に特化したツールを使用するため、スケーラビリティが高い。
  • リアルタイムでフラグの変更が反映され、ユーザーごとの柔軟な制御が可能。
  • 分析ツールやA/Bテスト機能が組み込まれていることが多い。
  • 外部サービスへの依存が生じ、サービス障害や遅延が発生するリスクがある。
  • コストがかかる(特に大規模なアプリケーションでは料金が高くなることがある)。

ユースケース

  • 大規模なマイクロサービスアーキテクチャや、複数のプラットフォームでFeatureFlagを統一的に管理する必要がある場合。
  • FeatureFlagの運用管理や分析を外部に委託したい場合

トレタでのFeatureFlagを使っている実例

さて、弊社でのFeatureFlagの導入事例についての紹介です。

上記でまとめたパターンごとに紹介していきます。

コード埋め込みパターンの例

{
  "USE_CALENDAR": true,
  "IS_FULLSCREEN": false,
}

弊社では設定画面のプロダクトにてコード埋め込みパターンでのFeatureFlag運用を行っています。 FeatureFlagを使う目的としては、クライアントアプリ向けのAPIとのその設定画面のリリースタイミングをずらすために利用しています。

新機能を出す際に、まずはAPIを先行してリリースし、利用するクライアントアプリ側が対応の後に設定画面をリリース、という流れとなります。 切り替えを行うのはデプロイを伴っても構わないため、一番簡易な手段としてコード埋め込みでFeatureFlagを扱っています。

環境変数パターンの例

export const isExtraColorPattern = (organizationId: OrganizationId): boolean => {
  const organizationIdsEnv = process.env.EXTRA_COLOR_PATTERN_ORGANIZATION_IDS;
  if (!organizationIdsEnv) {
    throw new ProcessEnvNotFoundError(500, "EXTRA_COLOR_PATTERN environment not set");
  }
  const organizationIds = organizationIdsEnv.split(",");
  return organizationIds.includes(organizationId);
};

上記のコードは、組織が特定のIDの場合にのみ特別なカラーパターンを利用できるようにするための判定式です。 利用可能な組織IDをEXTRA_COLOR_PATTERN_ORGANIZATION_IDS というkeyの環境変数に設定しています。 環境変数なので文字列しか設定できないため、カンマ区切りで配列を表現しています。

PoCであったり個社向けの対応に利用する用途なので、値の変更頻度は高くないため値の変更時にデプロイが必要になっても問題にはなりません。

データベース連携型の例

type Item {
  id: ID!
  name: Name!
  createdAt: Timestamp!
  updatedAt: Timestamp!
  ...
  metadata: KV  
}

データベースにFeatureFlagを設定する場合にはmetadata といったようなプロパティ名のKey-Value型のフィールドを用意して利用しています。 metadata のkeyの定義はDBスキーマ上は行っていないので必要応じてFeatureFlagのkeyと値を自由に追加・削除できます。

こちらは値の変更時にデプロイが不要なため、上記の環境変数パターンに比べて変更頻度が高い場合に利用しています

分離型の例

{
  "organiaztionIds": [
    "clilazd3oidfx0mm2akbmf1dj3",
    "cl3d3jpoklpx9jlkmlass31smpo"
 }

こちらは分離型として、静的ファイルに分離するという手法を取りました。

Githubで専用リポジトリを作り、/public にjsonファイルを配置しており、Vercelでプロジェクトを配信することで運用しています。 構成としてはあまり手間がかからないのと、Githubで管理されているため変更履歴が残るのとブランチを切り替えることで本番用と開発用で分けられるのが良いところです。

用途としては、リプレイスを行なったシステムに対しての移行をフラグ管理しています。 移行が終わっていない組織のIDをリストに入れておき、該当する組織は各アプリケーションではリプレイス前のシステムを利用します。 移行が済んだ組織はリストから外していき、リストが空になったらこの仕組みごと削除する、という流れになります。

複数のアプリケーションがその組織の移行状況を確認する必要があったため特定のアプリケーションではない場所で管理したかったのと、 最終的には不要になるため削除する時に後が残らず、また一覧性もあることが望ましかったため、この手法を取りました。

FeatureFlagを消すタイミング

FeatureFlagがそもそも永続化されるか一時的なものかは、パターンとあわせて利用するユースケースによります。 コード埋め込みパターンや環境変数パターンは比較的に短期利用で採用することが多いため、基本的には不要になった時点で削除を行います。

ただ、実際のところでゆうと消し忘れることは多いです。 フラグをオンにするタイミングで消すといいのですが、大体のケースはオンにして様子見てから消す、とするとそのまま放置されることが多いです。 この辺は運用ルールとして定めても守れらないと意味がないので、自動的に守られるような仕組みが必要ですが今のところそこまで至っていないのが現状です。

まとめ

長くなったので上記の内容のまとめです。 私見でのマトリクスを作ったので、FeatureFlagを使う際の判断の参考にぜひご活用ください。

  • FeatureFlagには、複数のユースケースやそれに適したアプローチがある。
  • どのアプローチでFeatureFlagを実装するかはユースケースに応じて、以下の観点で選択するとよい。
    • 外部依存度
      • レイテンシ
      • 障害点
    • 変更容易性
      • 変更にデプロイが必要でも問題ないか
    • 設定柔軟性
      • 環境にごとに切り替えたいか
      • 特定の店舗への設定か
    • 管理のしやすさ
      • 一覧性
      • 消しやすさ
コード埋め込み 環境変数型 データベース連携 分離 外部サービス
外部依存度 × ×
変更容易性 × ×
環境ごとの切り替え
設定柔軟性 ×
管理のしやすさ × ×
コスト ×
© Toreta, Inc.

Powered by Hatena Blog