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

Cloud Spannerと上手く付き合うコツ

Cloud Spannerと上手く付き合うコツ

\積極的に技術発信を行なっております/

▽ Twitter/COLOPL_Tech
https://twitter.com/colopl_tech

▽ connpassページ
http://colopl.connpass.com

▽ COLOPL Tech Blog
http://blog.colopl.dev

COLOPL Inc.

June 30, 2023
Tweet

More Decks by COLOPL Inc.

Other Decks in Technology

Transcript

  1. 氏名  : 部署  : • 2020年度新卒入社(4年目) • 運用タイトルのサーバーエンジニアとして バリバリ運用開発中 •

    最近「セメント」と呼ばれる減量飯を食べ続け て-10kgのダイエットに成功したらしい 西川 蒼太郎 2 自己紹介 技術基盤本部 第2バックエンドエンジニア部 第1グループ 第1チーム
  2. 今日お話する内容 • Cloud Spanner とは? • 開発する上での Cloud Spanner との付き合い方

    ◦ トランザクションの扱い ◦ インターリーブ ◦ ステイル読み取り 4
  3. • 当然 SQL が使えるし、トランザクションもしっかりある • 自前シャーディングが一切不要 • スケールイン・アウトが非常に容易 7 Cloud

    Spanner の特徴 MySQL Spanner シャーディング 自前シャーディングが必要 内部で自動シャーディング! スケールイン・アウト オペレーションが大変 WebUIからワンクリックで完了! 接続先管理 どのシャードに接続するか アプリ側で管理が必要 管理不要! コロプラにおける MySQL 運用との比較
  4. • だいたいその通り • ただし Spanner の特性に寄り添った設計・実装は不可欠 ◦ Spanner ≠ MySQL

    • 現場で意識すると良いポイントを3点ほど紹介 ◦ トランザクションの扱い ◦ インターリーブ ◦ ステイル読み取り 8 Cloud Spanner は夢のデータベース?
  5. • 読み書きトランザクション(Read-Write) ◦ MySQL(InnoDB) の SERIALIZABLE とだいたい同じ感覚 ◦ select したデータに問答無用で共有ロックをかける

    • 読み取り専用トランザクション(Read-Only) ◦ MySQL(InnoDB) の REPEATABLE READ とだいたい同じ感覚 ◦ ロックを取らずに select できる ◦ ただし更新処理はできない • ゲームバックエンドの処理は大抵更新処理を伴う • =ほぼ Read-Write しか使わない • =複数のトランザクションでロックが衝突しやすい! 10 Spannerにおける2種類のトランザクション
  6. • Abort されたトランザクションはリトライされる ◦ リトライされる前提でコードを書く必要アリ ◦ (Spanner クライアントの実装による) • Spanner

    以外のデータを更新する際は常に注意 ◦ static 変数や Redis のキャッシュなど 12 トランザクションのリトライ // Transaction の中で... DB::transaction(function (){ ... // static 変数をインクリメントしたり static::$value++; // Redis に値を詰めたり Redis::set('key', $value); }); NG例
  7. • 基本的に static 変数は使わない方がよい • もし使いたい場合、ロールバックイベントに初期化処理を登録する 13 リトライを意識したコード例① static $value

    = null; // トランザクションがロールバックしたら値を初期化する app()['events']->listen(TransactionRolledBack::class, function () { $value = null; }); OK例
  8. • キャッシュ更新, キューイングなどは commit 後のコールバックとして登録する 14 リトライを意識したコード例② DB::transaction(function () {

    ... // commit 後のコールバックに処理を登録する DB::afterCommit(function ($value){ // Redis に値を詰めたり Redis::set('key', $value); // キューにデータを詰めたり Job::dispatch($value); // 重要なログを出力したり(調査用ログなど) Log::info("調査用ログ", ["value" => $value]); }); }); OK例
  9. 16 Spannerの分散アーキテクチャ クライアント Node Node Node Node Node (Spanner Servers)

    4TBまで管理できるサーバー Colossus(分散ストレージ) 実データはここに格納される 各 Node は 複数の Split の オーナー Split 参考:Cloud Spanner のハイレベルアーキテクチャ解説
  10. • Split を跨ぐクエリはパフォーマンス劣化につながる ◦ 低QPS→高QPSになるまで顕在化しないケースも ◦ できるだけ Split を跨がないクエリが肝要 •

    そのための「インターリーブ」 ◦ 特定のデータに親子関係を付与できる ◦ 親子データは物理的に同じ Split に配置される 17 Spannerの分散アーキテクチャ
  11. Splitを跨ぐ • 軽い気持ちで Split を跨ぐ処理を書いてみる • (例)自分の所持アイテムを取得する 18 User (PK)

    UserId … UserItem (複合PK) - UserId - UserItemId ItemId … SELECT * FROM UserItem WHERE UserId = "自分のUserId";
  12. インターリーブしないとデータごとに Split がバラバラ 19 Splitを跨ぐ Split Split Split Split Aさん

    User Bさん UserItem Aさん UserItem Bさん User Aさん UserItem Cさん User … …
  13. Splitを跨がない • インターリーブを適用してみる • 「User:UserItem=親:子」の関係にする 20 SELECT * FROM UserItem

    WHERE UserId = "自分のUserId"; CREATE TABLE UserItem ( userId STRING(36) NOT NULL, userItemId STRING(36) NOT NULL, itemId INT64 NOT NULL, ... ) PRIMARY KEY(userId, userItemId), INTERLEAVE IN PARENT User ON DELETE CASCADE
  14. • Spanner のテーブル設計においてインターリーブはマスト • コロプラでは User を親にした設計がスタンダード 22 インターリーブの例 User

    Aさん UserChara UserItem … Split User Bさん UserChara UserItem … User Cさん UserChara UserItem … Split … … …
  15. • 「ステイル読み取り」を駆使して可能な限りパフォーマンスを上げる • 過去のタイムスタンプを使った読み取りになる ◦ 整合性:✕ ◦ パフォーマンス:◯ • ロックを取らない

    ◦ Read-Write トランザクション内でもノーロックで select が可能 ▪ トランザクションから独立した読み取りになる ◦ ロック解放待ちの時間が減る! ◦ Abort(リトライ)の危険性も減る! 28 ステイル読み取りする
  16. • Split には「リーダー」「レプリカ」の2種類が存在する ◦ リーダーは常に最新 ◦ レプリカはちょっと古いことがある • 過去のタイムスタンプを指定すると 「そのタイムスタンプより新しいデータを持つレプリカ」から

    データを読み取ることができる 29 過去のタイムスタンプを使った読み取り? リーダー レプリカ レプリカ ちょっと古いことがある 10秒に一度 リーダーと同期する 常に最新