go-sqlite3を使ってCloud Spannerエミュレーターを作ってみた / Cloud Spanner emulator with go-sqlite3

59c0cc69a8ad4ca8d26d752b3b795b55?s=47 kazegusuri
February 10, 2020

go-sqlite3を使ってCloud Spannerエミュレーターを作ってみた / Cloud Spanner emulator with go-sqlite3

59c0cc69a8ad4ca8d26d752b3b795b55?s=128

kazegusuri

February 10, 2020
Tweet

Transcript

  1. 4.

    Cloud Spanner • Google Cloud Platformが提供するフルマネージドデータベース ◦ スキーマ,トランザクション,強整合性,スケーラブル • Google

    Cloud Client Libraryを使ってアクセス ◦ https://github.com/googleapis/google-cloud-go • gRPCでAPIを提供 ◦ gRPCの定義も公開されている ◦ https://github.com/googleapis/googleapis
  2. 5.

    handy-spanner • Cloud Spannerの(非公式)エミュレーター (Go) ◦ 現状では一番再現率が高いはず ◦ https://github.com/gcpug/handy-spanner ◦

    https://speakerdeck.com/kazegusuri/handy-spanner-gcpug • SQLパーサーはmemefish (Go) ◦ Cloud SpannerのSQLを正確にパース可能 ◦ https://github.com/MakeNowJust/memefish • ストレージはSQLite
  3. 9.

    Spannerの機能は大きく3つ • RPCによるデータの参照・更新 ◦ e.g. StreamingRead, Mutation(Commit) ◦ SQLiteのRead/Write(クエリ)に変換 •

    SQLによるデータの参照・更新 ◦ e.g. ExecuteStreamingSql, ExecuteSql(DML) ◦ SQLiteのRead/Write(クエリ)に変換 • トランザクション管理 ◦ e.g. BeginTransaction, Commit, Rollback ◦ SQLiteのトランザクションとして管理
  4. 10.

    handy-spanner StreamingRead “SELECT FirstName, LastName FROM Singers WHERE FirstName =

    ‘foo’” SQLite SQLite SQL ReadRequest Table = Singers Columns = FirstName, LastName KeySet = ‘foo’ クエリ生成
  5. 11.

    handy-spanner ExecuteStreamingSql “SELECT * FROM Singers WHERE FirstName = ‘foo’”

    SELECT * FROM WHERE Singers = FirstName ‘foo’ “SELECT * FROM Singers WHERE FirstName = ‘foo’” SQLite Spanner SQL AST SQLite SQL クエリ生成 クエリパース
  6. 13.

    配列と構造体の実装 • SQLiteのJSON型を利用 • 配列はJSON ARRAY ◦ e.g. [1, 2,

    3], ["foo", "bar"] • 構造体はJSON OBJECT ◦ ただしフィールドに順序があるのでkey, valueを配列で扱う ◦ e.g. {"keys": ["x", "y"], "values": [1, 2]}
  7. 14.

    配列と構造体は組み合わせ可能 • 配列の構造体の配列の構造体みたいなのが可能 ◦ もちろん配列の要素型や構造体のフィールドは任意 • 読み書き前に型の定義は正確にわかっている ◦ e.g. ARRAY<STRUCT<x

    INT64, y ARRAY<STRING>>> • DataValue型 ⇔ JSONの相互変換を実現したい ◦ DataValueは型定義と値を保持したstruct []struct { x int64 y []string } type DataValue struct { Type DataType Value interface{} } type DataType struct { // INT64, STRING... }
  8. 16.

    データベースから透過的に扱いたい • database/sql.Scannerとdatabase/sql/driver.Valuerを実装する ◦ Scannerはデータベースから値を取り出す(Scan)するとき ◦ Valuerはデータベースに値を書き込むとき • 実際の値(JSON)への変換はMarshal/Unmarshalにまかせる func

    (v *DataValue) Value() (driver.Value, error) { b, err := v.MarshalJSON() if err != nil { … } return driver.Value(string(b)), nil } func (v *DataValue) Scan(src interface{}) error { s, _ := src.(string) return v.UnmarshalJSON([]byte(s)) }
  9. 17.

    関数 • Cloud Spannerには大量の関数がある ◦ 集計関数, 数学関数, 文字列関数, 配列関数, タイムスタンプ関数...

    ◦ https://cloud.google.com/spanner/docs/functions-and-operators • 全ての関数がSQLiteに存在する訳ではない ◦ ABS() など基本的なものはある ◦ 配列関数や, 文字列関数などほとんどの関数はない
  10. 18.

    関数 • リテラルやパラメータなどは入力値がわかるのでクエリ変換可能 • データベースの値に対する操作はできることに制限がある… SELECT DATE_ADD( DATE "2008-12-25", INTERVAL

    5 DAY); SELECT DATE "2008-12-30"; クエリ変換 SELECT DATE_ADD( @date, INTERVAL 5 DAY); @date = "2008-12-25" SELECT DATE "2008-12-30"; クエリ変換 SELECT DATE_ADD( tbl.created_at, INTERVAL 5 DAY); クエリ変換 Spanner SQL SQLite SQL
  11. 24.

    カスタム関数を使えば • DATE_ADDの機能をX_DATE_ADDと登録した場合 SELECT DATE_ADD( DATE "2008-12-25", INTERVAL 5 DAY);

    クエリ変換 SELECT DATE_ADD( @date, INTERVAL 5 DAY); @date = "2008-12-25" クエリ変換 SELECT DATE_ADD( tbl.created_at, INTERVAL 5 DAY); クエリ変換 Spanner SQL SQLite SQL SELECT X_DATE_ADD( DATE "2008-12-25", INTERVAL 5 DAY); SELECT X_DATE_ADD( @date, INTERVAL 5 DAY); @date = "2008-12-25" SELECT X_DATE_ADD( tbl.created_at, INTERVAL 5 DAY);
  12. 27.

    時間があればトランザクションの難しさを… • SQLiteはスキーマレベル Lock ◦ 誰かがロックを取るの他の全員がロック待ちになる • Shared Cache Modeにするとテーブルレベル

    Lock ◦ 誰かがテーブルを読み込むとRead Lock ◦ 誰かがテーブルを書き込むとWrite Lock ◦ Read Lock中はReadはできるがWriteはできない ◦ Write中は他の誰もReadできない • ずっとReadしているやつがいるといつまでもWriteできない…!
  13. 29.

    まとめ • エミュレーションは面白い ◦ エミュレーション対象のことがわかる • SQLiteのハックは面白い ◦ Goの拡張だけでなくC拡張も使えばもっとできることは広がる •

    開発デバッグ目的だけじゃなくプロダクション用途にも発展可能 ◦ S3互換ストレージのような ◦ CQL(Cassandra)とかJQL(Jira) みたいなクエリベース ◦ Redisみたいなコマンドベース