スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Darklaunchという便利なものと、その未来

Darklaunchという便利なものと、その未来

3行要約

  • スタディサプリのRubyバックエンドで気軽に誰でも使える、"Darklaunch"という Feature toggles 機構がある
  • 4年強ほど運用してきて、様々な知見、多様なユースケース、問題などが明らかになった
  • 知見を活かして、近い将来Darklaunch v2というのを社内で作って公開予定 (HTTP APIであり、Rubyバックエンドだけでなく全環境から直接利用可能)

Feature togglesとは

Feature togglesとかFeature flagsとかchankoとかcanary releaseとかA/BテストとかlaunchdarklyとかflipperとかDarklaunchなどという言葉を見聞きした機会のある方は多いと思います。ちょっとそれぞれレイヤーやカバレッジは違いますが、ようするに「コードの変更とproduction systemの挙動の変更を分離する」ということを実現するためのものです。

以前、RailsDM というカンファレンスにて、"雑" というタイトルでMicroservicesとFeature togglesについての発表 を行いました。詳しくはこの発表動画を見ていただくのが一番わかりやすいのですが、ここで簡単にまとめますと、Feature togglesとは要するに、ソースコード上で以下のように見えるなにかのことです。

(以下、コード例はすべてRubyで記述します。また名称はDarklaunchで固定します。)

if Darklaunch.variation('enable-fancy-dashboard', {id: @user.id})
  # めっちゃ便利な新機能。また実装に自信なし
else
  # もともとある既存の機能。絶対に動作することがすでにわかっている
end

肝は以下の3点

  • ユーザによって、新機能が採用されるか否かが分岐される。
  • どのように分岐するかのhowは該当コードには直接書かず、あくまで判断基準の材料 (この場合は@user.id)だけを提示する
  • Darklaunch側に判断のロジックがあり、実際の判断はDarklaunchの内部データが用いられる

Darklaunchは管理画面を必要とします。めちゃくちゃオシャレでreactiveなwebページで管理画面を作っても良いし、jenkinsとかで逐次的に更新するのでもいいし、yamlで書いてどっかにcommitとかなんでもよいです。スタディサプリでは、そこまでめちゃくちゃオシャレでreactiveではない素朴なrailsのscaffoldで作った管理画面でもってDarklaunchの値を操作しています。

jjj

Darklaunchとして運用して4年

初期版を作ったのは2018年の秋です。当時僕が取り組んでいた部分で、スタディサプリのとある部分の処理がタイムアウトしがちであったので同期処理から非同期処理にかえるというのがあったんですが、サーバサイドのロジックもフロントエンドの魅せ方もいろいろ変わる上に内容的にリリースがちょっと怖い部分でした。これはFeature togglesの導入機会にちょうどいいなと思い、さっと初期版を書いて動作するものをみんなに見せてしれっと導入してみました。

当時社内ですでにFeatureFlagという名前の似て非なるものがありました。これは新機能のリリースを安心して行うためのものではなく、ユーザごとのpermissionの管理に相当するものです。(以下のfeature togglesの4象限のうちの右上のpermission togglesに相当します)

Feature togglesの4象限 (出典: Feature Toggles (aka Feature Flags) - Martin Fowler)

初期実装を作ったあと、社内で広く使うために、ドキュメントを整備して社内のtech talkを行う場で解説して、その後継続的にメンテしていきました。初期設計思想は以下のようなものです。

  • Darklaunchは単なるbooleanのkey-value storeを特殊化しただけのものである。よってデータはRedisに保存する
  • 分断されたモノリスのどのRubyコンポーネントからも直接使える。web frontendからは使わず、あくまでweb backendからのみ使う
  • 新機能を段階的にリリースするとき、ユーザ単位だけではなくユーザの所属する団体 (= スタディサプリを利用している学校)単位で区切った方がコミュニケーションが行いやすいので、両方をサポートする
  • release toggles (前述の4象限の左上) としてのみ使用する
  • Darklaunchはすでに抽象化されたレイヤなので、Darklaunchをさらに抽象化するようなレイヤは入れてはいけない。なるべく素直にいきなり使うこと
  • 特に明確なオーナーチームはいない。全員がオーナーであり、全員でメンテする

一方、途中から入った設計思想、というかいつのまにかそのようになっていた実際の運用は以下のようです。

  • Darklaunchはmonolithの一部である。データ構造はもうちょっと複雑である。よってRedisではなく他のデータと同様にMongoDBに保存する。(これによって実装もむしろシンプルになる)
  • web frontendから直接は使わないが、ドメインごとにまとめてデータを取得して別名をつけてapiとして利用可能になっており、実際の条件分岐がweb frontend側にある (※documentと矛盾)
  • permission toggles (前述の4象限の右上)としても使用する (※documentと矛盾)
  • 特定のサービスが不安定になったときに一時的に閉塞するためのbreakerとしても使用する (※documentと矛盾)
  • Darklaunchは社内で適当に作ったもので、長期的にはより多機能なlaunchdarklyなどのSaaSに乗り換えたいという願望があるので、そこまでちゃんと大きな機能変更はしない

