go-sqlite3を使ってCloud Spannerエミュレーターを作ってみた / Cloud Spanner emulator with go-sqlite3
by
kazegusuri
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Slide 1
Slide 1 text
go-sqlite3を使って Cloud Spannerエミュレータを つくってみた mercari.go #13
Slide 2
Slide 2 text
@kazegusuri ● Merpay Backend Engineer ● Architect Team 2015/11 2017/01 2018/01 SRE & Platform Platform Architect
Slide 3
Slide 3 text
トークの概要 handy-spannerの説明をしながら 01 SQLiteの話をして Goでどうやっているのか 03 02
Slide 4
Slide 4 text
Cloud Spanner ● Google Cloud Platformが提供するフルマネージドデータベース ○ スキーマ,トランザクション,強整合性,スケーラブル ● Google Cloud Client Libraryを使ってアクセス ○ https://github.com/googleapis/google-cloud-go ● gRPCでAPIを提供 ○ gRPCの定義も公開されている ○ https://github.com/googleapis/googleapis
Slide 5
Slide 5 text
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
Slide 6
Slide 6 text
SQLite ● "SQLデータベースエンジンを実装したインプロセスライブラリ" ○ Full-Featured SQL ○ Extensionによる機能拡張 ● mattn/go-sqlite3 ○ https://github.com/mattn/go-sqlite3 ○ Goのsqlite3用driver ○ database/sqlから操作可能
Slide 7
Slide 7 text
Cloud Spannerエミュレーター ● gRPCのAPIを実装してCloud Spannerの挙動を再現する ● Google Cloud Client Libraryからアクセス先を切り替えて利用する
Slide 8
Slide 8 text
App Google Cloud Client Library Google Cloud Spanner handy-spanner SQLite gRPC gRPC
Slide 9
Slide 9 text
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のトランザクションとして管理
Slide 10
Slide 10 text
handy-spanner StreamingRead “SELECT FirstName, LastName FROM Singers WHERE FirstName = ‘foo’” SQLite SQLite SQL ReadRequest Table = Singers Columns = FirstName, LastName KeySet = ‘foo’ クエリ生成
Slide 11
Slide 11 text
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 クエリ生成 クエリパース
Slide 12
Slide 12 text
何がSQLiteで難しいの? ● SQLiteにないデータ型(配列, 構造体) ● SQLiteにない操作の実現 (UNNESTなど) ● Cloud Spannerの関数 ● トランザクション
Slide 13
Slide 13 text
配列と構造体の実装 ● SQLiteのJSON型を利用 ● 配列はJSON ARRAY ○ e.g. [1, 2, 3], ["foo", "bar"] ● 構造体はJSON OBJECT ○ ただしフィールドに順序があるのでkey, valueを配列で扱う ○ e.g. {"keys": ["x", "y"], "values": [1, 2]}
Slide 14
Slide 14 text
配列と構造体は組み合わせ可能 ● 配列の構造体の配列の構造体みたいなのが可能 ○ もちろん配列の要素型や構造体のフィールドは任意 ● 読み書き前に型の定義は正確にわかっている ○ e.g. ARRAY>> ● DataValue型 ⇔ JSONの相互変換を実現したい ○ DataValueは型定義と値を保持したstruct []struct { x int64 y []string } type DataValue struct { Type DataType Value interface{} } type DataType struct { // INT64, STRING... }
Slide 15
Slide 15 text
JSONとの相互変換 ● json.Marshalerとjson.Unmarshalerを実装する ○ DataValue型からJSONへの変換処理をMarshaler ○ JSONからDataValue型への変換処理をUnmarshaler ● 型の定義を見ながら再帰的に展開していく func (v *DataValue) MarshalJSON() ([]byte, error) { } func (v *DataValue) UnmarshalJSON([]byte) error { }
Slide 16
Slide 16 text
データベースから透過的に扱いたい ● 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)) }
Slide 17
Slide 17 text
関数 ● Cloud Spannerには大量の関数がある ○ 集計関数, 数学関数, 文字列関数, 配列関数, タイムスタンプ関数... ○ https://cloud.google.com/spanner/docs/functions-and-operators ● 全ての関数がSQLiteに存在する訳ではない ○ ABS() など基本的なものはある ○ 配列関数や, 文字列関数などほとんどの関数はない
Slide 18
Slide 18 text
関数 ● リテラルやパラメータなどは入力値がわかるのでクエリ変換可能 ● データベースの値に対する操作はできることに制限がある… 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
Slide 19
Slide 19 text
SQLiteのデータを操作したい ● SQLite上のデータはアプリケーションから直接操作できない ○ 出力時にScanner, 入力時にValuerを通すことで加工はできる ○ JSONのようにExtensionによりSQLite側に直接ロジックも追加可能 Extension SQLite handy-spanner JSON driver Scanner Valuer
Slide 20
Slide 20 text
SQLiteのカスタム関数 ● SQLiteのクエリからGoの関数が呼び出せる ○ 任意の値を受け取って、任意の値を返す関数 Extension SQLite handy-spanner JSON driver Scanner Valuer Function
Slide 21
Slide 21 text
カスタム関数の実装 ● Goの関数の引数と戻り値がカスタム関数と一致 ○ interface{}で任意の型も可能 ○ 可変長引数も可能 ● エラーも返せる SIGN() 関数の実装 SELECT SIGN(3) // => 1 SELECT SIGN(-2) // => -1
Slide 22
Slide 22 text
EXTRACT() 関数の実装 カスタム関数の実装
Slide 23
Slide 23 text
カスタム関数の登録 ● SQLiteドライバーを再登録する ○ github.com/mattn/go-sqlite3.SQLiteConnのRegisterFuncで関数を登録 ● database/sql.Openで登録したドライバを指定
Slide 24
Slide 24 text
カスタム関数を使えば ● 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);
Slide 25
Slide 25 text
カスタム関数を使えば ● 仮想的にデータ型も定義可能なはず! ● 例えば、位置情報型(Geometry) ○ SQLiteには"(100, 200)"のような文字列で保存 ○ 比較(=)などのオペレーターは関数に置き換える SELECT tbl.pos1 = tbl.pos2 クエリ変換 SELECT GEO_EQUAL(tbl.pos1, tbl.pos2)
Slide 26
Slide 26 text
カスタム関数を使う場合の注意 ● rows.Err()のチェックは必須 ○ カスタム関数でエラーが発生時にScanやク エリはエラーにならない
Slide 27
Slide 27 text
時間があればトランザクションの難しさを… ● SQLiteはスキーマレベル Lock ○ 誰かがロックを取るの他の全員がロック待ちになる ● Shared Cache Modeにするとテーブルレベル Lock ○ 誰かがテーブルを読み込むとRead Lock ○ 誰かがテーブルを書き込むとWrite Lock ○ Read Lock中はReadはできるがWriteはできない ○ Write中は他の誰もReadできない ● ずっとReadしているやつがいるといつまでもWriteできない…!
Slide 28
Slide 28 text
handy-spannerのトランザクション管理 ● SQLiteのトランザクションがどのテーブルをどうロックしているか全部管理 ● トランザクションの優先度をつける ○ 必要に応じて任意のトランザクションを終了させる ○ 通常ならデッドロックするケースでも動くようになる
Slide 29
Slide 29 text
まとめ ● エミュレーションは面白い ○ エミュレーション対象のことがわかる ● SQLiteのハックは面白い ○ Goの拡張だけでなくC拡張も使えばもっとできることは広がる ● 開発デバッグ目的だけじゃなくプロダクション用途にも発展可能 ○ S3互換ストレージのような ○ CQL(Cassandra)とかJQL(Jira) みたいなクエリベース ○ Redisみたいなコマンドベース