Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
handy-spanner GCPUG
Search
kazegusuri
February 06, 2020
Technology
2.1k
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
handy-spanner GCPUG
kazegusuri
February 06, 2020
More Decks by kazegusuri
See All by kazegusuri
go-sqlite3を使ってCloud Spannerエミュレーターを作ってみた / Cloud Spanner emulator with go-sqlite3
kazegusuri
5
6.8k
Open SKT: メルペイ開発の裏側 / builderscon tokyo 2019 Open SKT
kazegusuri
22
28k
Keep watching and extending features of gRPC
kazegusuri
3
2.7k
Testing with microservices in merpay
kazegusuri
10
11k
Real World Mercari API Architecture
kazegusuri
1
6.4k
gRPC and REST with gRPC in practice
kazegusuri
19
8.2k
Fluentdで始めるPrometheus / Prometheus Tokyo Meetup #1
kazegusuri
1
2k
GRPCの実践と現状での利点欠点 / Go Conference 2016 Spring
kazegusuri
44
32k
OutputとBufferedOutputの間の何か
kazegusuri
2
3.5k
Other Decks in Technology
See All in Technology
「守りたい体験」を渡すだけで E2E を生成させられるようになった話
hinac0
1
880
個人開発で育てる「大規模設計の苗床」 - AI時代の1人開発から始める業務への知識接続 / The Seedbed for Large-Scale Design - From AI-Era Solo Projects to Professional Knowledge
bitkey
PRO
1
290
Webアプリ認証の全体像 / The Big Picture of Web App Authentication
kitano_yuichi
0
340
ガバナンスの「ちょうどいい落とし所」を探れ!開発スピードを妨げない運用判断の勘所 / SRE NEXT 2026
genda
1
270
第67回コンピュータビジョン勉強会CVPR2026読会前編
tsukamotokenji
0
160
Vポイント分析基盤におけるデータモデリング20年史
taromatsui_cccmkhd
2
220
インシデント事例と パッケージの全量解析に学ぶ ソフトウェアサプライチェーンの守り方 / supply-chain-attack-defense
flatt_security
0
450
AI Native なプロダクト組織の立ち上げ方 : 生産性 100 倍への挑戦
mikesorae
0
630
人とエージェントが高め合う協業設計
kintotechdev
0
360
誤解だらけの開発生産性 / Myths and Misconceptions about Developer Productivity
i35_267
2
900
Making sense of Google’s agentic dev tools
glaforge
1
290
アップデートで何が変わった?デモで学んで使いこなすIBM Bob2.0
muehara
0
200
Featured
See All Featured
Side Projects
sachag
455
43k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.7k
Designing for humans not robots
tammielis
254
26k
Embracing the Ebb and Flow
colly
88
5.1k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
240
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
320
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Chasing Engaging Ingredients in Design
codingconduct
0
240
The agentic SEO stack - context over prompts
schlessera
0
850
The World Runs on Bad Software
bkeepers
PRO
72
12k
Transcript
handy-spanner A Cloud Spanner emulator GCPUG Tokyo Handy Spanner Day
February 2020
@kazegusuri • Merpay Backend Engineer • Architect Team 2015/11 2017/01
2018/01 SRE & Platform Platform Payment & Architect
アジェンダ handy-spannerの概要 01 handy-spannerの作り方 02
handy-spannerの概要
突然のSpannerのハードルが高い理由(一般論) • GCPにロックインされる ◦ ですよね… • ノウハウが少ない ◦ パラダイムは違う。一般的なノウハウは増えてきた •
高い ◦ 最近は1node(月約9万円)からでもSLAがある • 開発環境(ローカル) ◦ それな
開発環境の問題 • やはりクラウドにしかないのは辛い ◦ 開発のためにCloud Spannerを直接使う必要がある • ローカルかのように爆速なら良いが… ◦ 通信へのレイテンシ
◦ データベース作成速度 ◦ スキーマ変更速度 • 軽く試すだけならGCPUG Shared Spannerがあるよ!
神降臨!? • そんな中突如出現したGoogle製のCloud Spannerのfake実装 (spannertest) ◦ googleapis/google-cloud-go の spanner ライブラリの下にひっそりと爆誕
◦ gRPCで接続先を切り替えることで本物のSpannerかのように振る舞う ◦ google-cloud-go のテストでもしっかり使われている • ついでにSpannerのSQLをパースするためのライブラリも誕生 (spansql) • これは我々が求めていたエミュレーターなのか…
現実は厳しい • 圧倒的な機能不足 ◦ 単純なSELECTのみサポート (以前は * も使えなかった) ◦ 一部のデータ型のみサポート
(以前は TIMESTAMP も使えなかった) ◦ そもそもSQLをパースできないものが多い • 必要(要望)があれば機能を追加すると明言(Issue1689) ◦ だが google-cloud-go リポジトリ… (※) • そもそも雰囲気からすると公式エミュレーターとして提供ではない
spannertestの実装 • Cloud Spannerが提供するgRPCサーバーのAPIを実装 ◦ googleapis/googleapis の spanner.proto など •
SQLのパースは spansql に依存 ◦ フルスクラッチのGo実装 • データストアなどのデータベースの機能もフルスクラッチ実装 ◦ データはGoの値としてインメモリ、クエリの評価も自前実装 • 結果として外部ライブラリに何も依存しないのでポータブル
コントリビュートして育てたい、が… • だが google-cloud-go リポジトリ… (※) • そもそも Go でフルスクラッチ実装は現実的なのか?
◦ クエリエンジンをゼロから作るのはかなり大変そう • そもそもまずspansql直すところからスタート ◦ memefishならフル実装
かっとなって作った • handy-spanner 爆誕 ◦ SQLiteとmemefishを利用したCloud Spannerエミュレーター ◦ spannertestと同様にCloud SpannerのgRPCのAPIを提供
◦ 2週間程度(土日)でspannertestと同じ機能を実装 • SQLite ◦ 組み込み型のRDBMS • github.com/MakeNowJust/memefish ◦ SpannerのSQLを全てパース可能
App Google Cloud Client Library Google Cloud Spanner handy-spanner spannertest
memory SQLite gRPC gRPC gRPC
機能性の比較(リリース当時 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 不可 不可
人類の欲望に際限はない • これでだいたいのユースケースは満たせると期待 ◦ データ型もクエリのサポートもspannertestよりできている ◦ ローカル開発時に使ってもらうことを想定 ◦ 動かないものはそれだけテストをスキップ ▪
完全にCIでのテストを置き換えることを想定してない • 思ったより使ってもらうのが大変 ◦ 単純にhandy-spannerを使うようにすると動かないテストが多い ◦ 9割〜10割は動かないとみんな諦める ▪ むしろそんなクエリ使っているんか!!
本気を出した • 一部だけじゃなく全部動くようにする ◦ Subquery全パターン ◦ JOIN全パターン ◦ 算術演算, 集合操作(UNION)
◦ 関数, CAST, リテラル値 ◦ STRUCT型
機能性の比較(v0.3.0 2019/11/23) spannertest handy-spanner Data型 STRUCT未サポート 全部 Mutation REPLACE以外 全部
Read KeyRange, SecondaryIndex未サポート 全部 Query かなり限定的 ほぼ全部 関数 不可 一部 Transaction 不可 不可 DML 不可 不可 DDL 一部(Drop, Alter系含む) 一部(Createのみ)
ほとんどのユースケースで動くように • 複雑なクエリさえ動けば既存のテストはほぼ動く ◦ JOIN, Subquery, UNNEST, STRUCT ◦ メルカリ社内での導入が進み始めた
(20+ repos) • 足りていない機能も多い ◦ 関数系, DDL • エッジケースの対応はまだまだ ◦ STRUCT周り ◦ Strict Type Check, Coercing
社外でも使ってもらえるようにしたい • 他社事例ではやはりDMLを使っている ◦ メルカリではDMLを使っていないので放置していた ◦ DMLにはトランザクション管理が必須 • トランザクション管理するなら本物に近い挙動に ◦
分離レベルをSERIALIAZABLEに ◦ 競合発生時のAbort • また本気出して作ってみた
機能性の比較(v0.4.0 2020/01/13) spannertest handy-spanner Data型 STRUCT未サポート 全部 Mutation REPLACE以外 全部
Read KeyRange, SecondaryIndex未サポート 全部 Query かなり限定的 ほぼ全部 関数 不可 一部 Transaction 不可 可能 DML 不可 全部 DDL 一部(Drop, Alter系含む) 一部(Createのみ)
みんなに使って欲しい! • トランザクションは力技で実装 ◦ SQLiteでは単純には実現できない ◦ 若干制約はあるもののアプリケーションからの挙動はほぼ本物と変わらない • DMLの処理自体は単純 •
試してないけどJavaのJDBC経由でも動くんじゃないかな!
要望に応じて直します • こういう使い方をすると動かないとかあればすぐ直します ◦ ほとんどの場合はすぐ修正可能 ◦ エッジケースの考慮が漏れているだけ ◦ たまにSQLiteの限界を超えていて難しいのもある •
機能的なところも増やす ◦ DDL, admin API (database/instance) • Strictモード的なの作りたい ◦ Coercing禁止とか
handy-spannerの使い方 • handy-spannerを起動: 方法は大きく2種類 ◦ 独立したプロセスとしてhandy-spannerを起動する (全環境) ◦ Goのライブラリとしてhandy-spannerを起動する (Go専用)
• クライアントライブラリの接続先を切り替え ◦ Google Cloud Client Library(google-cloud-goなど)なら切り替え方法が用意されているは ず ◦ SPANNER_EMULATOR_HOST 環境変数で接続先を指定など • Go 以外でももちろん使えます!
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への接続
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. 接続先を切り替える
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 ◦ インスタンスやデータベースなどは空
クライアントの接続先切り替え • クライアントライブラリによって方法が異なります • SPANNER_EMULATOR_HOST 環境変数を設定する ◦ ライブラリがこの環境変数を見て接続先を切り替えてくれます ◦ e.g.
$ export SPANNER_EMULATOR_HOST=localhost:9999 • アプリケーションでgRPCクライアントの接続先を明示的に指定 ◦ 接続先のアドレスを指定 (Cloud Spannerではなくhandy-spannerに接続するため) ◦ TLS接続を無効化 (gRPCクライアントがデフォルトTLS接続なので) ◦ 認証処理の無効化 (クライアントライブラリのトークン発行処理)
クライアントの接続先切り替え(Go)
クライアントの接続先切り替え(PHP) • PHPは @castaneai さんがブログにまとめてくれていた ◦ PHPからhandy-spannerに接続する ◦ https://castaneai.hatenablog.com/entry/2020/01/30/115837 •
他の言語でもやり方わかればまとめたいな!
あとは使うだけ…? • あとはCloud Spannerを使う時と同様にクライアントを使うだけ • ただしいろいろな疑問が… ◦ プロジェクトやインスタンスの生成ってconsoleからやっていたけどどうするの? ◦ データベースはどうやって作る?
◦ スキーマ(テーブルなど)はどうやって作る? ◦ 動的にスキーマ変更(DDL)できますか?
データベースなどの作成 • Spannerはプロジェクト, インスタンス, データベース, テーブルの階層 ◦ 通常は順番に作っていく必要があるが… • handy-spannerではプロジェクト,
インスタンス, データベースは自動生成 ◦ アクセスしたときに勝手に作られるので気にしなくて良い ◦ 厳密には CreateSession でセッション作成時にデータベースまで全て作られる • マニュアルで管理可能が後述
テーブル,インデックスなどの作成 • テーブル, インデックスは自身で用意してもらう必要がある ◦ handy-spannerの初期化機能を使う (簡単) ◦ DDLを使ってアプリケーションから操作する (複雑)
• DDLを使うためには database admin clientを別途用意する必要がある ◦ まずはCloud SpannerでDDLを使うコードを書くのに慣れよう ◦ その後に接続先を handy-spanner に切り替えるように
テーブル,インデックスなどの作成 • handy-spannerのオプションで起動時にスキーマを指定して作成 ◦ $ handy-spanner -project foo -instance bar
-schema -baz -schema schema.sql • Dockerを使う場合はファイルをマウントする必要がある
動的にテーブルやインデックスを作成 • 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)
DDLの利用例
その他たまにある質問 • SQLiteを直接操作(参照、変更)できますか ◦ 今の所できない (やってないだけなので要望があればやる) • 開発でどうやって使う想定ですか(本番以外は全てhandy-spanner使うなど) ◦ 基本おまかせだが、本番適用前のどこかでCloud
Spannerは使う方が良い ◦ まだまだエッジケースでCloud Spannerと挙動が異なることがある • SQLiteだから永続化可能!本番でもCloud Spannerの代わりに使えるね! ◦ GO BOLD
handy-spannerの作り方 ⚠ ここからは Spanner の Deep な解説があります
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への接続
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, ノード数変更など)
挙動を理解する • Cloud SpannerでSDKを使って操作してみる ◦ e.g. client.Single().Read(ctx, "Sample", spanner.AllKeys(), []string{"Pkey"})
• 向き先をhandy-spannerに切り替えて確認する ◦ どのgRPCメソッドが呼ばれるか ◦ どんなメッセージが送られてくるか • Cloud SpannerにgRPCで直接つないで操作してみる ◦ 同じメッセージで送ればSDKを使った場合と同じ結果が得られるはず ◦ レスポンスメッセージを確認する ⚠ 悪用はしないでください
ひたすら仕様を調べる • Cloud Spannerの仕様はドキュメントにかなり詳しく書いてある ◦ 普通だったら読み飛ばしてしまうけど実は結構書いてある ◦ Protoのコメントにも有用な情報がある • Cloud
Spannerを触ってCloud Spannerの気持ちになる ◦ SDKのソースコードを読むと"使い方"が見えてくる ◦ gRPCのメソッドを直接使って異常なケースを試してみる ⚠ 悪用はしないでください
セッション • あらゆる操作にはセッションが必要 ◦ データベース単位でセッションを作る • CreateSession または BatchCreateSessions で作成
セッションの仕様 • 1セッションでトランザクションを無限に作れる ◦ RW Txは32個まで • セッションは別コネクションで利用できる ◦ 実質1セッションを全サーバーで共有も可能!?
• セッションにはラベルがつけられる ◦ ListSessionsでラベルによるフィルターが可能 • セッション名には実はルールがある ◦ projects/xxx/instances/xxx/databases/xxx/sessions/AJSwhARkqNlfkTvz2LsMsJrJf83_1kxUQiAY8uPQcq2NpDEsVt1ylIwPz4Xy
トランザクション • データベースの読み書きにはトランザクションも必要 ◦ ReadOnlyかReadWriteのモードを指定する (PartitionedDMLは忘れる) • ReadOnlyでは Timestamp Boundの指定ができるらしい
Session セッションの構造 ・Active RO Txs ・Active RW Txs ・Inactive Txs
Database ・Sessions Transaction ・Mode (RO/RW) ・State
トランザクションの終了 • トランザクションはRollbackかCommitで終了できる ◦ ただしReadOnlyトランザクションは終了できない… ◦ つまりReadOnlyトランザクションは作りっぱなしでかつ無限に作れる…! • Commit時にMutationを渡すことでデータベースを操作できる ◦
つまりここまでできれば書き込みができる!
ミューテーション • 構造はシンプル ◦ Insert, Update, InsertOrUpdate, Replace, Deleteの操作 ◦
カラム名の配列とそれに対応する値のリストを渡して書き込む
バリュー • 値をProtoで表現 ◦ Null値, 浮動小数点数 ◦ 文字列, 真偽値 ◦
構造体, 配列
Spannerのデータ型 • Spannerで使えるデータ型は多い ◦ 真偽値, 整数, 浮動小数点数 ◦ 文字列, バイト列
◦ 日付, タイムスタンプ ◦ 構造体, 配列 • 単純にValueにマッピングできなさそう
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”
値の型 • Mutationでは値の元の型はわからない ◦ 値をバリデーションするには型を知る必要がある ◦ カラム名から本当の型を知るしかない
データベーススキーマ • DDLでテーブルを作成時にテーブル情報を保持する ◦ 主キー、カラム名や型、Nullableなど ◦ ユニークインデックス • handy-spannerではなるべくCloud Spannerと同じエラーメッセージを再現
◦ クエリ実行前に静的解析してエラーをチェック ◦ 実行時には再現が難しい場合がある ◦ ユニーク制約などは実行時じゃないと厳しいのでSQLiteのエラーを元に再現
ミューテーション: Insert • SQLiteでのSQL INSERT INTO TABLE_NAME (column1, …) VALUES
(value1, …)
ミューテーション: Update • SQLiteでのSQL • 主キーに対応するカラムをWHERE句に置くのがポイント UPDATE TABLE_NAME SET column1
= value1, … WHERE prim_column1 = prim_value1, ...
ミューテーション: Replace • SQLiteでのSQL • Insertとほぼ同じ REPLACE INTO TABLE_NAME (column1,
…) VALUES (value1, …)
ミューテーション: InsertOrUpdate • SQLiteでのSQL • ON CONFLICT でInsert失敗時にUpdateする INSERT INTO
TABLE_NAME (column1, ...) VALUES (value1, ...) ON CONFLICT (prim_column1, ...) DO UPDATE SET column1 = value1, ...
Spannerのデータ型とSQLiteのデータ型 Spannerデータ型 SQLiteデータ型 真偽値 INTEGER 整数 INTEGER 浮動小数点数 REAL 文字列
TEXT バイト列 BLOB 日付 TEXT タイムスタンプ TEXT • SQLiteのデータ型は少ない ◦ INTEGER ◦ REAL ◦ TEXT ◦ BLOB • 日付とタイムスタンプ ◦ 文字列のままそのまま格納 ◦ 文字列のままでも比較可能! • 配列と構造体をどうする…?
JSON型 • SQLiteはextensionでJSONをデータ型として利用可能 • 基本的な操作はだいたい揃っている ◦ JSON() => 文字列をJSON化 ◦
JSON_EXTRACT() => Pathの値を取り出す ◦ JSON_REMOVE() => Pathの値を削除 ◦ JSON_INSERT(), JSON_SET(), JSON_REPLACE() => Pathの値を更新
配列と構造体をJSON型として扱う • 配列はJSON ARRAY ◦ 配列の要素に値がそのまま入る ◦ e.g. [1, 2,
3] or [“foo“, “bar“] • 構造体はJSON OBJECT ◦ ただしSpannerの構造体はフィールドに順序性がある ◦ フィールド名の配列と値の配列をもったオブジェクト ◦ e.g. {“keys“: [“x”, “y”], “values”: [1, 2]}
配列や構造体の複雑さ • 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
型のProto表現 • Spannerの型は Type メッセージ • TypeCodeが型の種類 ◦ BOOL, INT64,
STRING, ARRAY, STRUCT… • 構造体はフィールドに順序がある ◦ map[string]Type ではない
参照系API • Read(StreamingRead)とQuery(ExecuteStreamingSql)の2つのAPI • Read ◦ テーブル, キーの集合, カラムを指定 •
Query ◦ SQLとパラメータを指定 • レスポンスは共通 ◦ PartialResultSetメッセージがストリームで返ってくる
参照系API
クエリの解析 • SQLのパースはmemefishにまかせる ◦ クエリの構造を表現したASTができる • 意味解析しつつSQLite用のクエリを構築 ◦ UNNESTや配列,構造体の扱いを変換する必要がある •
気合で実装していく
クエリの解析 • SQLのパースはmemefishにまかせる ◦ クエリの構造を表現したASTができる • 意味解析しつつSQLite用のクエリを構築 ◦ UNNESTや配列,構造体の扱いを変換する必要がある •
気合で実装していく
レスポンス • PartialResultSetにValueの配列をセット ◦ ValueがRowに相当するもの ◦ つまりValue自体が値の配列 • metadataにカラム名の情報が入る
続きは…
Spannerの気持ちになる • データ構造やアルゴリズムがあっていないと実装がおかしくなる ◦ Spannerの気持ちになることで実装がきれいになる ◦ 配列,構造体の扱いは何度も実装をかえてやっと気持ちに近づいてきた • 気持ちがわかると実装が見えてくる ◦
バリデーションのかかるタイミングや順番などから感じる ◦ なるほどここの処理は別実装になっているんだな〜