Upgrade to Pro — share decks privately, control downloads, hide ads and more …

オンラインゲームのRails複数db戦略

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 オンラインゲームのRails複数db戦略

Avatar for Yasutomo Uemori

Yasutomo Uemori PRO

January 16, 2024
Tweet

More Decks by Yasutomo Uemori

Other Decks in Programming

Transcript

  1. 自己紹介 植森 康友 所属: 株式会社Aiming 役職: エンジニア 管理ツー ル開発 WebAPI

    開発 Docker おじさん 好きなgem: rubocop 好きなメソッド: each SNS github: yuemori twitter: wakaba260yen
  2. Aiming オンラインゲー ム・ ソー シャルゲー ムを企画・ 開発・ 運営 開発実績 剣と魔法のログレス(

    ブラウザ版) 剣と魔法のログレス ~ いにしえの女神~( スマホ版) ルナプリ from 天使帝國(New!!) etc...
  3. オンラインゲー ムの特徴 数万~ 数十万規模の同時接続数に耐えられるシステムが要求される トラフィックは時間や時期でかなり偏りがち 時間限定イベント CM・ 広告 ユー ザ数・

    同時接続数は突然跳ね上がることがある 大きく当たったときの負荷に耐えられないといけない ある程度予想は可能だが予想を上回ることも しかし、 まったく当たらないこともある
  4. オンラインゲー ムのデー タの傾向 キャラクター を軸にしたデー タが非常に多い キャラクター 数×N 個 所持アイテム、

    フレンド、 クエストの進行度など 仕様次第で1 つのテー ブルが膨大なデー タを持つことがある 例)1 人のキャラクター は同じアイテムを複数持つことが出来 る 普通のWeb アプリに比べて書き込み頻度が非常に多い
  5. オンラインゲー ムのデー タの傾向 ダウンロー ド数◯000 万人達成! 多くのタイトルはリセマラ数を含んでない数字 リセマラ数を含んだ場合のキャラクター 数は(゚ A゚;)ゴクリ

    サー ビスが当たった場合、 キャラクター 数1 億以上も想定 「1 キャラあたり500 個までアイテムを持てるようにしたい」 → アイテムDB のレコー ド数が大変なことになる こうなるとDB 分割やR/W splitting だけでは対応不能
  6. DB 分割 establish_connection を宣言したクラスに対してconnection_pool が作成される 継承ツリー を遡って最初に見つけたconnection_pool を使う → モデルごとに別々

    のconnection を張るのを避けるために抽象クラスを 導入する c l a s s D B 2 B a s e < A p p l i c a t i o n R e c o r d s e l f . a b s t r a c t _ c l a s s = t r u e e s t a b l i s h _ c o n n e c t i o n : d b 2 e n d c l a s s D b 2 M o d e l A < D B 2 B a s e e n d c l a s s D b 2 M o d e l B < D B 2 B a s e e n d
  7. R/W splitting establish_connection の中でremove_connection している 接続先を切り替えたいときにestablish_connection すると、 切り替 えのたびに再接続が発生する →

    connection を維持したまま切り替えたい場合はR/W それぞれ専用の Model を宣言して切り替えるなどの実装が必要 c l a s s U s e r < A p p l i c a t i o n R e c o r d e s t a b l i s h _ c o n n e c t i o n : u s e r _ r e a d o n l y e n d c l a s s U s e r : : W r i t a b l e < A p p l i c a t i o n R e c o r d e s t a b l i s h _ c o n n e c t i o n : u s e r _ w r i t a b l e e n d R/W だけで良いならswitch_point というgem を使うのが一番
  8. Rails5 以降の動き Rails5.0 からconnection_specification_name というのが追加 同じconnection_specification_name をもつモデル同士で connection を共有できる Rails5.1

    で3‑level database.yml というformat が提案されていた 諸事情により5.1 には入らなかったが一部の機能がmerge され ている 詳しくはrails のPR を参考に connection_specification_name: #24844 3‑level database.yml: #27611 #28095 #28896
  9. デー タ分散のアルゴリズム shard count modulo id % shard 数 +

    1 = 振り分け先のshard 番号 shard 数が変わると計算結果が変わってしまう マッピングテー ブル user_to_shard のようなテー ブルを作る マッピングテー ブル自体のレコー ド数が多くなると辛い hash modulo / mapping ハッシュ関数などを噛ませて値の範囲を絞ってからmodulo や マッピングを行う explicit instagram やpinterest が採用している方法。 id の中にシャー ド番号も織り込む
  10. ID の生成戦略 採番テー ブルを使う auto_increment を使って採番だけ行うテー ブルを作る MySQL ならLAST_INSERT_ID を使う方法が公式で紹介されて

    いる Flickr やモンストなどで採用されている UUID を使う S e c u r e R a n d o m . u u i d オリジナルUID のルー ルを作る twitter: snowflake Instagram: timestamp, shard ID, auto_increment のmod から 64bit のID を生成
  11. 実装検討:DB 分割と同じ実装パター ン c l a s s S h

    a r d 1 B a s e < A p p l i c a t i o n R e c o r d e s t a b l i s h _ c o n n e c t i o n : s h a r d 1 s e l f . a b s t r a c t _ c l a s s = t r u e e n d c l a s s S h a r d 2 B a s e < A p p l i c a t i o n R e c o r d e s t a b l i s h _ c o n n e c t i o n : s h a r d 2 s e l f . a b s t r a c t _ c l a s s = t r u e e n d c l a s s U s e r : : S h a r d 1 < S h a r d 1 B a s e e n d c l a s s U s e r : : S h a r d 2 < S h a r d 2 B a s e e n d 例えばItem がshard1, shard2 を見る場合、 コネクションは共有でき る しかし同じモデルなのに継承先が違うため実装を共有できなくなる concern を使うという手もあるがやりたくない
  12. 実装検討:R/W splitting と同じ実装パター ン c l a s s U

    s e r < A p p l i c a t i o n R e c o r d s e l f . a b s t r a c t _ c l a s s = t r u e e n d c l a s s U s e r : : S h a r d 1 < U s e r e s t a b l i s h _ c o n n e c t i o n : s h a r d 1 e n d c l a s s U s e r : : S h a r d 2 < U s e r e s t a b l i s h _ c o n n e c t i o n : s h a r d 2 e n d 継承先が同じモデルになるため、 実装は共有できる しかし同じDB を見る違うモデル同士でコネクションは共有できなく なる
  13. 実装検討:connection をオー バー ライドする c l a s s S

    h a r d 1 B a s e < A p p l i c a t i o n R e c o r d e s t a b l i s h _ c o n n e c t i o n : s h a r d 1 s e l f . a b s t r a c t _ c l a s s = t r u e e n d c l a s s I t e m < A p p l i c a t i o n R e c o r d s e l f . a b s t r a c t _ c l a s s = t r u e e n d c l a s s I t e m : : S h a r d 1 < I t e m d e f s e l f . c o n n e c t i o n S h a r d 1 B a s e . c o n n e c t i o n e n d e n d Model.connection をオー バー ライドして向き先を変更することで 解決 ActiveRecord の挙動に手を入れないといけない
  14. octopus シャー ディング用gem の中ではスター 数最大( 約2k) 機能はかなり多い シャー ディング、R/W splitting、DB

    分割、etc.. ActiveRecord の中にかなり手を入れているためあまり使いたくない O c t o p u s . r a i l s 5 1 ? のようなメソッドを見るのが辛い association などにも手を入れている
  15. mixed_gauge p r o d u c t i o

    n _ u s e r _ 0 0 1 : < < : * d e f a u l t h o s t : d b ‐ u s e r ‐ 0 0 1 p r o d u c t i o n _ u s e r _ 0 0 2 : < < : * d e f a u l t h o s t : d b ‐ u s e r ‐ 0 0 2 M i x e d G a u g e . c o n f i g u r e d o | c o n f i g | c o n f i g . d e f i n e _ c l u s t e r ( : u s e r ) d o | c l u s t e r | c l u s t e r . d e f i n e _ s l o t _ s i z e ( 1 0 2 4 ) c l u s t e r . r e g i s t e r ( 0 . . 5 1 1 , : p r o d u c t i o n _ u s e r _ 0 0 1 ) c l u s t e r . r e g i s t e r ( 5 1 2 . . 1 0 2 3 , : p r o d u c t i o n _ u s e r _ 0 0 2 ) e n d e n d Z l i b . c r c 3 2 ( k e y ) % 1 0 2 4 の結果で振り分け先を決める 最大スロット数、 範囲内での振り分けなどは自由に決められる シャー ド数が変わると振り分け先が変わるので、 シャー ド追加時は DB 側の対応が必要
  16. mixed_gauge c l a s s U s e r

    < A c t i v e R e c o r d : : B a s e i n c l u d e M i x e d G a u g e : : M o d e l u s e _ c l u s t e r : u s e r d e f _ d i s t k e y : e m a i l e n d U s e r . p u t ! ( e m a i l : ' a l i c e @ e x a m p l e . c o m ' , n a m e : ' a l i c e ' ) a l i c e = U s e r . g e t ( ' a l i c e @ e x a m p l e . c o m ' ) a l i c e . a g e = 1 a l i c e . s a v e ! U s e r . a l l _ s h a r d s . f l a t _ m a p { | m | m . f i n d _ b y ( n a m e : ' a l i c e ' ) } . c o m p a c t シャー ディングキー を指定する どのクラスター を使うか指定する p u t ! g e t ! などのメソッドを使って抽象化
  17. activerecord‑sharding u s e r _ s e q u

    e n c e r : # 採番テー ブル < < : * d e f a u l t h o s t : u s e r _ s e q u e n c e r u s e r _ 0 0 1 : < < : * d e f a u l t h o s t : u s e r ‐ d b ‐ 0 0 1 u s e r _ 0 0 2 : < < : * d e f a u l t h o s t : u s e r ‐ d b ‐ 0 0 2 A c t i v e R e c o r d : : S h a r d i n g . c o n f i g u r e d o | c o n f i g | c o n f i g . d e f i n e _ s e q u e n c e r ( : u s e r ) d o | s e q u e n c e r | s e q u e n c e r . r e g i s t e r _ c o n n e c t i o n ( : u s e r _ s e q u e n c e r ) s e q u e n c e r . r e g i s t e r _ t a b l e _ n a m e ( ' u s e r _ i d ' ) e n d c o n f i g . d e f i n e _ c l u s t e r ( : u s e r ) d o | c l u s t e r | c l u s t e r . r e g i s t e r _ c o n n e c t i o n ( : u s e r _ 0 0 1 ) c l u s t e r . r e g i s t e r _ c o n n e c t i o n ( : u s e r _ 0 0 2 ) e n d e n d
  18. activerecord‑sharding c l a s s U s e r

    < A c t i v e R e c o r d : : B a s e i n c l u d e A c t i v e R e c o r d : : S h a r d i n g : : M o d e l u s e _ s h a r d i n g : u s e r , : m o d u l o d e f i n e _ s h a r d i n g _ k e y : i d i n c l u d e A c t i v e R e c o r d : : S h a r d i n g : : S e q u e n c e r u s e _ s e q u e n c e r : u s e r b e f o r e _ p u t d o | a t t r i b u t e s | u n l e s s a t t r i b u t e s [ : i d ] a t t r i b u t e s [ : i d ] = n e x t _ s e q u e n c e _ i d e n d e n d e n d シャー ディングキー を指定 クラスター とアルゴリズムを指定 アルゴリズムはmodulo のみ next_sequence_id で採番テー ブルからID を取得
  19. activerecord‑shard_for mixed_gauge, activerecord‑sharding を参考に実装。 内部実装は2 つのgem と大部分が同じ デー タ分散アルゴリズム: ユー

    ザが選択 ID 生成: ユー ザが選択 拡張を追加 シャー ドへのルー ティング方法をユー ザが実装可能にした octopus ライクな u s i n g syntax でシャー ドを指定してモデル をfetch 可能にした コネクション周りはModel.connection のオー バー ライド実装 に変更 クラス名を無名クラスから動的定義( U s e r : : S h a r d F o r 0 0 1 な ど ) にしてassociation 対応可能に
  20. activerecord‑shard_for p r o d u c t i o

    n _ u s e r _ 0 0 1 : < < : * d e f a u l t h o s t : d b ‐ u s e r ‐ 0 0 1 p r o d u c t i o n _ u s e r _ 0 0 2 : < < : * d e f a u l t h o s t : d b ‐ u s e r ‐ 0 0 2 A c t i v e R e c o r d : : S h a r d F o r . c o n f i g u r e d o | c o n f i g | c o n f i g . d e f i n e _ c l u s t e r ( : u s e r ) d o | c l u s t e r | c l u s t e r . r e g i s t e r ( 0 , : p r o d u c t i o n _ u s e r _ 0 0 1 ) c l u s t e r . r e g i s t e r ( 1 , : p r o d u c t i o n _ u s e r _ 0 0 2 ) e n d e n d 設定方法はほぼmixed_gauge と同じ
  21. activerecord‑shard_for c l a s s U s e r

    < A c t i v e R e c o r d : : B a s e i n c l u d e A c t i v e R e c o r d : : S h a r d F o r : : M o d e l u s e _ c l u s t e r : u s e r , : h a s h _ m o d u l o d e f _ d i s t k e y : e m a i l e n d クラスター とアルゴリズムを指定
  22. activerecord‑shard_for A c t i v e R e c

    o r d : : S h a r d F o r . c o n f i g u r e d o | c o n f i g | c o n f i g . r e g i s t e r _ c o n n e c t i o n _ r o u t e r ( : m o d u l o , S i m p l e M o d u l o R o u t e r ) # i n i t i a l i z e r などで登録 c o n f i g . r e g i s t e r _ c o n n e c t i o n _ r o u t e r ( : m a p p i n g , T a b l e M a p p i n g R o u t e r ) e n d ルー ティングをユー ザが実装して登録できる c l a s s S i m p l e M o d u l o R o u t e r < A c t i v e R e c o r d : : S h a r d F o r : : C o n n e c t i o n R o u t e r d e f r o u t e ( k e y ) k e y . t o _ i % c o n n e c t i o n _ c o u n t # s h a r d c o u n t m o d u l o e n d e n d c l a s s T a b l e M a p p i n g R o u t e r < A c t i v e R e c o r d : : S h a r d F o r : : C o n n e c t i o n R o u t e r d e f r o u t e ( k e y ) M a p p i n g T a b l e . f i n d _ b y ! ( k e y ) . s h a r d _ n u m b e r # m a p p i n g e n d e n d
  23. 現在の実装 分散アルゴリズム: マッピングテー ブル シャー ド追加時にデプロイ以外のオペレー ション不要を重視 各デー タの軸となるキャラクターID をキー

    にマッピング ID 生成方法: オリジナルUUID twitter のsnowflake を参考に独自実装 Web サー バ以外でもID の生成が必要なためアルゴリズムを統一
  24. まとめ ActiveRecord の複数DB 実装パター ンはユー スケー スによって選択 自前実装を使うか、gem を使うかもユー スケー

    スによって選択 オンラインゲー ムでは勝機を逃さないための負荷対策が重要となる → 勝機を逃さないために、 負荷に耐えられる設計・ 実装を早めに検討し ておく
  25. We are hiring! Aiming ではRails エンジニアを募集しています。 基盤開発 ゲー ム管理ツー ル開発

    ゲー ムWebAPI 開発 などなど、 興味があれば是非お声がけください!