推し活の ハイトラフィックに立ち向かう Railsとアーキテクチャ - Kaigi on Rails 2024
by
Hayato OKUMOTO
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
2024年10月26日 推 し 活 の ハ イ ト ラ フ ィ ッ ク に 立 ち 向 か う R a i l s と ア ー キ テ ク チ ャ 株式会社TwoGate 取締役CTO 奥本 隼
Slide 2
Slide 2 text
奥本 隼 (Hayato OKUMOTO) @falcon_8823 株式会社TwoGate 取締役CTO チーム組成から11年 / 創業8年 長野高専出身 Rails歴10年以上 自己紹介
Slide 3
Slide 3 text
Ruby Sponsor
Slide 4
Slide 4 text
TwoGateの主要なソリューション ライブイベント向け OEM型モバイルオーダーアプリ オンラインくじ ファンクラブアプリ チケットサイト 共通会員ID基盤 Shopify EC構築支援 多数のサービス展開 ライブエンタメ領域に対して、 コンパウンド戦略でサービスを複数展開 しています。
Slide 5
Slide 5 text
Caravan - イベント物販に特化したアプリ
Slide 6
Slide 6 text
特徴 サーバサイドはマルチテナント アプリはOEM型 1アーティスト=1アプリ 短納期での提供 内製向けローコード化 負荷対策に強い
Slide 7
Slide 7 text
事例 TwoGate inc. のApp Storeで一部配信されています。 累計220万DL / 100万ユーザ登録 エンタメ業界、事例を公開しにくい… TwoGateのブースでこっそりお教えします。 累計50アプリほど提供
Slide 8
Slide 8 text
きっと会場内にも ユーザがいるはず
Slide 9
Slide 9 text
技術スタック サーバサイド フロントエンド インフラ
Slide 10
Slide 10 text
Fastly インフラアーキテクチャ 実はシンプル+モノリシックなRailsアプリ ALB nginx nginx Rails App Aurora PostgreSQL ElastiCache Redis (Cache) ElastiCache Redis (Queue) Sidekiq API Request Amazon ECS
Slide 11
Slide 11 text
ピークトラフィック CDN 50,000 RPS ALB / Rails 8,000 RPS 決済エンドポイント 400 RPS 先日660 RPSに記録更新
Slide 12
Slide 12 text
トラフィックに 耐えきれず 障害の経験も
Slide 13
Slide 13 text
Rails × ハイトラフィック 本発表のテーマ
Slide 14
Slide 14 text
推し活 × ハイトラフィック
Slide 15
Slide 15 text
ハイトラフィックに挑む設計 スロークエリをおこさないように実装 CDNでのキャッシュを活用する 後から導入ではなく最初から / CDNでキャッシュするエ ンドポイントは名前空間を切る アプリケーションキャッシュ(Redis)の活用 CDNでのキャッシュがどうしても難しいとき
Slide 16
Slide 16 text
この規模で起きる問題 DBレイヤーでのボトルネックから始まる インデックスの不足 / クエリが悪い / テーブル設計が悪い 実行計画が変わって急に遅くなることも 各種外部APIのレートリミットに引っかかる ピーク性能を出すパラメータチューニングが必要 ALBのスケーリングが間に合わずにエラーが発生する
Slide 17
Slide 17 text
どのように対処しているか Performance Insight / APMによる分析 アプリケーションログの分析 特定条件だけ遅いケース:ある商品だけ在庫が非常に多い 実行計画の解析 ChatGPTに投げ込むと便利 負荷試験
Slide 18
Slide 18 text
ChatGPTで実行計画を解析
Slide 19
Slide 19 text
先着販売 予告した日時に販売が開始する 在庫限りでの販売 受け取り時間枠選択→商品選択→カート→決済の流れ 本発表で扱うプロダクトの機能 ユーザが一同にアクセスし、在庫の奪い合いが始まる。
Slide 20
Slide 20 text
ライブエンタメでの販売の難しさ 在庫を綺麗に売り切りたい 在庫数を大幅に超過してはいけない 決済エラー等によって在庫が浮いたままではいけない ピークトラフィックでサーバダウンできない レスポンスタイムの悪化はユーザの不満になる
Slide 21
Slide 21 text
本発表で扱うテーマ 本発表では、次の2つのテーマについて取り扱います。 高スループットかつ正確な在庫確保のアーキテクチャ 外部決済APIのレートリミットとの向き合い
Slide 22
Slide 22 text
在庫確保 × パフォーマンス Part.1
Slide 23
Slide 23 text
在庫確保の簡易な実装 商品ID 在庫数 販売数 P1 100 20 P2 150 10 P3 50 30 在庫テーブル 商品P1を10購入 UPDATE 在庫テーブル SET 販売数 = 販売数 - 10 WHERE 商品ID = P1;
Slide 24
Slide 24 text
在庫確保の簡易な実装 商品ID 在庫数 販売数 P1 100 10 P2 150 10 P3 50 30 在庫テーブル 商品P1を10購入 UPDATE 在庫テーブル SET 販売数 = 販売数 - 10 WHERE 商品ID = P1; SQLでトランザクションを 学ぶ題材でよく出てくる実装
Slide 25
Slide 25 text
在庫確保の簡易な実装 商品ID 在庫数 販売数 P1 100 20 P2 150 10 P3 50 30 在庫テーブル 商品P1を10購入 UPDATE 在庫テーブル SET 販売数 = 販売数 - 10 WHERE 商品ID = P1; 同時に購入者がいると… 商品P2を20購入 UPDATE 在庫テーブル SET 販売数 = 販売数 - 20 WHERE 商品ID = P2;
Slide 26
Slide 26 text
在庫確保の簡易な実装 商品ID 在庫数 販売数 P1 100 -10 P2 150 10 P3 50 30 在庫テーブル 同時に購入者がいると… 在庫数を確認したタイミングと 更新するまでの間に更新が走れ ば、在庫数を超過する。 解決策 行ロックを導入する
Slide 27
Slide 27 text
在庫確保の安全な実装 商品ID 在庫数 販売数 P1 100 20 P2 150 10 P3 50 30 在庫テーブル 商品P1を10購入 BEGIN; SELECT * FROM 在庫テーブル WHERE 商品ID = P1 FOR UPDATE; -- ここで在庫数 > 販売数を確認 UPDATE 在庫テーブル SET 販売数 = 販売数 - 10 WHERE 商品ID = P1; COMMIT;
Slide 28
Slide 28 text
行ロックを掛けることで正確な数量の販売ができるように しかし、 数百RPSでの在庫確保のワークロードでは… 同じ行を更新するユーザが多数いる ロックの奪い合いでロック待ち, デッドロックが頻発する 在庫確保の安全な実装
Slide 29
Slide 29 text
ロック競合を回避するには =同じデータを同時に書き換えないような構造に変更する 次の疑問 どうやって行を奪い合わないようにすればよい?? 同じ行を同時に書き換えるから競合する
Slide 30
Slide 30 text
在庫テーブルの設計を変える 在庫ID 商品ID ユーザID Z1 P1 Z2 P1 Z3 P1 Z4 P1 在庫テーブル 1行1在庫のテーブルに変える 順番に排出できれば、競合を回避 できるはず。 どのように実装する??
Slide 31
Slide 31 text
FOR UPDATE SKIP LOCKED これを対処する実装は非常に シンプル 行ロックされている行をスキ ップした結果を応答してくれ る PostgreSQL 9.5~ MySQL 8.0~ BEGIN; SELECT * FROM 在庫テーブル WHERE 商品ID = P1 LIMIT 4 FOR UPDATE SKIP LOCKED; -- ここで行数 = 購入数を確認 UPDATE 在庫テーブル SET ユーザID = Ua WHERE 商品ID = P1; COMMIT;
Slide 32
Slide 32 text
在庫テーブルの設計を変える 在庫ID 商品ID ユーザID Z1 P1 Z2 P1 Z3 P1 Z4 P1 在庫テーブル ユーザUaがP1を2行取得&ロック 1. ロックが無い2行取得 SELECT * FROM 在庫テーブル WHERE 商品ID = P1 LIMIT 2 FOR UPDATE SKIP LOCKED;
Slide 33
Slide 33 text
在庫テーブルの設計を変える 在庫ID 商品ID ユーザID Z1 P1 Z2 P1 Z3 P1 Z4 P1 在庫テーブル 2. ユーザUbがP1を3行取得 SELECT * FROM 在庫テーブル WHERE 商品ID = P1 LIMIT 3 FOR UPDATE SKIP LOCKED; Uaがロックしている行をスキップ Z3,Z4の2行だけを返す 足りないので在庫不足で扱う
Slide 34
Slide 34 text
高スループットでの在庫確保が可能に 行ロックされていない行を必ず返答する保証がある 競合することがなくなり、実測で数百TPSでの処理が可 能に 【余談】Solid QueueはDBをバックエンドにしているが、 同様にFOR UPDATE SKIP LOCKEDを使って実現してい る
Slide 35
Slide 35 text
在庫処理まとめ 1在庫1行のデータ構造で管理する 欠点としてテーブルが肥大化するが、販売が終わったら 消すことで解消 FOR UPDATE SKIP LOCKEDで競合を回避する 数百TPSの注文処理を捌ける 先日は200万在庫のテーブルで運用し問題無く捌いた
Slide 36
Slide 36 text
決済 × パフォーマンス Part.2
Slide 37
Slide 37 text
前提 クレジットカード決済には外部のペイメントプロバイダを利用 非通過、非保持化のため 決済関連のAPIレートリミットは20-30 rps程度 stripeでもデフォルトは100 rps 決済会社との交渉や料率の交渉が必要 そもそも400 rps近くまでの緩和は厳しい
Slide 38
Slide 38 text
与信確保 (オーソリ) 決済と在庫確保の流れ 決済できずに宙に浮く在庫を無くすため、この流れで実装。 在庫確保 売上確定 与信開放 (キャンセル) 在庫OK 在庫NG 売り切れた在庫が復活すると、タイミングと件数 によってはクレームが発生するため。
Slide 39
Slide 39 text
本当にあった怖い話 与信確保→在庫確保→売上確定 売上確定時にレートリミットエラー→ロールバック 奇跡的に与信確保できた人でも、 売上確定時にレートリミットエラー 結局、ほとんど誰も買えない事態に。
Slide 40
Slide 40 text
じゃあどうする? そもそも、決済プロバイダのレートを上げることができない 非同期にして後から決済を通知する? 待機してもらう?
Slide 41
Slide 41 text
諦めて待機してもらう いずれにせよ、 解決しない問題なので 諦めました
Slide 42
Slide 42 text
正しく諦める 決済システムの限界よりもリクエストを受け付けな いようにする これ以上捌けないのに処理を発行しない レートリミットを導入
Slide 43
Slide 43 text
レートリミットの実装 CDN/WAFで対応する? 正確性や条件制御が単純なため不採用 お高いプランにするとできたりする 正確なレートリミットのためにRedisで自作
Slide 44
Slide 44 text
決済×パフォーマンスまとめ コロンブスの卵的な対応だが… 限界に対して正しく向き合うことは大事 負荷試験で外部APIをモックするときはレートリミ ット時の挙動も考慮すること
Slide 45
Slide 45 text
最後に… Railsでハイトラフィックを捌いている事例をお伝 えしました TwoGateはライブエンタメ業界に全方位プロダク トを展開していきます
Slide 46
Slide 46 text
ASK THE SPEAKER 以下の場所で質疑をお受け付けします! TwoGate企業ブース FREE AFTER PARTY by TWOGATE & KOMOJU 本日19時開催 『推し活のハイトラフィックに立 ち向かうRailsとアーキテクチャ』 【登壇者】取締役CTO 奥本 『Type on Rails: Railsアプリ ケーションの安全性と開発体験を 型で革新する』 【登壇者】エンジニア 村田 Speakers
Slide 47
Slide 47 text
No content