{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihtrojg4ysnpqoalqh5ju5zwp6nazcorfguhkbcij2u5mybe4jw24",
"uri": "at://did:plc:jixicdme7omicxzdn5nuvxzl/app.bsky.feed.post/3mihx2awl3db2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreib3ueuwq5vpzu2ppjfkscit72o7gzdrii2s2p554itznfimhnmxyq"
},
"mimeType": "image/png",
"size": 105480
},
"path": "/entry/darklaunch-v2-features",
"publishedAt": "2026-04-02T00:00:00.000Z",
"site": "https://blog.studysapuri.jp",
"tags": [
"@kumackey",
"Feature toggles",
"Rails アプリケーションの Ruby モジュールとして提供されていました",
"Go のマイクロサービスとしてフルスクラッチで再構築",
"Microsoft Office では約 12,000 もの Feature flags が存在するそう"
],
"textContent": "こんにちは、バックエンドエンジニアの @kumackey です。\n\n『スタディサプリ』では、新機能の段階的リリースを支える仕組みとして、内製の Feature toggles 基盤「Darklaunch v2」を運用しています。本記事では、Darklaunch v2 が提供する機能群と改善の進め方について紹介します。\n\n * 概要\n * Feature toggles とは\n * Darklaunch v2 とは\n * 機能群\n * リリース済みキーの Slack リマインド\n * 課題\n * 解決策\n * リクエスト数と最後にリクエストされた日時\n * 課題\n * 解決策\n * Owner team によるフィルタリング\n * 課題\n * 解決策\n * Ruby SDK での Fail-safe 機能\n * 課題\n * 解決策\n * Ruby SDK でのテストの stub ライブラリ\n * 課題\n * 解決策\n * 改善の進め方\n * Slack サポートチャンネル\n * 定期的なサーベイと要望の収集\n * まとめ\n\n\n\n## 概要\n\n### Feature toggles とは\n\nFeature toggles とは、**コードのデプロイとリリースを分離** する仕組みです。\n\n通常の開発では、コードをデプロイするとそのまま新機能がリリースされます。デプロイとリリースが同時に発生するため、もし問題があった場合はコードを再デプロイして戻す必要があり、all-or-nothing なリスクの高いリリースになりがちです。\n\nFeature toggles を導入すると、コードのデプロイと新機能のリリースは独立して行われます。コード上で if 分岐を書いておき、管理画面でトグルを切り替えるだけで制御できます。デプロイなしに任意の粒度で少しずつリリースしたり、問題があればすぐに戻したりできます。\n\n### Darklaunch v2 とは\n\nDarklaunch v2 は、この Feature toggles を実現するための社内サービスです。以前は Rails アプリケーションの Ruby モジュールとして提供されていましたが、現在は Go のマイクロサービスとしてフルスクラッチで再構築されています。\n\nDarklaunch v2 では、リリースを制御する単位を「**キー** 」と呼びます。キーは新機能ごとに作成し、管理画面から設定を変更することでリリースを制御します。\n\n開発者は SDK を使って、アプリケーションコードに if 分岐を書くだけで Feature toggles を利用できます。以下は Ruby SDK の例です。\n\n\n if DarklaunchV2Client.variation('enable-new-feature', 'User.id', current_user.id, num_retries: 2) # 新機能の処理 else # 既存の処理 end\n\n`variation` メソッドは、キー名と identifier(ユーザーを特定する情報)を受け取ります。管理画面での設定に基づいて `true` または `false` を返し、この結果で新機能の公開・非公開を制御できます。\n\nDarklaunch v2 の全体像を以下に示します。\n\n## 機能群\n\nここからは、Darklaunch v2 が提供する機能群を紹介します。それぞれ、どのような課題があり、どう解決しているかを説明します。\n\n### リリース済みキーの Slack リマインド\n\n#### 課題\n\nFeature toggles を使った新機能のリリースが完了した後、キーがそのまま放置されてしまうことがあります。リリース済みのキーが残り続けると、以下の問題が発生します。\n\n * 開発者にとっては、コード上の不要な分岐が技術的負債になる\n * Darklaunch v2 基盤側としては、キー数やリクエスト数が増加し続ける\n\n\n\nちなみに Microsoft Office では約 12,000 もの Feature flags が存在するそうで、キーの放置は広く知られた課題です。\n\n#### 解決策\n\nArgo CronWorkflow で定期実行されるジョブが、**30日以上更新がないリリース済みのキー** を検出し、Slack で通知します。\n\n通知メッセージには、キー名や前回更新からの経過日数、管理画面へのリンクが含まれます。さらに Claude Code Action によるキーの削除 PR 自動作成リンクも付いています。\n\n### リクエスト数と最後にリクエストされた日時\n\n#### 課題\n\n前述のリマインドでキーの放置に気づいた後、実際に削除してよいかの判断材料が必要です。「本当にもう使われていないのか?」が分からないと、削除に踏み切れません。\n\n#### 解決策\n\nDarklaunch v2 API では、`variation` リクエストのたびに Datadog StatsD でメトリクスを記録しています。管理画面のキー詳細ページには、このメトリクスを利用した 2 つの情報が表示されます。\n\n 1. **リクエスト数のグラフ** : Datadog の iframe を埋め込み、リクエスト数の推移をグラフで表示\n 2. **最後に使われたとき** : Datadog API を使って、最後にリクエストがあった時期を算出\n\n\n\nこの情報があることで、キーを削除する際に「最後に使われたのが約 3 ヶ月前なら、もう消して大丈夫だろう」と安心して判断できるようになります。\n\n### Owner team によるフィルタリング\n\n#### 課題\n\nDarklaunch v2 のキーは組織全体で共有されているため、一覧には全チームのキーが混在して表示されます。キーの数は現在 100 個以上あり、自チーム関連のキーを探すのが大変になっていました。\n\n#### 解決策\n\n一覧ページに Owner team のドロップダウンフィルターを設けています。チームを選択すると、そのチームが管理するキーだけに絞り込まれます。自チームのキーの状態をすばやく把握でき、他チームのキーに埋もれることがなくなりました。\n\n### Ruby SDK での Fail-safe 機能\n\n#### 課題\n\nDarklaunch v2 はさまざまなサービスで使われており、停止するとサービス全体へ障害が波及しかねません。\n\n#### 解決策\n\nRuby SDK では、API へのリクエストが失敗した場合に **false を返す** ことで安全側に倒す設計です。`variation()` が true を返すと新機能が有効になるため、エラー時に false を返せば「意図せず新機能が出てしまう」事故を防げます。\n\n### Ruby SDK でのテストの stub ライブラリ\n\n#### 課題\n\nDarklaunch v2 を利用するアプリケーションのテストを書くには工夫が必要でした。`DarklaunchV2Client` のメソッドを直接 stub するか、HTTP リクエストを stub しなければなりません。\n\n\n # DarklaunchV2Client を直接 stub する場合 allow(DarklaunchV2Client).to receive(:variation) .with('enable-new-feature', 'User.id', user.id, num_retries: 2) .and_return(true) # HTTP リクエストを stub する場合 stub_request(:get, %r{/variation\\?.*key=enable-new-feature}) .to_return(status: 200, body: { variation: true }.to_json)\n\n引数の順序やクエリパラメータの形式を正確に合わせる必要があり、テストが書きにくいという声がありました。\n\n#### 解決策\n\n`stub_darklaunch_v2` ヘルパーメソッドを提供し、テストでの利用を簡単にしています。管理画面での設定を模倣する設計で、キー、always_true、identifiers を指定するだけで stub を設定できます。\n\n\n # 常に true を返す key を stub stub_darklaunch_v2('enable-new-feature', true) # 特定の identifier に対してのみ true を返す key を stub stub_darklaunch_v2( 'enable-new-feature', false, { 'User.id' => ['user-id-1', 'user-id-2'] } )\n\n`always_true` が `true` の場合はどの identifier でも true を返します。`false` の場合は `identifiers` に指定された identifier のみ true を返します。管理画面での設定と同じ概念で stub を設定できるため、直感的にテストを書けます。\n\n内部では `DarklaunchV2Client::Mock::Server` クラスが管理画面の挙動をシミュレートしています。`variation` をはじめとするすべてのメソッドに対応しています。\n\n## 改善の進め方\n\nここまで紹介してきた機能群は、一度作って終わりではなく、利用者からのフィードバックを受けて継続的に改善してきたものです。ここでは、その進め方を紹介します。\n\n### Slack サポートチャンネル\n\nDarklaunch v2 には専用の Slack チャンネルがあり、利用者とのコミュニケーションの場として機能しています。チャンネルでは主に以下の 3 種類のやり取りが行われています。\n\n * 問い合わせ\n * 要望\n * 機能追加・改善のアナウンス\n\n\n\n**問い合わせ** : 使い方や挙動に関する質問・トラブルシューティングの相談が寄せられます。問い合わせの内容は、UI の改善や FAQ の整備に活かされます。\n\n**要望** : 利用者から「こうしてほしい」という要望が直接届きます。要望をそのまま実装するのではなく、「なぜそれが必要なのか」を掘り下げて本質的な課題を明確にすることを大切にしています。この対話を起点に機能改善が生まれることも多くあります。\n\n**機能追加・改善のアナウンス** : 新機能をリリースした際に、チャンネルで周知します。利用者に実際に使ってもらう機会を作ることで、次のフィードバックにつながります。\n\n### 定期的なサーベイと要望の収集\n\nSlack チャンネルでは日常的にフィードバックが集まりますが、声を上げにくい利用者の意見も拾うために、定期的にサーベイを実施しています。満足度の定量評価に加えて、自由記述で具体的な要望や困りごとを集めています。\n\nサーベイの回答からは、Slack では出てこなかった意見が見つかることもあります。\n\n## まとめ\n\n本記事では、内製 Feature toggles 基盤 Darklaunch v2 の機能群と改善の進め方について紹介しました。\n\n * **リリース済みキーの Slack リマインド** : 放置されたキーを定期的に検出・通知し、技術的負債の蓄積を防止\n * **リクエスト数と最後にリクエストされた日時** : Datadog メトリクスを活用し、キーの削除判断を支援\n * **Owner team フィルタリング** : 自チームのキーに素早くアクセスできる\n * **Fail-safe 機能** : エラー時に false を返す安全設計で、基盤の障害がサービス全体に波及することを防止\n * **テスト stub ライブラリ** : 管理画面の設定と同じ概念でテストを書ける\n\n\n\nこれらの機能は、Slack サポートチャンネルやサーベイを通じた利用者からのフィードバックを起点に改善を続けています。社内基盤は、作ったら終わりではなく、利用者との対話を通じて継続的に改善していくことが重要です。Darklaunch v2 は、開発者が安心してリリースできる体験を提供し続けるために、今後も改善を続けていきます。",
"title": "内製 Feature toggles 基盤 Darklaunch v2 の機能群と改善の進め方",
"updatedAt": "2026-04-02T00:00:01.000Z"
}