TwoGate - エンタメ×テクノロジーの現場から
こんにちは。初めまして、TwoGateのチャン[1]です。RubyKaigi 2024が迫ってきている中[2]、TwoGateという会社を知ってもらいたいということで記事を書いています。TwoGateで行っていることは技術的に特色があって書けることには困らないし、書きたいこともたくさんあるので今まで書こうと思い続けていましたが、仕事にもまた困らない会社なので、書けずじまいでした。今回は張り切って各プロダクトのエンジニアで座談会を開いて、横断的にプロダクトの特長と技術的特色などを書こうと思っています!
技術スタック
まずはTwoGateについて基本的な技術スタックの紹介をしましょう。RubyKaigiにスポンサーとして出展するわけですから、当然ながらRubyがバックエンドの言語となっていて、スタンダードなRuby on Railsの構成です。しかしながらこれだけに縛られず、AWS LambdaにTypeScriptなど他の組み合わせが使われることもあります。
フロントとしてはAngularを使うことがほとんどで、Ionicと組み合わせることでモバイルアプリの開発を効率化しています。場合によってはShopifyアプリの実装のためにReactを用いることもあります。
インフラとしては大部分がAWSで、ECSにデプロイし、EC2またはFargate+ALB+RDS+ElastiCacheでサーブする構成がほぼ大半です[3]。
TwoGateとしてはそれが主流ですが、要件に応じて他の言語やアーキテクチャを試してみるというのも受容される組織です[4]。言語やアーキテクチャの良し悪しを議論することはあっても極端なイデオロギーを主張する人はいない印象です[5]。
プロダクト
TwoGateは主にエンタメに関連するサービスを提供していて、もしかすると使われた方もいるのかもしれません[6]。ファンアプリのノーコード構築システムのCrayon、コンサート会場での物販や整理券配布を行うシステムのCaravan、アニメーションが柔軟にカスタマイズ可能でマルチチャンネル、モール型のSlashGift、ホワイトラベル可能なチケットシステムのTripleがあります。
Crayon
Crayonは、一言で言えばノーコードで作成できるファンクラブアプリで、例えばアーティストのタイムラインや、ファン同士が交流したり、アーティストのライブ情報などの告知、生配信が行える機能が実装されたアプリと、そのアプリの機能やデザインなどのカスタマイズが行えるCanvasからなるノーコードアプリ開発プラットフォームです。
ファンクラブアプリを色々なアーティストに広げていくためにマルチテナントアーキテクチャを当初から設計思想として組み込んでいました。その後、CanvasやBoilerplateというノーコード基盤が開発され、爆速でのiOS/Androidのアプリリリースが可能になりました。
フロントを主に担当しているクリエイティブ・マネージャーの今西が言うには、
「お客さんとの一発目の打ち合わせから 初回のアプリが出てくるまでが異様に早い」
「むしろお客さんによっては初回の打ち合わせで もうアプリ作ってきましたので見てくださいっていうパターンもあったぐらい追い越してくる。それぐらいローコストで作れる」
とのこと。
ノーコードでアプリを作成
開発の難所
Crayonの開発の難所を聞いてみると、アプリは常に最新版となるWebとは違ってバージョンが混在し、しかもそのアプリはカスタマイズ可能であるため管理画面のバージョン管理も必要となり、バージョン管理が複数軸になってしまうことだそうです。
バージョン管理の軸が複数存在する
また、藤井風さんなどが通知を打つとサーバーの負荷が跳ね上がる「藤井風砲」[7]が発生することがあるため、通知するユーザーを分散する仕組みなどを取り入れるなど大規模なユーザーを持つアプリ特有の仕組みがあります。
Caravan
Caravanはアーティストのライブ会場などで使われる物販アプリで、機能的にはお客さん側のアプリとスタッフ向けのレジアプリ、そして販売する商品などを管理する管理画面と3つに大別されます。
まず某有名アーティストさんのために作ったものが、オーダーシート機能で、注文リストを事前にお客さんが作れるというものです。会場に来た際にQRコードをスタッフ側のレジアプリで読み取り、決済までするという仕組みです。このアプリ以前はレジに来て、これとこれとこれと言ってたものが、QR見せるだけで良くなり、お会計に関しても、お支払いいくらですよ、カードでとか現金でとかやっていたことが、QRを提示するだけで、決済方法を登録していればすぐに決済をすることができます。Caravanによって回転率がとても上がり、1日目だけで2日目の在庫まで枯渇し、回転が早くなりすぎてダンボールの山を潰すのがボトルネックになり始めたくらいでした。
ただその後、ツアーの途中でCOVID-19が襲い、ツアーは中止になってしまいました。エンジニアはその間、追加の機能開発などを行っていました。
プレ(事前)ピッキング機能はその後にホロライブさんのイベントのために開発された機能で、お客さんが入場したタイミングでまずQRを読み取ることで、在庫エリアの特定のゾーンに商品がキューされます。ゾーンは複数に分かれており、広い会場でも、並ぶ列と近いゾーンの在庫が確保されます。このキューに応じて100人ほどのピッキング部隊が商品をピックアップし、番号が割り当てられたカゴに詰め、レジエリアに渡すというものです。レジ数は50台ほどあるという大規模な構成です。
現場に持ち込まれたレジ係やピッキングチームに使われる大量の端末
これによってボトルネックだったピッキング作業も効率化され、非常に高いスループットでお客さんの列が捌けるようになりました。
人間Sidekiq
しかし、エルゴヒューマンに座り、空調のよく調節された部屋で、美味しいコーヒーをすすりながら優雅にコードを書いている時には予期できないことはいくらでも起こるものです。
プレピッキングは事前販売と注文リストの場合の2種類に対応しています。事前販売についてはお客さんは会場に来る前に在庫を抑えるのですが、注文リスト機能については会場で注文する商品を決めるため、並んでいる間に売り切れが発生することがあります。
例えばお客さんがタオル、Tシャツ、キーホルダーが欲しいとしましょう。まずタオルが売り切れの場合を考えてみます。
その場合、ピックアップするスタッフが在庫切れを登録できる機能があり、タオルの在庫が0であることを登録します。そうするとレジで売り切れがわかり、お客さんに「タオルが売り切れですがいかがなさいますか?」と聞くことができます。
しかし、タオル、Tシャツ、キーホルダー全てが売り切れの場合、数量0のカゴができるというバグがありました。
この"エラーカゴ"は誰にも交換されないカゴで、ロックされ続けてしまうため、QRを読んで使おうとするとエラーになるのです。そのエラーカゴの写真を撮って、サーバーチームがデータベースをいじって修正するという"人間Sidekiq"によりエラーカゴの解放を行っていました。
開発時に予測できないトラブルは人間Sidekiqで臨機応変に対応する
一発限り
アーティストのライブイベントで使われるシステムという性質上、スケジュールは変更不可能で、一発勝負が求められます。当然ながらバグや負荷の対策はするものの、完璧は存在し得ないため、その場での現場力も求められます。
現地の物販待機列をものすごい勢いで捌いていく
一発目の某有名アーティストさんのツアーで、外部ライブラリのion-datetimeのバグを踏んだことがありました。
露見したのは、ライブ前日にもかかわらずクレジットカードが登録できないという事象です。
クレジットカードの入力要素として、有効期限の年・月を入力する部分があります。ion-datetimeにはMM/YYYYのオプションがあり、それを使っていましたが、内部的には日付も渡される仕様でした。その日は1月31日でした。
この実装で、クレジットカードの有効期限が2,4,6,9,11月のユーザーは内部的には例えば存在しない"4月31日"となりエラーとなるのです。しかし運が良かったのか、ライブの日は2月1日の月頭でした。この件については調査を行い、翌日にはクレジットカードが登録できることを検証した上で、一日クレジットカードの登録を見送ってもらうことをアナウンスしことなきを得ました[8]。
他にはAndroidの特定のバージョンでQRを表示するCanvasのバグで読み取れないことがあったのですが、管理画面でユーザーを特定したりレジアプリ側の操作をできるようにしたりして手動で凌ぎつつ、当日の夜にAndroidの審査を出して2日目には間に合わせたり、ホロライブのイベントにおいては、DBの実行計画が乱れてCPUが100%に張り付きめちゃくちゃレスポンスが遅くなるような事象が起きましたが、遅い特定のAPIエンドポイントのレスポンスを手動で作り、CDNにキャッシュをさせて凌ぐといったようなものです。
レジの様子
Caravanは二度目はない一発限りの現場で使われるため、エンジニアが現場に出向いて機動的に運用するといったことも特色です。
オフィスで書くコードだけでシステムが動いているのではなく、コードには書かれない現場での運用も含めてシステムが動いているのです[9]。
SlashGift
SlashGiftはカスタマイズ性に優れたオンラインくじ(Web)で、ちいかわをはじめとした巨大IPに利用されています。このことからSlashGiftはTwoGateのプロダクトの中で最も秒間リクエスト数が多い上、トラフィックのスパイクも非常に鋭くなるといった特色があります。
ちいかわのLINEには友達が500万人ほど存在しており、友達の皆さんにpush通知を送らせていただいています。これによるトラフィックは凄まじいもので、分間430,000リクエスト[10]を記録したこともあります。
排出アニメーションとしてLottieを採用しており、凝ったアニメーションを自由にカスタマイズできることが特長です。
トラフィックと闘う
SlashGiftは当初、くじ在庫はCassandraによって管理されていました。しかしながら思ったよりも性能が出ず、システムの応答が止まってしまうことがありました。[11]そもそも、結果整合的な性質を持つCassandraに対して、一貫性が重要な使い方していました(if付きのupdateとかquorumなど)。つまりCassandraはこのサービスには不適合な役割だったようです。そのため、RDBに立ち返り[12]、PostgreSQLのFOR UPDATE SKIP LOCKEDなどを使って在庫を管理する方針に改められました。
トラフィックスパイクに対応するSREマネージャとCTO[13]
一方、さらに捌けるスループットが上がると、今度は上流の決済ゲートウェイが対応しきれなくなるため、Redisを使ったレートリミットを導入するなどして対策しました。
しかしながら、ちいかわはさらにその上をいくのです。今度はRedisのCPUが100%に張り付き、応答が遅くなり始めました。ElastiCacheのインスタンスサイズを上げることで逃げ切りましたが、原因としては KEYS
を使うという実装上の問題があり、これによりRedisのシーケンシャルスキャンが走ったことでした。
SCAN系のコマンドがあるとキーが増えるごとに負荷が上がっていってしまう
これについても KEYS
などスキャン系のメソッドを排除することで高いスループットを捌けるようになっています。
SlashGift(に限られないが、TwoGateプロダクト全般として)は速いコードを書いてインフラをカリカリチューンし、Gatlingにより負荷試験を行っています。それなら、あとはEC2のオートスケールに任せておけば大丈夫と思うでしょう。
しかしトラフィックのスパイクは針の先よりも鋭く、突然のトラフィックに、アプリケーションどころかAWSのロードバランサーが悲鳴をあげてしまうのです。そのため、多くのトラフィックが予期されるイベントの前にはSREマネージャーの宮崎がロードバランサーを暖める暖機作業を行うというのが通例になっています。
負荷試験を何度も繰り返して“予想できない”ことを減らしていく
この件でのパフォーマンスチューニングの話などはまたいつか記事にしたいと思っています。
TRIPLE
TRIPLEは、マルチテナント・マルチブースに対応したチケッティングシステムで、こちらも各アーティストに特化したカスタマイズが可能なものとなっています。こちらもWebベースのシステムで、鬼滅祭や宇多田ヒカルさんなどに使われた実績があります。
このシステムはもともとある物流会社さんの既存システムをリプレイスするということで作られたものなのですが、ビジネスロジックが非常に難解で、TwoGateのシステムの中で設計が最も複雑になりました。
もともとのシステムはマルチテナントではなく、あるコードベースをforkし、それぞれのテナントで独自のカスタマイズを行うといった形で運用されており、カスタム機能についても導入の経緯がわからないなど、あまり整理がされていない状態でした。
それを半年ほどかけて要件を吸い上げて整理しました。
マルチテナントの仕組みを導入しつつもカスタマイズの幅を持たせるため、例えば外部の独自認証でも繋がるように疎な認証システムとする設計になっています。
集大成
TRIPLEは複雑なロジックをRailsで安全に組むため、Sorbetによる型アノテーションを当初から導入しました。Sorbetの型アノテーションファイルのrbiを作成するコンパイラにTapiocaがあるのですが、それを拡張したり[14]して型注釈できる範囲を広げるなど工夫をしていました。
そのほか、主要プロダクトの中では初めて初期段階でYJITの導入とjemallocの導入を行いました[15]。Redisに関してもSidekiq Enterprise用と汎用キャッシュ用を分けるなど、今までの運用で培ったノウハウを集めた集大成的なプロダクトとなっています。
TRIPLEは今後、チケット発券などでCaravanとの連携などが予定されていて[16]、TwoGateのプロダクトのエコシステム間の繋がりがさらに強化されていく予定です。
フロントの話
Angular Schematics
上記のように、マルチテナント構成が多いTwoGateではフロントでの開発時にAngular Schematicsを利用しています。
もしテナントごとにフロントのコードベースが存在すると
- テナントごとにアプリをフルスクラッチで開発するのは非効率
- 共通部分の変更を各テナントに反映させるのが手間
- テナントごとの設定やリソースの管理が煩雑
といった問題があります。
しかしながら要件としてはアーティストごとの特色を出しつつ、共通の機能を提供する必要があります。
そのためTwoGateで作られるアプリのフロントのコードベースは、共通部分が実装されたコアパッケージ・プロジェクトと、それを参照する各テナントのプロジェクトという構造になっています。テナントプロジェクトでは、コアパッケージをインストールした上で、ビルド時にソースコードを再配置し、自プロジェクトのソースコードと一緒にビルドします。これにより、コアパッケージの型情報を保ったまま開発できるようになっています。
Angular Schematicsとは
Angular Schematicsは、Angularプロジェクトの構造を変更するためのコード生成ツールです。CLIコマンドを実行することで、コンポーネントやサービスなどを自動生成できます。
また、カスタムSchematicsを作成することで、プロジェクトに対して任意の操作を行うこともできます。これを活用することで、マルチテナントアプリの開発を効率化しています。
ng-addによるテナントプロジェクトの自動生成
- 必要なライブラリ(ionic, capacitor)のインストール
- ネイティブアプリの設定(AndroidやiOSのビルド設定など)
- Firebase プロジェクトの自動生成(ステージング用とプロダクション用)
- デフォルトのページの生成
マイグレーションによるコアパッケージのアップデート
コアパッケージに変更があった場合は、Schematicsのマイグレーション機能を使って、各テナントプロジェクトに変更を反映させます。
例えば、コアパッケージのバージョンを上げるマイグレーションを作成したとします。コアパッケージをリリースすると、CIが各テナントプロジェクトに対して以下の処理を自動で行います。
- コアパッケージのバージョンを更新
- マイグレーションスクリプトを実行
- 設定ファイルの更新
- 依存ライブラリのバージョン更新
- 既存コードの書き換え
- ビルドとテスト
これにより、コアパッケージの変更を、すべてのテナントプロジェクトに自動で反映させることができます。
フロントエンド・テクニカル・リードの柄澤は自動化がとても好きなので、このようにいかに人の手を動かす部分を減らすかというところでかなりの工夫がされています。
まとめ
TwoGateはこのようにエンタメ業界においてTechで課題を解決していくようなプロダクトを開発しています。
エンタメ業界におけるシステム運用のノウハウについても他にない強みを持っているTwoGateですが、さらに開発や運用における効率化や自動化、より信頼できるシステムの設計、パフォーマンスの最適化など、エンジニアリング的に面白い技術的な挑戦を行っていきたいと思っています。
今後も記事を書いていくのでぜひまた来てくださいね。
-
ペンネーム。日本人です ↩︎
-
この記事を書こうと思い始めたのは4/25、書き始めたのは5/7である。もっと早くから書き始めていればこのようなZennのストレージを圧迫する枕詞は不要となったであろう。 ↩︎
-
プロダクトや環境ごとにAWSアカウントは分離され、ラテラルムーブメントなどを最小限に抑えることができるようなセキュリティ的に堅牢なものとなっている。そのように細かくAWSアカウントが別れていても、TerraformによるIaCでデプロイが可能となっているため、システムの立ち上げは割と素早く行えることも特徴と言える ↩︎
-
当然ながらTwoGateの大多数を占める構成はRailsとIonic/Angularではあるのだが、それは他の構成に挑戦することを必ずしも妨げない。合理的な理由があればどういう構成でも挑戦できると言える。ただしその構成のその後の運用・メンテナンスに問題がなければの話だが… ↩︎
-
ここでいう極端なイデオロギーとは、例えばこの言語以外は許さん!一切の反論を受け付けない!的なもので、議論が通用しないような一方的な原理主義様とした主張と定義している。おそらくTwoGateには、とある言語が好きすぎて、例えば言語の哲学―構文や型システムに代表されるような―に興奮を覚えるような強烈な愛好家が存在しても、一方でその愛が他の言語への攻撃的な容態を示すことはないのである。 ↩︎
-
ホワイトラベルが可能なシステムが多いため、そうとは気づかず使っている方も多いかもしれない。 ↩︎
-
社内ではそう呼ばれている ↩︎
-
この件については、ライブ初日が1/31であった場合ほぼ詰みである…。日付もタイムトラベルしてきちんとテストする必要性がしみじみ感じられる ↩︎
-
織田裕二の名言が思い出される ↩︎
-
= 7,166 req/sec ↩︎
-
原因としてはJavaVMのHeap SizeのミスマッチによるGCのワールドストップ多発やTombstone、設定がデフォルトのままでconcurrencyが足りず、mutation read/write や Native-Transport-Requestsのpendingが残ることによってCPUが高いままになっていたなど色々考えられる。
そのほか、LWT (Light Weight Transaction)やセカンダリインデックスの多用、クエリのALLOW FILTERING、DELETEの多用によるTombstoneの発生などによってパフォーマンス劣化したような感じも見受けらた。 ↩︎ -
CAP定理のCAを重視するならRDBの方が良い ↩︎
-
とそれを見守るCEO ↩︎
-
他の小さいプロダクトではいくつか導入をしていた ↩︎
-
Caravanアプリでチケットを表示させるなど ↩︎
Discussion