読まなくてもいい前置き
現代のSEは間違いなく変革期にいると思います。
AIの台頭で単純に特定言語に詳しく実装力があることは、もはやスキルとして意味をなさなくなってきていると私は考えています。
そこでSEにとって重要なのは、実装をしたい内容・要件があがってきたときに、どのように実装するかという仕組み(複数システムをまたぐ設計など)の能力があることだと考えています。
そのため、今回は私が考えて実装した内容を備忘録として、残しておこうと思いました。
概要
データベースシャーディング(テーブルシャーディングではない)ようなシステムで、ユーザーそれぞれの数値をリアルタイムに集計して、表示する仕組みについて考えます。
CQRS(コマンドクエリ責務分離)パターンに近い考えで、リアルタイム性の担保と整合性の担保をいいとこ取りする仕組みを作成したので、その魚拓です。
バックエンドエンジニア向けの話です。
どうような事象に頭を悩ませる人の一助になればと思います。
フロー図
詳細な処理フロー(煩雑かつ長いので折りたたみ)
説明
データストアの構成
memcached(KVS)とMySQL(RDBMS)を併用
MySQLはデータベースシャーディングしているもの(userデータ)
シャーディングなしもの(マスタ(設定)データ)
課題
例として、ユーザーがそれぞれ敵を倒して、ポイントを集め、そのポイントをユーザー全体で合算した値をユーザー全体に見てもらいたいようなゲームがあったとします。
上記のようなデータストアの構成で、それを愚直に実現しようとすると下記の問題が発生します。
- 全シャード横断の集計によるDBの過負荷
ユーザーのデータは複数のMySQLサーバー(シャード)に分割されています。そのため、リアルタイムな全体ポイントを取得するために毎回全シャードに対して集計クエリ(SELECT SUM(pt)など)を実行すると、DBのリソースを激しく消費し、システム全体がパンクする原因になります - リアルタイム表示とレスポンス速度のトレードオフ
ユーザーが画面を開くたびに重い集計処理を行っていては、画面表示のレスポンスが著しく低下します。表示のリアルタイム性を求めれば求めるほど、RDBMSへの参照負荷が高まり、ユーザー体験(パフォーマンス)を損なってしまいます - インメモリキャッシュ(KVS)単体運用時のデータ揮発リスク
表示を高速化するためにMemcachedを利用して、全体ポイントを直接インクリメント(INCR)していく手法は有効ですが、キャッシュは揮発性です。万が一の再起動や障害でキャッシュが飛んでしまうと、全ユーザーで積み上げた全体ポイントの進行度が完全にロストしてしまうという致命的なリスクがあります
上記を避けるようにするとリアルタイムではなく、擬似的な加算処理などで増えてるように見せかけることも考えられますが、この手のイベントで重要なユーザー起因の盛り上がりによる数値爆増などの大切なUXを損なう可能性があります。
解決
CQRS (Command Query Responsibility Segregation)を参考にして、
書き込み(Command)と読み取り(Query)の責務とデータストアを分離します。
書き込みはDB_Shard(個人pt)。
読み取りはMemdから行う構成です。
これにより参照負荷が書き込みDBのパフォーマンスを阻害するのを防いでいます。
リアルタイム性の担保と整合性の担保をいいとこ取りしています。
ユーザーデータはShardingしているが、集計データをshardingしないテーブルに保存して、cacheにヒットしない場合でも、障害とならない堅牢な仕組みになっています。
バッチ実行中のレースコンディション(競合状態)について
バッチが合算値を計算してからMemcachedにズレ補正SETするまでの数秒の間に、ユーザーがポイントを獲得(INCR)するとユーザーが直前に獲得したptが表示上だけ「巻き戻る(消える)」可能性があります。
これについては表示側で「最終更新日時」を記載しておくことで対応しています。
合算値系は特定ptに到達した瞬間が大事なので、pt実態より表示が上振れると問題になります。
(表示上で達成したように見えるけど達成してない状態など)
上記はrollbackで下振れるものなので、表示側の注意喚起で十分です。
結果
最終的には上記で、約5万人程度のアクティブユーザーがいるシステムにて実装・運用しました。
今回は5万人でしたが、より大規模になってもmemdやサーバ構成のスケールで問題なく対応可能である感触でした。
成功しました、万歳🙌 ということで終わりです。
下記は検証や結果の簡単な内容です、会社のものなので細かい数値共有などはできませんがご了承ください。
事前検証
- プレイ会での検証
- 10名程度で1つのサーバにて、pt更新処理を行う
- rollbackなども見られず、表示速度についても体感的な変化は皆無
- QAチームによる検証
- 数値周りの問題はなし
- Locustなどによる負荷テスト
- 検証環境においては、SLAに準じて問題なし
事後結果
- ユーザーからは高評価
- 数値周りのエラーやクレームも皆無
- ログなどから検証環境からの乖離も小さく、サービスとして安定していた