LaunchDarkly
LaunchDarkly A/B分析 完全技術ガイド
目次
- はじめに
- LaunchDarklyの概要
- フィーチャーフラグの基本概念
- A/Bテストとエクスペリメンテーション
- アーキテクチャの全体像
- SDK統合とデータフロー
- メトリクスとイベント設計
- 統計エンジンと分析手法
- ターゲティングとセグメンテーション
- 実装パターンと具体例
- Relay Proxyとエンタープライズアーキテクチャ
- データパイプラインと外部連携
- セキュリティとガバナンス
- 運用のベストプラクティス
- トラブルシューティング
- まとめ
1. はじめに
1.1 本書の目的
本書は、LaunchDarklyのA/B分析(エクスペリメンテーション)機能を中心に、フィーチャーフラグプラットフォームとしてのLaunchDarklyの全体像を技術的に深く掘り下げた解説書である。単なる機能紹介にとどまらず、アーキテクチャ設計、統計手法、SDK統合、運用パターンまでを網羅し、エンジニアリングチームがLaunchDarklyを活用したデータドリブンな意思決定基盤を構築するための実践的な知識を提供する。
1.2 対象読者
- ソフトウェアエンジニア(バックエンド/フロントエンド)
- SRE / プラットフォームエンジニア
- プロダクトマネージャー(技術的背景を持つ方)
- データエンジニア / データサイエンティスト
- テクニカルリード / アーキテクト
1.3 フィーチャーフラグとA/Bテストの関係
フィーチャーフラグ(Feature Flag)は、コードのデプロイとリリースを分離する技術であり、A/Bテストはユーザーに異なるバリエーションを提示し、その影響を統計的に評価する手法である。LaunchDarklyはこの2つを統合し、フィーチャーフラグの基盤上でA/Bテストを実行できるプラットフォームを提供している。
従来のA/Bテストツール(Google Optimize、Optimizelyなど)が主にフロントエンドのUI変更に焦点を当てていたのに対し、LaunchDarklyはバックエンドロジック、アルゴリズム、インフラ構成の変更まで含めた広範なエクスペリメンテーションを可能にする点が大きな差別化要因である。
従来のA/Bテスト:
UI変更 → フロントエンドツール → コンバージョン測定
LaunchDarklyのアプローチ:
あらゆるコード変更 → フィーチャーフラグ → メトリクス測定 → 統計分析
2. LaunchDarklyの概要
2.1 プラットフォームの位置づけ
LaunchDarklyは、2014年に設立されたフィーチャーマネジメントプラットフォームのリーディングカンパニーである。単なるフィーチャーフラグのON/OFF管理にとどまらず、以下の包括的な機能を提供する。
| カテゴリ | 機能 | 説明 |
|---|---|---|
| フィーチャーマネジメント | フラグ管理 | ブール値、マルチバリエート、JSON型フラグ |
| ターゲティング | ユーザー属性ベースの配信制御 | |
| 段階的ロールアウト | パーセンテージベースの展開 | |
| エクスペリメンテーション | A/B/nテスト | 複数バリエーションの統計比較 |
| メトリクス管理 | カスタムイベント、ファネル分析 | |
| 統計エンジン | 逐次テスト、ベイズ推定 | |
| 運用 | 監査ログ | すべてのフラグ変更の追跡 |
| ワークフロー | 承認フロー、スケジュール変更 | |
| インテグレーション | Slack、Datadog、PagerDutyなど |
2.2 コアコンポーネント
LaunchDarklyのプラットフォームは、以下のコアコンポーネントで構成される。
+-------------------------------------------------------------------+
| LaunchDarkly Platform |
| |
| +------------------+ +------------------+ +------------------+ |
| | Flag Engine | | Experimentation | | Data Pipeline | |
| | | | Engine | | | |
| | - Evaluation | | - Allocation | | - Event Stream | |
| | - Targeting | | - Statistics | | - Analytics | |
| | - Scheduling | | - Metrics | | - Export | |
| +--------+---------+ +--------+---------+ +--------+---------+ |
| | | | |
| +--------v----------------------v----------------------v-------+ |
| | Unified Event Bus / Streaming Layer | |
| +--------------------------------------------------------------+ |
| | | | |
| +--------v---------+ +--------v---------+ +--------v--------+ |
| | Server-side SDK | | Client-side SDK | | Relay Proxy | |
| +------------------+ +------------------+ +-----------------+ |
+-------------------------------------------------------------------+
2.3 プロジェクトと環境の概念
LaunchDarklyでは、リソースを以下の階層で管理する。
Organization(組織)
└── Project(プロジェクト)
├── Environment: Production
├── Environment: Staging
├── Environment: Development
└── Environment: QA
各環境は独立したSDKキー、フラグ状態、メトリクスデータを持つ。エクスペリメンテーションも環境ごとに個別に実行・管理される。
設定例: プロジェクトと環境の構成
{
"project": {
"key": "payment-service",
"name": "Payment Service",
"environments": [
{
"key": "production",
"name": "Production",
"color": "FF0000",
"sdkKey": "sdk-prod-xxxxxxxx",
"mobileKey": "mob-prod-xxxxxxxx",
"clientSideId": "client-prod-xxxxxxxx"
},
{
"key": "staging",
"name": "Staging",
"color": "FFAA00",
"sdkKey": "sdk-stg-xxxxxxxx"
},
{
"key": "development",
"name": "Development",
"color": "00FF00",
"sdkKey": "sdk-dev-xxxxxxxx"
}
]
}
}
2.4 ライセンスモデルとエクスペリメンテーション
LaunchDarklyのエクスペリメンテーション機能は、Enterprise プラン以上で利用可能なアドオンとして提供される(2025年時点)。ライセンスモデルは以下の通り。
| プラン | フィーチャーフラグ | エクスペリメンテーション | 高度な分析 |
|---|---|---|---|
| Starter | 基本機能 | 非対応 | 非対応 |
| Pro | 全機能 | 基本機能 | 一部対応 |
| Enterprise | 全機能 | 全機能 | 全機能 |
エクスペリメンテーション機能の利用には、Monthly Experimentation Key(MEK)というメトリクスが使用され、実験に参加したユニークユーザー数に基づいて課金される。
3. フィーチャーフラグの基本概念
3.1 フラグの種類
LaunchDarklyは以下の4種類のフラグタイプを提供する。
3.1.1 ブールフラグ(Boolean Flag)
最もシンプルなON/OFF型のフラグ。機能の有効化/無効化に使用する。
{
"key": "enable-new-checkout",
"name": "Enable New Checkout Flow",
"kind": "boolean",
"variations": [
{ "value": true, "name": "Enabled", "description": "新しいチェックアウトフローを有効化" },
{ "value": false, "name": "Disabled", "description": "既存のチェックアウトフローを使用" }
],
"defaults": {
"onVariation": 0,
"offVariation": 1
}
}
3.1.2 マルチバリエートフラグ(Multivariate Flag)
3つ以上のバリエーションを持つフラグ。A/B/nテストに最適。
{
"key": "checkout-button-color",
"name": "Checkout Button Color Experiment",
"kind": "multivariate",
"variations": [
{ "value": "blue", "name": "Control - Blue" },
{ "value": "green", "name": "Variant A - Green" },
{ "value": "orange", "name": "Variant B - Orange" },
{ "value": "red", "name": "Variant C - Red" }
]
}
3.1.3 数値フラグ(Number Flag)
数値パラメータの最適化に使用する。
{
"key": "api-rate-limit",
"name": "API Rate Limit Experiment",
"kind": "number",
"variations": [
{ "value": 100, "name": "Conservative" },
{ "value": 500, "name": "Moderate" },
{ "value": 1000, "name": "Aggressive" }
]
}
3.1.4 JSONフラグ(JSON Flag)
複雑な設定オブジェクトをフラグとして管理する。
{
"key": "recommendation-config",
"name": "Recommendation Algorithm Config",
"kind": "json",
"variations": [
{
"value": {
"algorithm": "collaborative-filtering",
"numRecommendations": 10,
"minScore": 0.7,
"includePopular": true
},
"name": "Collaborative Filtering"
},
{
"value": {
"algorithm": "content-based",
"numRecommendations": 8,
"minScore": 0.5,
"includePopular": false
},
"name": "Content-Based"
}
]
}
3.2 フラグの評価ロジック
フラグの評価は以下の優先順位で行われる。
評価フロー:
1. フラグが OFF → offVariation を返す
2. 個別ターゲティング(ユーザー指定)に該当 → 指定されたバリエーションを返す
3. ターゲティングルール(条件ベース)に該当 → ルールに基づくバリエーションを返す
4. フォールスルー(デフォルト)→ デフォルトバリエーションを返す
評価フローの詳細図:
+------------------+
| Flag Request |
| (user + context) |
+--------+---------+
|
+--------v---------+
| Is Flag ON? |
+--------+---------+
| |
No Yes
| |
+--------v--+ +----v--------------+
| Return | | Check Individual |
| offVariation| | Targeting |
+------------+ +----+--------------+
| |
Match No Match
| |
+---------v--+ +----v--------------+
| Return | | Evaluate Rules |
| Specified | | (top to bottom) |
| Variation | +----+--------------+
+------------+ | |
Match No Match
| |
+---------v--+ +----v--------------+
| Return | | Return Fallthrough|
| Rule | | (default % |
| Variation | | rollout) |
+------------+ +-----------------+
3.3 コンテキスト(Context)
LaunchDarkly v6以降では、ユーザーの概念が「コンテキスト」に拡張された。これにより、ユーザーだけでなく、組織、デバイス、アプリケーションなど複数のエンティティに対してフラグ評価を行える。
{
"kind": "multi",
"user": {
"key": "user-123",
"name": "Taro Yamada",
"email": "taro@example.com",
"custom": {
"plan": "enterprise",
"company": "Acme Corp",
"signupDate": "2024-01-15"
}
},
"organization": {
"key": "org-456",
"name": "Acme Corp",
"custom": {
"industry": "technology",
"employeeCount": 500,
"tier": "premium"
}
},
"device": {
"key": "device-789",
"custom": {
"os": "iOS",
"version": "17.4",
"model": "iPhone 15 Pro"
}
}
}
4. A/Bテストとエクスペリメンテーション
4.1 エクスペリメンテーションの概要
LaunchDarklyのエクスペリメンテーション(Experimentation)は、フィーチャーフラグの上に構築されたA/Bテストフレームワークである。以下の特徴を持つ。
- フィーチャーフラグネイティブ: 既存のフラグ基盤上で実験を実行
- サーバーサイド対応: UIだけでなくバックエンドロジックの実験が可能
- 逐次テスト: 固定サンプルサイズ不要、リアルタイムで結果を評価
- 複数メトリクス同時評価: 1つの実験で複数のKPIを同時に追跡
- 自動サンプルサイズ計算: 統計的に有意な結果を得るための推奨サイズ
4.2 実験の構成要素
Experiment(実験)
├── Flag(フラグ): どのフラグを実験するか
│ └── Variations(バリエーション): 比較する選択肢
├── Metrics(メトリクス): 何を測定するか
│ ├── Primary Metric: 主要評価指標
│ └── Secondary Metrics: 副次評価指標
├── Audience(オーディエンス): 誰を対象にするか
│ ├── Traffic Allocation: トラフィック配分率
│ └── Targeting Rules: ターゲティング条件
└── Duration(期間): いつまで実行するか
├── Minimum Runtime: 最小実行期間
└── Stopping Criteria: 停止条件
4.3 実験のライフサイクル
Draft(下書き)
│
├── 実験設計、メトリクス設定、オーディエンス定義
│
v
Recording(記録中)
│
├── データ収集開始、バリエーション配信開始
│
v
Running / Analyzing(実行中 / 分析中)
│
├── 統計分析実行、有意差の検出
│
v
Winning Variation Identified(勝者バリエーション特定)
│
├── 統計的に有意な差を確認
│
v
Stopped(停止)
│
├── 勝者バリエーションを100%展開
│ または実験中止
│
v
Archived(アーカイブ)
│
└── 実験結果の保存、フラグのクリーンアップ
4.4 実験の種類
4.4.1 機能実験(Feature Experiment)
新機能のON/OFFによるビジネスインパクトを測定する。
実験名: "New Recommendation Engine Impact"
フラグ: enable-new-recommendations
バリエーション:
- Control (OFF): 既存のレコメンドエンジン
- Treatment (ON): 新しいMLベースレコメンドエンジン
メトリクス:
- Primary: click_through_rate(クリック率)
- Secondary: average_order_value(平均注文額)
- Secondary: page_load_time(ページ読み込み時間)
4.4.2 最適化実験(Optimization Experiment)
パラメータの最適値を見つける。
実験名: "Optimal Search Results Count"
フラグ: search-results-per-page
バリエーション:
- 10件: 従来の表示件数
- 20件: 中間オプション
- 50件: 大量表示オプション
メトリクス:
- Primary: search_satisfaction_score
- Secondary: time_on_page
- Secondary: bounce_rate
4.4.3 コンフィグ実験(Configuration Experiment)
システムの設定パラメータを最適化する。
実験名: "Cache TTL Optimization"
フラグ: cache-ttl-seconds
バリエーション:
- 300秒 (5分): 短いTTL
- 1800秒 (30分): 中間TTL
- 3600秒 (1時間): 長いTTL
メトリクス:
- Primary: cache_hit_rate
- Secondary: p99_response_time
- Secondary: origin_server_load
4.5 実験設定の具体例(API / UI)
LaunchDarkly API を使用した実験作成
curl -X POST \
'https://app.launchdarkly.com/api/v2/projects/payment-service/environments/production/experiments' \
-H 'Authorization: api-xxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"name": "Checkout Flow Optimization",
"description": "新しいチェックアウトフローのコンバージョン率への影響を測定",
"maintainerId": "user-id-xxx",
"key": "checkout-flow-optimization",
"iteration": {
"hypothesis": "ステップ数を削減したチェックアウトフローにより、コンバージョン率が5%以上向上する",
"canReshuffleTraffic": true,
"metrics": [
{ "key": "checkout-conversion-rate", "isGroup": false },
{ "key": "average-order-value", "isGroup": false },
{ "key": "checkout-abandonment-rate", "isGroup": false }
],
"primarySingleMetricKey": "checkout-conversion-rate",
"treatments": [
{
"name": "Control - Current Checkout",
"baseline": true,
"allocationPercent": "34",
"parameters": [
{ "flagKey": "checkout-flow-version", "variationId": "variation-current" }
]
},
{
"name": "Treatment A - Simplified",
"baseline": false,
"allocationPercent": "33",
"parameters": [
{ "flagKey": "checkout-flow-version", "variationId": "variation-simplified" }
]
},
{
"name": "Treatment B - One-Page",
"baseline": false,
"allocationPercent": "33",
"parameters": [
{ "flagKey": "checkout-flow-version", "variationId": "variation-one-page" }
]
}
],
"flags": {
"checkout-flow-version": {
"ruleId": "rule-id-xxx",
"flagConfigVersion": 12
}
},
"randomizationUnit": "user"
}
}'
5. アーキテクチャの全体像
5.1 システムアーキテクチャ
LaunchDarklyのアーキテクチャは、高可用性・低レイテンシを実現するために、以下の主要コンポーネントで構成されている。
+-----------------------------+
| LaunchDarkly Cloud |
| |
| +------------------------+ |
| | Management API | |
| | (REST API v2) | |
| +----------+-------------+ |
| | |
| +----------v-------------+ |
| | Flag Configuration | |
| | Store | |
| +----------+-------------+ |
| | |
| +----------v-------------+ |
| | Streaming Service | |
| | (Server-Sent Events) | |
| +----------+-------------+ |
| | |
| +----------v-------------+ |
| | Events Pipeline | |
| | (Ingestion/Analysis) | |
| +------------------------+ |
+--------------+--------------+
|
+---------------------------+---------------------------+
| | |
+---------v----------+ +-----------v----------+ +----------v----------+
| Server-side SDK | | Client-side SDK | | Relay Proxy |
| - Node.js | | - JavaScript | | - On-premise |
| - Python | | - React | | - Flag cache |
| - Java / Go | | - iOS / Android | | - Event proxy |
| - .NET / Ruby | | - Flutter / RN | | - HA deployment |
+--------------------+ +----------------------+ +---------------------+
5.2 ストリーミングアーキテクチャ
LaunchDarklyは、フラグ変更をリアルタイムでクライアントに配信するために、Server-Sent Events(SSE)ベースのストリーミングアーキテクチャを採用している。
フラグ変更時のデータフロー:
管理者がフラグを変更
|
v
Management API -- フラグ設定をストアに書き込み
|
v
Configuration Change Stream -- 変更を検知
|
v
SSE Streaming Service -- 接続中の全SDKにプッシュ
|
+--+--+
| | |
v v v
SDK1 SDK2 SDK3 -- 各SDKがローカルのフラグ状態を更新
| | |
v v v
即座にフラグ評価に反映(ミリ秒レベル)
ストリーミングとポーリングの比較:
| 特性 | ストリーミング (SSE) | ポーリング |
|---|---|---|
| レイテンシ | ミリ秒レベル | ポーリング間隔に依存(通常30秒) |
| 帯域使用量 | 変更時のみデータ送信 | 定期的にフル状態を取得 |
| 接続維持 | 長期接続 | リクエストごとに接続 |
| フォールバック | ポーリングにフォールバック | - |
| 推奨用途 | デフォルト | ネットワーク制約がある場合 |
5.3 フラグ評価のアーキテクチャ
サーバーサイドSDKでのフラグ評価は完全にローカルで行われる。これは、外部API呼び出しが不要であることを意味し、極めて低いレイテンシ(マイクロ秒レベル)を実現する。
サーバーサイドSDKのフラグ評価フロー:
+----------------------------------------------+
| Server-side SDK |
| |
| +----------------------------------------+ |
| | In-Memory Flag Store | |
| | Flag A: { rules: [...], fallthrough } | |
| | Flag B: { rules: [...], fallthrough } | |
| +------------------+---------------------+ |
| | |
| +------------------v---------------------+ |
| | Evaluation Engine | |
| | 1. Prerequisites Check | |
| | 2. Individual Targeting | |
| | 3. Rule Evaluation | |
| | 4. Percentage Rollout (Hash) | |
| | 5. Fallthrough | |
| +------------------+---------------------+ |
| | |
| +------------------v---------------------+ |
| | Event Buffer | |
| | Evaluation events -> batch送信 | |
| | (非同期、フラグ評価をブロックしない) | |
| +----------------------------------------+ |
+----------------------------------------------+
クライアントサイドSDKとの違い:
| 特性 | サーバーサイドSDK | クライアントサイドSDK |
|---|---|---|
| フラグデータ | 全フラグの完全データ | 特定ユーザー向けの評価済み値のみ |
| 評価場所 | SDK内(ローカル) | LaunchDarklyサーバー |
| セキュリティ | SDKキーは秘密 | クライアントIDは公開可 |
| 初期化 | 全フラグデータのダウンロード | 該当ユーザーの評価結果のみ受信 |
| 適用場面 | バックエンド、マイクロサービス | ブラウザ、モバイルアプリ |
5.4 パーセンテージロールアウトのハッシュアルゴリズム
LaunchDarklyのパーセンテージロールアウト(トラフィック配分)は、決定論的なハッシュアルゴリズムに基づいている。これにより、同じユーザーは常に同じバリエーションを受け取る(一貫性の保証)。
# LaunchDarklyのバケット配分アルゴリズム(概念的な擬似コード)
import hashlib
def bucket_user(flag_key: str, user_key: str, salt: str) -> float:
"""ユーザーを0.0-1.0の範囲のバケットに配分する。"""
hash_input = f"{flag_key}.{salt}.{user_key}"
hash_value = hashlib.sha1(hash_input.encode()).hexdigest()[:15]
int_value = int(hash_value, 16)
bucket = int_value / 0xFFFFFFFFFFFFFFF
return bucket
def evaluate_rollout(flag_key, user_key, salt, variations, weights):
"""ロールアウト設定に基づいてバリエーションを割り当てる。"""
bucket = bucket_user(flag_key, user_key, salt)
cumulative = 0.0
for i, weight in enumerate(weights):
cumulative += weight / 100000.0
if bucket < cumulative:
return variations[i]
return variations[-1]
ハッシュの重要な性質:
- 決定論性: 同じ入力に対して常に同じバケット値を返す
- 均一分布: ハッシュ値が均一に分布するため、配分が偏らない
- 独立性: 異なるフラグ間でのバケット配分は独立している(ソルトが異なるため)
- 再シャッフル: ソルトを変更することで、トラフィック配分を再シャッフルできる
5.5 イベントパイプラインのアーキテクチャ
イベントのフロー:
SDK (Evaluation/Custom Events)
| バッチ送信(デフォルト: 5秒間隔)
v
Event Ingestion Service
(受信/検証/重複排除/ストリーム書き込み)
|
+--+--+
| | |
v v v
Real-time Batch Data Export
Analysis Analysis Pipeline
| | |
v v v
実験ダッシュ 統計レポート 外部データウェアハウス
ボード
6. SDK統合とデータフロー
6.1 サーバーサイドSDKの統合
6.1.1 Node.js SDK
const LaunchDarkly = require('@launchdarkly/node-server-sdk');
const client = LaunchDarkly.init('sdk-key-xxxxxxxx', {
stream: true,
streamUri: 'https://stream.launchdarkly.com',
eventsUri: 'https://events.launchdarkly.com',
flushInterval: 5,
allAttributesPrivate: false,
privateAttributes: ['email', 'phone'],
featureStore: LaunchDarkly.integrations.Redis({
redisOpts: { host: 'redis.example.com', port: 6379 },
prefix: 'launchdarkly',
cacheTTL: 30
}),
diagnosticOptOut: false,
diagnosticRecordingInterval: 900
});
await client.waitForInitialization({ timeout: 10 });
const context = {
kind: 'multi',
user: {
key: 'user-123',
name: 'Taro Yamada',
email: 'taro@example.com',
custom: { plan: 'enterprise', country: 'JP' }
},
organization: {
key: 'org-456',
name: 'Acme Corp',
custom: { tier: 'premium' }
}
};
// フラグの評価
const checkoutVersion = await client.variation('checkout-flow-version', context, 'current');
// 評価の詳細情報を取得
const detail = await client.variationDetail('checkout-flow-version', context, 'current');
console.log('Value:', detail.value);
console.log('Variation Index:', detail.variationIndex);
console.log('Reason:', detail.reason);
// カスタムイベントの送信(メトリクス計測用)
client.track('checkout-completed', context, {
orderId: 'order-789', amount: 15000, currency: 'JPY', items: 3
}, 15000);
// 数値メトリクスの送信
client.track('page-load-time', context, null, 1250);
// グレースフルシャットダウン
process.on('SIGTERM', async () => {
await client.flush();
await client.close();
process.exit(0);
});
6.1.2 Python SDK
import ldclient
from ldclient import Context
from ldclient.config import Config
config = Config(
sdk_key='sdk-key-xxxxxxxx',
http=ldclient.config.HTTPConfig(connect_timeout=10, read_timeout=15),
events=ldclient.config.EventsConfig(
capacity=10000, flush_interval=5,
private_attributes=['email', 'ssn']
),
stream=True, send_events=True
)
ldclient.set_config(config)
client = ldclient.get()
multi_context = Context.create_multi(
Context.builder("user-123").kind("user").name("Taro").set("plan", "enterprise").build(),
Context.builder("org-456").kind("organization").set("tier", "premium").build()
)
recommendation_config = client.variation('recommendation-config', multi_context,
{"algorithm": "default", "numRecommendations": 5})
client.track('recommendation-click', multi_context, metric_value=1)
client.track('purchase-amount', multi_context, metric_value=12500)
6.1.3 Go SDK
package main
import (
"fmt"
"time"
ld "github.com/launchdarkly/go-server-sdk/v7"
"github.com/launchdarkly/go-server-sdk/v7/ldcomponents"
ldcontext "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
)
func main() {
config := ld.Config{
Events: ldcomponents.SendEvents().Capacity(10000).FlushInterval(5 * time.Second),
DataSource: ldcomponents.StreamingDataSource().InitialReconnectDelay(1 * time.Second),
}
client, _ := ld.MakeCustomClient("sdk-key-xxxxxxxx", config, 10*time.Second)
defer client.Close()
multiContext := ldcontext.NewMultiBuilder().
Add(ldcontext.NewBuilder("user-123").Kind("user").SetString("plan", "enterprise").Build()).
Add(ldcontext.NewBuilder("org-456").Kind("organization").SetString("tier", "premium").Build()).
Build()
value, _ := client.JSONVariation("recommendation-config", multiContext,
ldvalue.ObjectBuild().SetString("algorithm", "default").SetInt("numRecommendations", 5).Build())
fmt.Printf("Algorithm: %s\n", value.GetByKey("algorithm").StringValue())
client.TrackMetric("checkout-completed", multiContext, ldvalue.Null(), 15000)
}
6.2 クライアントサイドSDKの統合
6.2.1 React SDK
import { LDProvider, useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
function App() {
return (
<LDProvider
clientSideID="client-id-xxxxxxxx"
context={{ kind: 'user', key: 'user-123', name: 'Taro', custom: { plan: 'enterprise' } }}
options={{ streaming: true, bootstrap: 'localStorage' }}
>
<CheckoutPage />
</LDProvider>
);
}
function CheckoutPage() {
const flags = useFlags();
const ldClient = useLDClient();
const checkoutVersion = flags['checkout-flow-version'] || 'current';
const handleCheckoutComplete = (orderAmount) => {
ldClient.track('checkout-completed', { orderId: 'order-xxx' }, orderAmount);
};
switch (checkoutVersion) {
case 'simplified': return <SimplifiedCheckout onComplete={handleCheckoutComplete} />;
case 'one-page': return <OnePageCheckout onComplete={handleCheckoutComplete} />;
default: return <CurrentCheckout onComplete={handleCheckoutComplete} />;
}
}
6.2.2 iOS SDK (Swift)
import LaunchDarkly
// SDK設定
var config = LDConfig(mobileKey: "mob-key-xxxxxxxx", autoEnvAttributes: .enabled)
config.streamingMode = .streaming
config.eventFlushInterval = 5.0
var userBuilder = LDContextBuilder(key: "user-123")
userBuilder.kind("user")
userBuilder.trySetValue("plan", .string("enterprise"))
let context = try! userBuilder.build().get()
LDClient.start(config: config, context: context) {
print("LaunchDarkly SDK initialized")
}
// フラグ評価
let client = LDClient.get()!
let version = client.variation(forKey: "checkout-flow-version", defaultValue: "current") as String
// メトリクスの送信
try? client.track(key: "checkout-completed", metricValue: 15000)
7. メトリクスとイベント設計
7.1 メトリクスの種類
7.1.1 コンバージョンメトリクス(Conversion Metrics)
バイナリイベント(発生/非発生)を測定する。コンバージョン率として算出される。
{
"key": "checkout-completed",
"name": "Checkout Completed",
"kind": "custom",
"eventKey": "checkout-completed",
"isNumeric": false,
"successCriteria": "HigherThanBaseline"
}
7.1.2 数値メトリクス(Numeric Metrics)
連続値を測定する。平均値、中央値などの統計量で評価される。
{
"key": "page-load-time",
"name": "Page Load Time (ms)",
"kind": "custom",
"eventKey": "page-load-time",
"isNumeric": true,
"unit": "ms",
"successCriteria": "LowerThanBaseline"
}
client.track('page-load-time', context, null, 1250); // 1250ms
client.track('purchase-amount', context, null, 15000); // 15000円
client.track('api-response-time', context, null, 45.3); // 45.3ms
7.1.3 ファネルメトリクス(Funnel Metrics)
複数のステップからなるユーザージャーニーの完了率を測定する。
ファネル: チェックアウトプロセス
Step 1: カート閲覧 --- cart-viewed
Step 2: 配送先入力 --- shipping-entered
Step 3: 支払い情報入力 --- payment-entered
Step 4: 注文確認 --- order-confirmed
Step 5: 注文完了 --- order-completed
7.2 メトリクスの設計原則
良い設計:
Primary: checkout_conversion_rate(主要KPI)
Secondary: average_order_value(収益影響)
Secondary: checkout_time(UX品質)
Guardrail: error_rate(品質保証)
Guardrail: page_load_time(パフォーマンス保証)
7.3 イベント設計のベストプラクティス
// 命名規約: {domain}-{action}-{detail}
client.track('checkout-completed', context, null, orderAmount); // 良い
client.track('search-results-viewed', context, { query }, resultCount); // 良い
client.track('click', context); // 悪い - 何のクリックか不明
client.track('event1', context); // 悪い - 意味が不明
8. 統計エンジンと分析手法
8.1 統計手法の概要
| 手法 | 用途 | 特徴 |
|---|---|---|
| 逐次テスト(Sequential Testing) | リアルタイム分析 | 固定サンプルサイズ不要 |
| 頻度論的検定(Frequentist) | 信頼区間の提示 | 従来のA/Bテスト手法 |
| ベイズ推定(Bayesian) | 確率的な意思決定 | 直感的な確率表現 |
| CUPED | 分散削減 | 実験前データを活用 |
8.2 逐次テスト(Sequential Testing)
LaunchDarklyの主要な統計手法は逐次テストである。従来の固定水平線テスト(Fixed-Horizon Test)では、事前にサンプルサイズを決定し、そのサンプルが集まるまで結果を見てはならないという制約があった。逐次テストはこの制約を取り除く。
固定水平線テスト:
[データ収集期間 (途中で見てはいけない)] -> 結果評価(1回のみ)
逐次テスト:
[データ収集期間]
^ ^ ^ ^ ^ ^ ^
評価 評価 評価 評価 ... (いつでも結果を確認可能)
LaunchDarklyは、Always Valid Inference(AVI)フレームワークに基づく逐次テストを実装しており、任意の時点で有効な信頼区間を提供する。
信頼区間の計算(概念的な数式):
通常の信頼区間: CI = x_bar +/- z * sigma / sqrt(n)
逐次テスト(AVI): CI_t = x_bar_t +/- sqrt(2 * sigma^2 * (log(log(n_t)) + C) / n_t)
8.3 ベイズ推定
ベイズ推定の出力例:
Treatment A vs Control:
Probability of Being Best: 92.3%
Expected Improvement: +4.2%
Credible Interval (95%): [+1.1%, +7.3%]
Risk (Expected Loss): 0.15%
Treatment B vs Control:
Probability of Being Best: 5.1%
Expected Improvement: -1.8%
Credible Interval (95%): [-4.2%, +0.6%]
Risk (Expected Loss): 2.34%
8.4 CUPED(Controlled-experiment Using Pre-Experiment Data)
CUPEDは、実験前のデータを活用して分散を削減する手法である。
CUPEDの原理:
Y_cuped = Y - theta * (X - E[X])
Var(Y_cuped) = Var(Y) * (1 - rho^2)
分散削減率:
rho = 0.3 -> 9%削減
rho = 0.5 -> 25%削減
rho = 0.7 -> 51%削減
rho = 0.9 -> 81%削減
8.5 多重比較補正
LaunchDarklyは、バリエーション間の比較にはDunnett補正、メトリクス間にはBenjamini-Hochberg法(FDR制御)を自動的に適用する。
8.6 サンプルサイズ計算
例:
ベースライン: 3.0% 期待改善: 3.3% (相対10%) 有意水準: 5% 検出力: 80%
n = 約44,600 ユーザー/バリエーション
2バリエーション合計: 約89,200ユーザー
1日10,000ユーザーの場合: 約9日間
8.7 実験結果の解釈
実験結果ダッシュボード:
Experiment: Checkout Flow Optimization
Status: Running (Day 12 of minimum 14)
Total Participants: 87,432
PRIMARY METRIC: Checkout Conversion Rate
Variation | Users | Conv. Rate | Change | CI (95%)
-------------------|--------|------------|---------|------------------
Control (Current) | 29,144 | 3.02% | base | -
Simplified [win] | 29,156 | 3.28% | +8.6% | [+2.1%,+15.3%]
One-Page | 29,132 | 3.09% | +2.3% | [-4.0%,+8.9%]
GUARDRAIL METRIC: Error Rate
Variation | Error Rate | Change | Status
-------------------|------------|---------|--------
Control (Current) | 0.12% | base | -
Simplified | 0.11% | -8.3% | Safe
One-Page | 0.18% | +50.0% | Warning
9. ターゲティングとセグメンテーション
9.1 ターゲティングルールの構造
{
"flagKey": "checkout-flow-version",
"on": true,
"targets": [
{ "contextKind": "user", "values": ["user-001", "user-002"], "variation": 1 }
],
"rules": [
{
"id": "rule-1",
"description": "Premium users in Japan",
"clauses": [
{ "contextKind": "user", "attribute": "country", "op": "in", "values": ["JP"] },
{ "contextKind": "organization", "attribute": "tier", "op": "in", "values": ["premium", "enterprise"] }
],
"variation": 1,
"trackEvents": true
},
{
"id": "rule-2",
"description": "50/50 rollout for US users",
"clauses": [
{ "contextKind": "user", "attribute": "country", "op": "in", "values": ["US"] }
],
"rollout": {
"variations": [
{ "variation": 0, "weight": 50000 },
{ "variation": 1, "weight": 50000 }
],
"bucketBy": "key",
"contextKind": "user"
}
}
],
"fallthrough": {
"rollout": {
"variations": [
{ "variation": 0, "weight": 90000 },
{ "variation": 1, "weight": 10000 }
]
}
},
"offVariation": 0
}
9.2 演算子(Operators)
| 演算子 | 説明 | 例 |
|---|---|---|
in | 値がリストに含まれる | country in ["JP", "US"] |
endsWith | 文字列が特定の値で終わる | email endsWith "@example.com" |
startsWith | 文字列が特定の値で始まる | name startsWith "test-" |
matches | 正規表現にマッチ | email matches "^.*@corp\.com$" |
contains | 文字列が含まれる | name contains "admin" |
lessThan / greaterThan | 数値比較 | age lessThan 18 |
before / after | 日時比較 | createdAt after "2024-06-01T00:00:00Z" |
semVerEqual / semVerLessThan / semVerGreaterThan | バージョン比較 | appVersion semVerGreaterThan "1.5.0" |
segmentMatch | セグメントに属する | segmentMatch "beta-users" |
9.3 トラフィック配分と相互排他的実験
全トラフィック (100%)
|-- 実験対象 (20%)
| |-- Control (50% of 20% = 10%)
| |-- Treatment A (25% of 20% = 5%)
| +-- Treatment B (25% of 20% = 5%)
+-- 実験非対象 (80%) -> デフォルト
相互排他グループ:
|-- 実験A用 (30%): バケット 0-30%
|-- 実験B用 (30%): バケット 30-60%
+-- 未割当 (40%): バケット 60-100%
10. 実装パターンと具体例
10.1 バックエンドA/Bテスト: レコメンデーションアルゴリズム
class RecommendationService:
def __init__(self):
self.ld_client = ldclient.get()
self.algorithms = {
'collaborative-filtering': CollaborativeFilteringEngine(),
'content-based': ContentBasedEngine(),
'hybrid-ml': HybridMLEngine()
}
def get_recommendations(self, user_id, user_attrs, num_items=10):
context = Context.builder(user_id).kind("user") \
.set("plan", user_attrs.get("plan", "free")).build()
config = self.ld_client.json_variation('recommendation-config', context,
{"algorithm": "collaborative-filtering", "numRecommendations": 10})
start_time = time.time()
engine = self.algorithms[config["algorithm"]]
recommendations = engine.generate(user_id, config["numRecommendations"])
latency_ms = (time.time() - start_time) * 1000
self.ld_client.track('recommendation-latency', context, metric_value=latency_ms)
return recommendations
10.2 マイクロサービスでのコンテキスト伝播
HTTP Headers:
X-LaunchDarkly-User-Key: user-123
X-LaunchDarkly-User-Plan: enterprise
X-LaunchDarkly-Org-Key: org-456
X-LaunchDarkly-Org-Tier: premium
@app.before_request
def extract_ld_context():
user_key = request.headers.get('X-LaunchDarkly-User-Key')
if user_key:
g.ld_context = Context.builder(user_key).kind("user") \
.set("plan", request.headers.get('X-LaunchDarkly-User-Plan', 'free')).build()
11. Relay Proxyとエンタープライズアーキテクチャ
11.1 Relay Proxyの概要
Relay Proxyは、SDKとLaunchDarklyクラウドの間に配置されるプロキシサーバーである。
- 接続数の削減: 多数のSDKインスタンスからの接続を集約
- レイテンシの削減: フラグデータをローカルにキャッシュ
- セキュリティ: SDKがインターネットに直接アクセス不要
- 高可用性: LaunchDarklyサービスの障害からの保護
11.2 Relay Proxyの設定
# relay-proxy-config.yaml
main:
port: 8030
metricsPort: 8031
logLevel: "info"
environment:
production:
sdkKey: "sdk-prod-xxxxxxxx"
mobileKey: "mob-prod-xxxxxxxx"
redis:
host: "redis.example.com"
port: 6379
localTtl: 30000
prefix: "ld-relay"
tls: true
# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: ld-relay-proxy
namespace: feature-flags
spec:
replicas: 3
template:
spec:
containers:
- name: ld-relay
image: launchdarkly/ld-relay:8
ports:
- containerPort: 8030
- containerPort: 8031
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "1000m", memory: "512Mi" }
livenessProbe:
httpGet: { path: /status, port: 8030 }
11.3 SDK からの Relay Proxy 接続設定
const client = LaunchDarkly.init('sdk-key-xxxxxxxx', {
streamUri: 'http://ld-relay-proxy.feature-flags.svc.cluster.local:8030',
baseUri: 'http://ld-relay-proxy.feature-flags.svc.cluster.local:8030',
eventsUri: 'http://ld-relay-proxy.feature-flags.svc.cluster.local:8030'
});
12. データパイプラインと外部連携
12.1 Data Export
LaunchDarklyは、Kinesis Firehose、Google Pub/Sub、Segment、mParticle等への イベントデータエクスポートをサポートする。
12.2 エクスポートイベントのスキーマ
{
"kind": "feature",
"creationDate": 1709827200000,
"key": "checkout-flow-version",
"context": { "kind": "user", "key": "user-123" },
"variation": 1,
"value": "simplified",
"reason": { "kind": "RULE_MATCH", "ruleIndex": 0, "inExperiment": true }
}
12.3 Terraform Provider
resource "launchdarkly_feature_flag" "checkout_flow" {
project_key = "payment-service"
key = "checkout-flow-version"
name = "Checkout Flow Version"
variation_type = "string"
variations {
value = "current"
name = "Current Flow"
}
variations {
value = "simplified"
name = "Simplified Flow"
}
variations {
value = "one-page"
name = "One-Page Flow"
}
defaults {
on_variation = 0
off_variation = 0
}
tags = ["experiment", "checkout"]
}
resource "launchdarkly_metric" "checkout_conversion" {
project_key = "payment-service"
key = "checkout-conversion-rate"
name = "Checkout Conversion Rate"
kind = "custom"
event_key = "checkout-completed"
is_numeric = false
success_criteria = "HigherThanBaseline"
}
13. セキュリティとガバナンス
13.1 認証と認可
トークンの種類:
1. API Access Token: 管理API操作用(サーバーサイドのみ)
2. SDK Key: フラグデータ取得/イベント送信用(環境ごとに固有、秘密)
3. Mobile Key: モバイルアプリ組み込み用
4. Client-side ID: ブラウザSDK用(公開可)
13.2 カスタムロール
{
"name": "experiment-manager",
"policy": [
{
"resources": ["proj/*:env/production:flag/*"],
"actions": ["createExperiment", "updateExperiment", "stopExperiment"],
"effect": "allow"
},
{
"resources": ["proj/*:env/production:flag/*"],
"actions": ["updateOn", "updateFallthrough"],
"effect": "deny"
}
]
}
13.3 承認ワークフロー
本番環境でのフラグ変更に、複数のレビュアーによる承認を強制可能。
13.4 プライベート属性
const client = LaunchDarkly.init('sdk-key-xxx', {
privateAttributes: ['email', 'phone', 'ssn']
});
const context = {
kind: 'user',
key: 'user-123',
name: 'Taro Yamada',
email: 'taro@example.com',
_meta: { privateAttributes: ['email'] }
};
14. 運用のベストプラクティス
14.1 フラグのライフサイクル管理
1. 作成 (Creation) -> OFF状態
2. テスト (Testing) -> 開発/ステージング環境で検証
3. ロールアウト (Rollout) -> 段階的に本番展開 (1% -> 10% -> 50% -> 100%)
4. 実験 (Experiment) -> A/Bテスト実行
5. 全展開 (Full Rollout) -> 勝者バリエーション100%
6. クリーンアップ (Cleanup) -> フラグ削除、コード除去 ← 最も忘れられがち
14.2 フラグの命名規約
パターン: "{domain}-{feature}-{detail}"
良い例: checkout-flow-version, api-rate-limit-config
悪い例: flag1, newFeature, test_flag_do_not_delete
タグの標準化:
- team:checkout, type:experiment, quarter:Q1-2025, status:cleanup-needed
14.3 技術的負債の管理
# Code References CLI でコード内のフラグ参照を追跡
ld-find-code-refs \
--accessToken="${LD_ACCESS_TOKEN}" \
--projKey="payment-service" \
--repoName="payment-api" \
--dir="." \
--defaultBranch="main"
14.4 段階的ロールアウト戦略
Phase 1 - Internal (Day 0-2): 社内ユーザーのみ、個別ターゲティング
Phase 2 - Canary (Day 3-5): 全ユーザーの1%
Phase 3 - Early Adopters (Day 6-10): 全ユーザーの10%
Phase 4 - Experiment (Day 11-25): 33% Control + 33% Treatment
Phase 5 - Broad Rollout (Day 26-30): 100%(勝者バリエーション)
Phase 6 - Cleanup (Day 31+): フラグ削除、デッドコード除去
14.5 障害対応
// 障害時のフォールバック実装
const primaryClient = LaunchDarkly.init('sdk-key-xxx', { stream: true });
const offlineClient = LaunchDarkly.init('sdk-key-xxx', {
offline: true,
featureStore: LaunchDarkly.integrations.FileData.factory({
filePaths: ['./flag-defaults.json']
})
});
async function evaluateFlag(flagKey, context, defaultValue) {
try {
if (primaryClient.initialized()) {
return await primaryClient.variation(flagKey, context, defaultValue);
}
return await offlineClient.variation(flagKey, context, defaultValue);
} catch {
return defaultValue;
}
}
15. トラブルシューティング
15.1 フラグ評価が期待と異なる
const detail = await client.variationDetail('my-flag', context, false);
console.log('Value:', detail.value);
console.log('Reason:', detail.reason);
// reason.kind: "OFF", "TARGET_MATCH", "RULE_MATCH", "PREREQUISITE_FAILED",
// "FALLTHROUGH", "ERROR"
// errorKind: "CLIENT_NOT_READY", "FLAG_NOT_FOUND", "WRONG_TYPE"
15.2 実験でデータが記録されない
チェックリスト:
- trackEvents: true が設定されているか
- 実験ステータスが "Recording" か
- イベントキーがメトリクス定義と一致しているか
- sendEvents: true(SDKのイベント送信有効)か
- イベントバッファがフラッシュされているか
- コンテキストキーが空文字列やnullでないか
15.3 デバッグモード
const client = LaunchDarkly.init('sdk-key-xxx', {
logger: LaunchDarkly.basicLogger({ level: 'debug' }),
diagnosticRecordingInterval: 60
});
16. まとめ
16.1 LaunchDarkly A/Bテストの利点
| 利点 | 詳細 |
|---|---|
| フルスタック対応 | UI、バックエンド、インフラ、アルゴリズムなどあらゆるレイヤーでの実験が可能 |
| リアルタイム分析 | 逐次テストにより、データ収集中もリアルタイムで結果を評価可能 |
| 低レイテンシ | サーバーサイドSDKのローカル評価により、マイクロ秒レベル |
| 統合されたワークフロー | フラグ管理、実験、段階的ロールアウトが一つのプラットフォームで完結 |
| エンタープライズ対応 | RBAC、承認ワークフロー、監査ログ、SOC2準拠 |
| 充実したSDK | 25以上の言語/フレームワーク対応 |
| データエクスポート | 外部データウェアハウスへのイベントストリーム連携 |
| Infrastructure as Code | Terraform、API、CLIによる自動化対応 |
16.2 導入にあたっての考慮点
技術的考慮点:
- SDK初期化のタイムアウト設定とフォールバック値の設計
- マイクロサービス間のコンテキスト伝播戦略
- イベントバッファのサイズとフラッシュ間隔の最適化
- Relay Proxyの導入判断(接続数、セキュリティ要件)
組織的考慮点:
- フラグの命名規約とタグ付け標準の策定
- カスタムロールと承認ワークフローの設計
- フラグのライフサイクル管理プロセスの確立
- 技術的負債(古いフラグ)の管理体制
統計的考慮点:
- 適切なサンプルサイズの事前計算
- 最小検出効果(MDE)の決定
- 多重比較問題への対応
- ガードレールメトリクスの選定
16.3 参考リソース
公式ドキュメント:
- LaunchDarkly Docs: https://docs.launchdarkly.com/
- API Reference: https://apidocs.launchdarkly.com/
- SDK Reference: https://docs.launchdarkly.com/sdk/
- Experimentation: https://docs.launchdarkly.com/home/about-experimentation/
統計に関する参考文献:
- "Always Valid Inference" (Johari et al., 2017)
- "Peeking at A/B Tests" (Johari et al., 2015)
- "CUPED" (Deng et al., Microsoft, 2013)
- "Trustworthy Online Controlled Experiments" (Kohavi et al., Cambridge Press)
本書は、LaunchDarkly A/B分析機能の技術的な全体像を解説したものである。実際の導入に際しては、最新の公式ドキュメントを参照し、自社の要件に合わせたアーキテクチャ設計を行うことを推奨する。