しかし、以下のような問題が露わになってきました

  • web frontendからロジックの分岐を行いたいとき、Darklaunchのkey取得部分と実際の分岐との距離が離れていて、不必要に煩雑。これはとくに不要になった古いDarklaunchの削除のときに問題になる
  • documentとの矛盾が多い。実際のところpermission togglesもrelease togglesも実装は対して変わらなく、そこを分離する必要はそこまでなかったかもしれない。管理画面での見せ方や管理方法の方でうまくやれそう
  • launchdarklyを実際に運用してみたが、絶妙にスタディサプリでのユースケースと合致しきっていたかというとそうではなかった。むしろ既存のDarklaunchが十分うまくやれていた
    • 管理画面にアクセスできる人数がプラン金額的に制限されがちである
    • A/Bテストのための高度な分岐ではなく、単にユーザIDのリストや、もっと雑な全体on/off程度の粒度で十分だった
  • MongoDBのデータサイズが大きくかつ負荷も大きく、どんどんデータを脱MongoDBさせたい
  • オーナーシップをもって必要に応じてDarklaunch本体側に機能を追加したり啓蒙したりと継続的運用をするチームが絶対に必要

Darklaunch v2計画

というわけで、以上のような問題を解決するため、以下のような方針でDarklaunch v2を作るべく、草案を作りました。

  • Darklaunchは専用のinternet-facing serviceにする。web frontentからなんの準備もなしにいきなり利用可能
  • ユーザIDや団体IDだけでなく、token単位や保護者単位での分岐も可能 (実際に必要になっていた)
  • permission togglesとしての長期的な用途も前提とする。が、あくまで認可のためのロジックやデータは持たない。あくまでユーザIDなどの文字列一致のみで分岐を行うものとする。
  • 専任チームでもって運用する。具体的にはつい先日立ち上げたInternal Platform開発のためのチームであるk12-platform-enhancementチームが行う
  • データはRedisに保存する

しかし^の記事にもあるように、他にも必要そうなサービスの候補がいくつかあり、まずはそれぞれプロトタイプを作って比較しつつサービスへの理解の解像度を高めたいところです。というわけで、まずはdarklaunch-prototypeというサービスをさっと作り上げて、実際に動作するところまでもっていきました。これはproduction以外の社内kubernetesクラスタにデプロイ済みで、社内の誰でもいつでも気軽に実際に動作確認できる形になっています。

わかりやすく過去から将来へのタイムラインを図で示すとこんな感じです。

タイムライン

prototypeはujihisaが最もラクに何も考えずさくっと作れるものにするため、Ruby on Railsで作りました。Rails最高ですよね、大好きです。いっぽう実際に清書するときにはチームでの長期的なメンテを考え、別の言語で書かれる可能性が高いです。具体的にはGoを使う可能性が非常に高いです。

prototypeを作って、実際にDarklaunchを使うことになるチームなどとユーザインタビューなどを行う、それらすべての過程を経由して、以下のような知見が得られました。

  • Darklaunchのデータ構造は意外に複雑になる。これらを考えるとRedisで行うよりも、むしろ伝統的なRelational DBでもってしまった方がラクそう
    • 例えばDarklaunchで有効とみなすためのユーザIDの数が膨大でも、リクエストのたびに全ユーザIDを取得する必要はない。このあたりが素直に表現できる
    • Darklaunchの変更履歴も保持したい。RDBMSは履歴データの取り扱いをそれほど得意としていないが、かといって他の履歴データの取り扱いを得意としたNoSQLの利用にもそこまで魅力がなかった
  • 「特定日時になったら以後常に有効」ができると便利なケースがあった
    • jobで特定日時にDarklaunchのデータを変更する、という方法もあるが、それよりむしろDarklaunch側にアトミックに「特定日時になったら以後常に有効」が指定できればそれで必要十分である
    • 内容としては冗長だが、むしろその方が管理がしやすい
  • 複数の条件がある場合、それらを単純にOR結合するのがよい。AND結合なども許可する複雑な演算は一切不要
    • 例: enable-xxxというDarklaunchは、user id = 123または10%のユーザに有効、などという設定ができる
    • 一方enable-xxxというDarklaunchがuser id != 123 かつ 10%のユーザに有効、などという設定はできない。そしてそれは必要ではない
  • 各Darklaunch keyごとにオーナー情報を絶対必要なメタデータとして保持させる。ここでいうオーナーは作者個人名ではなく必ずチーム名とすること。
  • GitHubのDangerなどで、Darklaunchを抽象化しようとしていると警告コメントを出す (CIは落ちないので、抽象化すること自体は可能だが、それが意図通りのものではないということを警告するだけで気づきが得られるのでだいたいよしとする)
  • 語彙に名前をつけることが大事。本当に大事。

なおDarklaunch v1とDarklaunch v2はしばらくの間共存することになります。なぜならDarklaunch v1自体の変更をDarklaunchすることができないため、リリースが怖いためです。このとき、古いDarklaunchと新しいDarklaunchを誰でもわかる形で明確に区別できるようにする必要があります。古いDarklaunchは消え去る予定のため、長期的にはこんな名前など不要なのですが、過渡期に絶対に混乱が予想されます。そこで、誰でも単純にわかりやすい"v1"と"v2"というsuffixを用いました。この記事の本文中でも使っていますね、これがその名前です。

実際のDarklaunch v2の開発はまだです。そのときがきたら、やるぞ〜! 具体的にいつ開発になるかはまだ決めていないですが、もし開発に興味があれば、こちらの便利リンクをご活用になることができます。


文責: ujihisa