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

handy-spanner GCPUG

kazegusuri
February 06, 2020

handy-spanner GCPUG

kazegusuri

February 06, 2020
Tweet

More Decks by kazegusuri

Other Decks in Technology

Transcript

  1. @kazegusuri • Merpay Backend Engineer • Architect Team 2015/11 2017/01

    2018/01 SRE & Platform Platform Payment & Architect
  2. 神降臨!? • そんな中突如出現したGoogle製のCloud Spannerのfake実装 (spannertest) ◦ googleapis/google-cloud-go の spanner ライブラリの下にひっそりと爆誕

    ◦ gRPCで接続先を切り替えることで本物のSpannerかのように振る舞う ◦ google-cloud-go のテストでもしっかり使われている • ついでにSpannerのSQLをパースするためのライブラリも誕生 (spansql) • これは我々が求めていたエミュレーターなのか…
  3. 現実は厳しい • 圧倒的な機能不足 ◦ 単純なSELECTのみサポート (以前は * も使えなかった) ◦ 一部のデータ型のみサポート

    (以前は TIMESTAMP も使えなかった) ◦ そもそもSQLをパースできないものが多い • 必要(要望)があれば機能を追加すると明言(Issue1689) ◦ だが google-cloud-go リポジトリ… (※) • そもそも雰囲気からすると公式エミュレーターとして提供ではない
  4. spannertestの実装 • Cloud Spannerが提供するgRPCサーバーのAPIを実装 ◦ googleapis/googleapis の spanner.proto など •

    SQLのパースは spansql に依存 ◦ フルスクラッチのGo実装 • データストアなどのデータベースの機能もフルスクラッチ実装 ◦ データはGoの値としてインメモリ、クエリの評価も自前実装 • 結果として外部ライブラリに何も依存しないのでポータブル
  5. コントリビュートして育てたい、が… • だが google-cloud-go リポジトリ… (※) • そもそも Go でフルスクラッチ実装は現実的なのか?

    ◦ クエリエンジンをゼロから作るのはかなり大変そう • そもそもまずspansql直すところからスタート ◦ memefishならフル実装
  6. かっとなって作った • handy-spanner 爆誕 ◦ SQLiteとmemefishを利用したCloud Spannerエミュレーター ◦ spannertestと同様にCloud SpannerのgRPCのAPIを提供

    ◦ 2週間程度(土日)でspannertestと同じ機能を実装 • SQLite ◦ 組み込み型のRDBMS • github.com/MakeNowJust/memefish ◦ SpannerのSQLを全てパース可能
  7. 機能性の比較(リリース当時 2019/10/04) spannertest handy-spanner Data型 TIMESTAMP,STRUCT未サポート STRUCT未サポート Read Keysのみ、PrimaryKeyのみ カラム指定,

    Limit/Offset Keys, KeyRange、SecondaryIndex カラム指定、Limit/Offset Storing Column Query SELECT カラム指定 Limit/Offset カラム指定, *, alias Limit/Offset Query WHERE ほとんどの比較 ほとんどの比較 Query ORDER/GROUP 不可 ORDER BY, GROUP BY, HAVING Query Others JOIN, Subquery, Unnest Transaction 不可 不可 DML 不可 不可
  8. 人類の欲望に際限はない • これでだいたいのユースケースは満たせると期待 ◦ データ型もクエリのサポートもspannertestよりできている ◦ ローカル開発時に使ってもらうことを想定 ◦ 動かないものはそれだけテストをスキップ ▪

    完全にCIでのテストを置き換えることを想定してない • 思ったより使ってもらうのが大変 ◦ 単純にhandy-spannerを使うようにすると動かないテストが多い ◦ 9割〜10割は動かないとみんな諦める ▪ むしろそんなクエリ使っているんか!!
  9. 機能性の比較(v0.3.0 2019/11/23) spannertest handy-spanner Data型 STRUCT未サポート 全部 Mutation REPLACE以外 全部

    Read KeyRange, SecondaryIndex未サポート 全部 Query かなり限定的 ほぼ全部 関数 不可 一部 Transaction 不可 不可 DML 不可 不可 DDL 一部(Drop, Alter系含む) 一部(Createのみ)
  10. ほとんどのユースケースで動くように • 複雑なクエリさえ動けば既存のテストはほぼ動く ◦ JOIN, Subquery, UNNEST, STRUCT ◦ メルカリ社内での導入が進み始めた

    (20+ repos) • 足りていない機能も多い ◦ 関数系, DDL • エッジケースの対応はまだまだ ◦ STRUCT周り ◦ Strict Type Check, Coercing
  11. 機能性の比較(v0.4.0 2020/01/13) spannertest handy-spanner Data型 STRUCT未サポート 全部 Mutation REPLACE以外 全部

    Read KeyRange, SecondaryIndex未サポート 全部 Query かなり限定的 ほぼ全部 関数 不可 一部 Transaction 不可 可能 DML 不可 全部 DDL 一部(Drop, Alter系含む) 一部(Createのみ)
  12. handy-spannerの使い方 • handy-spannerを起動: 方法は大きく2種類 ◦ 独立したプロセスとしてhandy-spannerを起動する (全環境) ◦ Goのライブラリとしてhandy-spannerを起動する (Go専用)

    • クライアントライブラリの接続先を切り替え ◦ Google Cloud Client Library(google-cloud-goなど)なら切り替え方法が用意されているは ず ◦ SPANNER_EMULATOR_HOST 環境変数で接続先を指定など • Go 以外でももちろん使えます!
  13. App Google Cloud Client Library Google Cloud Spanner gRPC spanner.googleapis.com:443

    ・google.spanner.v1.Spanner ・google.spanner.admin.instance.v1.InstanceAdmin ・google.spanner.admin.database.v1.DatabaseAdmin gRPC Service Cloud Spannerへの接続
  14. App Google Cloud Client Library handy spanner gRPC localhost:9999 ・google.spanner.v1.Spanner

    ・google.spanner.admin.instance.v1.InstanceAdmin ・google.spanner.admin.database.v1.DatabaseAdmin gRPC Service Step 1 Step 2 handy-spannerへの接続 1. handy-spannerを起動 2. 接続先を切り替える
  15. handy-spannerの起動 • Docker imageをビルド ◦ $ git clone https://github.com/gcpug/handy-spanner.git ◦

    $ make docker-build • Docker imageを実行(port 9999でListen) ◦ $ docker run --rm -d -p 9999:9999 handy-spanner ◦ インスタンスやデータベースなどは空
  16. クライアントの接続先切り替え • クライアントライブラリによって方法が異なります • SPANNER_EMULATOR_HOST 環境変数を設定する ◦ ライブラリがこの環境変数を見て接続先を切り替えてくれます ◦ e.g.

    $ export SPANNER_EMULATOR_HOST=localhost:9999 • アプリケーションでgRPCクライアントの接続先を明示的に指定 ◦ 接続先のアドレスを指定 (Cloud Spannerではなくhandy-spannerに接続するため) ◦ TLS接続を無効化 (gRPCクライアントがデフォルトTLS接続なので) ◦ 認証処理の無効化 (クライアントライブラリのトークン発行処理)
  17. データベースなどの作成 • Spannerはプロジェクト, インスタンス, データベース, テーブルの階層 ◦ 通常は順番に作っていく必要があるが… • handy-spannerではプロジェクト,

    インスタンス, データベースは自動生成 ◦ アクセスしたときに勝手に作られるので気にしなくて良い ◦ 厳密には CreateSession でセッション作成時にデータベースまで全て作られる • マニュアルで管理可能が後述
  18. テーブル,インデックスなどの作成 • テーブル, インデックスは自身で用意してもらう必要がある ◦ handy-spannerの初期化機能を使う (簡単) ◦ DDLを使ってアプリケーションから操作する (複雑)

    • DDLを使うためには database admin clientを別途用意する必要がある ◦ まずはCloud SpannerでDDLを使うコードを書くのに慣れよう ◦ その後に接続先を handy-spanner に切り替えるように
  19. 動的にテーブルやインデックスを作成 • DDL(データ定義言語)を使う • DDLを使うためには database admin clientを別途用意する必要がある ◦ 通常のSpanner

    clientではDDLのAPIを利用できない ◦ まずはCloud SpannerでDDLを使うコードを書くのに慣れよう ◦ その後に接続先を handy-spanner に切り替えるように • インスタンスなどの操作も instance admin clientを使えば操作可能 ◦ handys-spannerはまだインスタンス操作には未対応 • admin client系は SPANNER_EMULATOR_HOST に対応していないので注意 ◦ 必ずアプリケーションで明示的に接続先を指定する必要がある… (Issue)
  20. App Google Cloud Client Library handy spanner gRPC localhost:9999 ・google.spanner.v1.Spanner

    ・google.spanner.admin.instance.v1.InstanceAdmin ・google.spanner.admin.database.v1.DatabaseAdmin gRPC Service handy-spannerへの接続
  21. gRPCサービスを実装する • Cloud SpannerのgRPC サービス定義は公開されている ◦ このinterfaceを満たすサービスを実装する • google.spanner.v1.Spanner ◦

    セッション、トランザクション、Read/Writeなどの基本機能 • google.spanner.admin.database.v1.DatabaseAdmin ◦ データベース操作(Create, Drop, DDLなど) • google.spanner.admin.instance.v1.InstanceAdmin ◦ インスタンス操作(Create, Drop, ノード数変更など)
  22. 挙動を理解する • Cloud SpannerでSDKを使って操作してみる ◦ e.g. client.Single().Read(ctx, "Sample", spanner.AllKeys(), []string{"Pkey"})

    • 向き先をhandy-spannerに切り替えて確認する ◦ どのgRPCメソッドが呼ばれるか ◦ どんなメッセージが送られてくるか • Cloud SpannerにgRPCで直接つないで操作してみる ◦ 同じメッセージで送ればSDKを使った場合と同じ結果が得られるはず ◦ レスポンスメッセージを確認する ⚠ 悪用はしないでください
  23. ひたすら仕様を調べる • Cloud Spannerの仕様はドキュメントにかなり詳しく書いてある ◦ 普通だったら読み飛ばしてしまうけど実は結構書いてある ◦ Protoのコメントにも有用な情報がある • Cloud

    Spannerを触ってCloud Spannerの気持ちになる ◦ SDKのソースコードを読むと"使い方"が見えてくる ◦ gRPCのメソッドを直接使って異常なケースを試してみる ⚠ 悪用はしないでください
  24. セッションの仕様 • 1セッションでトランザクションを無限に作れる ◦ RW Txは32個まで • セッションは別コネクションで利用できる ◦ 実質1セッションを全サーバーで共有も可能!?

    • セッションにはラベルがつけられる ◦ ListSessionsでラベルによるフィルターが可能 • セッション名には実はルールがある ◦ projects/xxx/instances/xxx/databases/xxx/sessions/AJSwhARkqNlfkTvz2LsMsJrJf83_1kxUQiAY8uPQcq2NpDEsVt1ylIwPz4Xy
  25. Session セッションの構造 ・Active RO Txs ・Active RW Txs ・Inactive Txs

    Database ・Sessions Transaction ・Mode (RO/RW) ・State
  26. ミューテーション • 構造はシンプル ◦ Insert, Update, InsertOrUpdate, Replace, Deleteの操作 ◦

    カラム名の配列とそれに対応する値のリストを渡して書き込む
  27. Spannerのデータ型 • Spannerで使えるデータ型は多い ◦ 真偽値, 整数, 浮動小数点数 ◦ 文字列, バイト列

    ◦ 日付, タイムスタンプ ◦ 構造体, 配列 • 単純にValueにマッピングできなさそう
  28. google-cloud-goがどうしているか Spannerデータ型 Value データ型 形式 サンプル 整数 string 文字列化 “1234”

    文字列 string そのまま “foo” バイト列 string Base64 “Zm9v” (“foo”) 日付 string RFC 3339 “2006-01-02” タイムスタンプ string RFC 3339 “2006-01-02T15:04:05.999999999Z07:00”
  29. データベーススキーマ • DDLでテーブルを作成時にテーブル情報を保持する ◦ 主キー、カラム名や型、Nullableなど ◦ ユニークインデックス • handy-spannerではなるべくCloud Spannerと同じエラーメッセージを再現

    ◦ クエリ実行前に静的解析してエラーをチェック ◦ 実行時には再現が難しい場合がある ◦ ユニーク制約などは実行時じゃないと厳しいのでSQLiteのエラーを元に再現
  30. ミューテーション: InsertOrUpdate • SQLiteでのSQL • ON CONFLICT でInsert失敗時にUpdateする INSERT INTO

    TABLE_NAME (column1, ...) VALUES (value1, ...) ON CONFLICT (prim_column1, ...) DO UPDATE SET column1 = value1, ...
  31. Spannerのデータ型とSQLiteのデータ型 Spannerデータ型 SQLiteデータ型 真偽値 INTEGER 整数 INTEGER 浮動小数点数 REAL 文字列

    TEXT バイト列 BLOB 日付 TEXT タイムスタンプ TEXT • SQLiteのデータ型は少ない ◦ INTEGER ◦ REAL ◦ TEXT ◦ BLOB • 日付とタイムスタンプ ◦ 文字列のままそのまま格納 ◦ 文字列のままでも比較可能! • 配列と構造体をどうする…?
  32. JSON型 • SQLiteはextensionでJSONをデータ型として利用可能 • 基本的な操作はだいたい揃っている ◦ JSON() => 文字列をJSON化 ◦

    JSON_EXTRACT() => Pathの値を取り出す ◦ JSON_REMOVE() => Pathの値を削除 ◦ JSON_INSERT(), JSON_SET(), JSON_REPLACE() => Pathの値を更新
  33. 配列と構造体をJSON型として扱う • 配列はJSON ARRAY ◦ 配列の要素に値がそのまま入る ◦ e.g. [1, 2,

    3] or [“foo“, “bar“] • 構造体はJSON OBJECT ◦ ただしSpannerの構造体はフィールドに順序性がある ◦ フィールド名の配列と値の配列をもったオブジェクト ◦ e.g. {“keys“: [“x”, “y”], “values”: [1, 2]}
  34. 配列や構造体の複雑さ • Spanner固有の操作がSQLiteの限界を超えてくる ◦ IN UNNEST(array), FROM UNNEST(array) ◦ SELECT

    AS STRUCT * FROM table ◦ SELECT t.struct.* FROM table t ◦ SELECT t2.* FROM table t, t.struct t2
  35. 型のProto表現 • Spannerの型は Type メッセージ • TypeCodeが型の種類 ◦ BOOL, INT64,

    STRING, ARRAY, STRUCT… • 構造体はフィールドに順序がある ◦ map[string]Type ではない
  36. 参照系API • Read(StreamingRead)とQuery(ExecuteStreamingSql)の2つのAPI • Read ◦ テーブル, キーの集合, カラムを指定 •

    Query ◦ SQLとパラメータを指定 • レスポンスは共通 ◦ PartialResultSetメッセージがストリームで返ってくる