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

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

kazegusuri
February 10, 2020

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

kazegusuri

February 10, 2020
Tweet

More Decks by kazegusuri

Other Decks in Technology

Transcript

  1. go-sqlite3を使って
    Cloud Spannerエミュレータを
    つくってみた
    mercari.go #13

    View Slide

  2. @kazegusuri
    ● Merpay Backend Engineer
    ● Architect Team
    2015/11 2017/01 2018/01
    SRE & Platform Platform Architect

    View Slide

  3. トークの概要
    handy-spannerの説明をしながら
    01
    SQLiteの話をして
    Goでどうやっているのか
    03
    02

    View Slide

  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

    View Slide

  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

    View Slide

  6. SQLite
    ● "SQLデータベースエンジンを実装したインプロセスライブラリ"
    ○ Full-Featured SQL
    ○ Extensionによる機能拡張
    ● mattn/go-sqlite3
    ○ https://github.com/mattn/go-sqlite3
    ○ Goのsqlite3用driver
    ○ database/sqlから操作可能

    View Slide

  7. Cloud Spannerエミュレーター
    ● gRPCのAPIを実装してCloud Spannerの挙動を再現する
    ● Google Cloud Client Libraryからアクセス先を切り替えて利用する

    View Slide

  8. App
    Google Cloud
    Client Library
    Google Cloud
    Spanner
    handy-spanner
    SQLite
    gRPC gRPC

    View Slide

  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のトランザクションとして管理

    View Slide

  10. handy-spanner
    StreamingRead
    “SELECT FirstName, LastName FROM Singers WHERE
    FirstName = ‘foo’”
    SQLite
    SQLite SQL
    ReadRequest
    Table = Singers
    Columns = FirstName, LastName
    KeySet = ‘foo’
    クエリ生成

    View Slide

  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
    クエリ生成
    クエリパース

    View Slide

  12. 何がSQLiteで難しいの?
    ● SQLiteにないデータ型(配列, 構造体)
    ● SQLiteにない操作の実現 (UNNESTなど)
    ● Cloud Spannerの関数
    ● トランザクション

    View Slide

  13. 配列と構造体の実装
    ● SQLiteのJSON型を利用
    ● 配列はJSON ARRAY
    ○ e.g. [1, 2, 3], ["foo", "bar"]
    ● 構造体はJSON OBJECT
    ○ ただしフィールドに順序があるのでkey, valueを配列で扱う
    ○ e.g. {"keys": ["x", "y"], "values": [1, 2]}

    View Slide

  14. 配列と構造体は組み合わせ可能
    ● 配列の構造体の配列の構造体みたいなのが可能
    ○ もちろん配列の要素型や構造体のフィールドは任意
    ● 読み書き前に型の定義は正確にわかっている
    ○ e.g. ARRAY>>
    ● DataValue型 ⇔ JSONの相互変換を実現したい
    ○ DataValueは型定義と値を保持したstruct
    []struct {
    x int64
    y []string
    }
    type DataValue struct {
    Type DataType
    Value interface{}
    }
    type DataType struct {
    // INT64, STRING...
    }

    View Slide

  15. JSONとの相互変換
    ● json.Marshalerとjson.Unmarshalerを実装する
    ○ DataValue型からJSONへの変換処理をMarshaler
    ○ JSONからDataValue型への変換処理をUnmarshaler
    ● 型の定義を見ながら再帰的に展開していく
    func (v *DataValue) MarshalJSON() ([]byte, error) {
    }
    func (v *DataValue) UnmarshalJSON([]byte) error {
    }

    View Slide

  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))
    }

    View Slide

  17. 関数
    ● Cloud Spannerには大量の関数がある
    ○ 集計関数, 数学関数, 文字列関数, 配列関数, タイムスタンプ関数...
    ○ https://cloud.google.com/spanner/docs/functions-and-operators
    ● 全ての関数がSQLiteに存在する訳ではない
    ○ ABS() など基本的なものはある
    ○ 配列関数や, 文字列関数などほとんどの関数はない

    View Slide

  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

    View Slide

  19. SQLiteのデータを操作したい
    ● SQLite上のデータはアプリケーションから直接操作できない
    ○ 出力時にScanner, 入力時にValuerを通すことで加工はできる
    ○ JSONのようにExtensionによりSQLite側に直接ロジックも追加可能
    Extension
    SQLite
    handy-spanner
    JSON
    driver
    Scanner Valuer

    View Slide

  20. SQLiteのカスタム関数
    ● SQLiteのクエリからGoの関数が呼び出せる
    ○ 任意の値を受け取って、任意の値を返す関数
    Extension
    SQLite
    handy-spanner
    JSON
    driver
    Scanner Valuer Function

    View Slide

  21. カスタム関数の実装
    ● Goの関数の引数と戻り値がカスタム関数と一致
    ○ interface{}で任意の型も可能
    ○ 可変長引数も可能
    ● エラーも返せる
    SIGN() 関数の実装
    SELECT SIGN(3) // => 1
    SELECT SIGN(-2) // => -1

    View Slide

  22. EXTRACT() 関数の実装
    カスタム関数の実装

    View Slide

  23. カスタム関数の登録
    ● SQLiteドライバーを再登録する
    ○ github.com/mattn/go-sqlite3.SQLiteConnのRegisterFuncで関数を登録
    ● database/sql.Openで登録したドライバを指定

    View Slide

  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);

    View Slide

  25. カスタム関数を使えば
    ● 仮想的にデータ型も定義可能なはず!
    ● 例えば、位置情報型(Geometry)
    ○ SQLiteには"(100, 200)"のような文字列で保存
    ○ 比較(=)などのオペレーターは関数に置き換える
    SELECT tbl.pos1 = tbl.pos2
    クエリ変換
    SELECT GEO_EQUAL(tbl.pos1, tbl.pos2)

    View Slide

  26. カスタム関数を使う場合の注意
    ● rows.Err()のチェックは必須
    ○ カスタム関数でエラーが発生時にScanやク
    エリはエラーにならない

    View Slide

  27. 時間があればトランザクションの難しさを…
    ● SQLiteはスキーマレベル Lock
    ○ 誰かがロックを取るの他の全員がロック待ちになる
    ● Shared Cache Modeにするとテーブルレベル Lock
    ○ 誰かがテーブルを読み込むとRead Lock
    ○ 誰かがテーブルを書き込むとWrite Lock
    ○ Read Lock中はReadはできるがWriteはできない
    ○ Write中は他の誰もReadできない
    ● ずっとReadしているやつがいるといつまでもWriteできない…!

    View Slide

  28. handy-spannerのトランザクション管理
    ● SQLiteのトランザクションがどのテーブルをどうロックしているか全部管理
    ● トランザクションの優先度をつける
    ○ 必要に応じて任意のトランザクションを終了させる
    ○ 通常ならデッドロックするケースでも動くようになる

    View Slide

  29. まとめ
    ● エミュレーションは面白い
    ○ エミュレーション対象のことがわかる
    ● SQLiteのハックは面白い
    ○ Goの拡張だけでなくC拡張も使えばもっとできることは広がる
    ● 開発デバッグ目的だけじゃなくプロダクション用途にも発展可能
    ○ S3互換ストレージのような
    ○ CQL(Cassandra)とかJQL(Jira) みたいなクエリベース
    ○ Redisみたいなコマンドベース

    View Slide