go-sqlite3を使ってCloud Spannerエミュレーターを作ってみた / Cloud Spanner emulator with go-sqlite3
by
kazegusuri
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
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みたいなコマンドベース