【書き起こし】Cloud SpannerとMerpayの歩み – sinmetal / apstndb 【Merpay Tech Fest 2022】

Merpay Tech Fest 2022 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2022年8月23日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。

この記事は、「Cloud SpannerとMerpayの歩み」の書き起こしです。

@sinmetal:こんにちは。ここからは私と@apstndbさんで「Cloud SpannerとMerpayの歩み」についてお話しします。

自己紹介

@sinmetal:@sinmetalと申します。ソリューションチームに所属しております。元々Google App Engineを使っていましたが、最近はCloud Runを使うようになりました。データベースのCloud Datastoreをよく使用しており、今はCloud Spannerも使っています。よろしくお願いします。

@apstndb:@sinmetalさんと同じソリューションチームに所属しています。2020年に入社してから社内の GCP 周りに携わっており、特にCloud Spanner周りについて、実行計画やClient Librariesの実装などを見つつパフォーマンスを改善しています。よろしくお願いします。

Merpay and Spanner Release History

@sinmetal:まずはMerpayとSpannerのRelease Historyを見て、昔を振り返ってみようと思います。

SpannerがGAになったのは、2017年5月でした。2017年夏にGoogle Cloud Next ’17 in Tokyoが開催され、そこで東京リージョンも登場しました。

メルペイは2019年2月にローンチしていますが、開発を始めたのは2018年です。Spannerの登場から1年も経たないうちに、Spannerを使って開発を進めました。

@apstndb:Spanner GAになって間もなく、まだDMLもないときから開発していたのですね。

@sinmetal:最初に「DBをどうしようか」という話がありましたが、当時のCTO・曾川さんの意見によりSpannerを使用することになりました。

私はもともとCloud Datastoreを使っていましたが、以前「メルカリ アッテ」や「メルカリカウル」を作っていたときはApp Engineを使っていました。

もともとCloud Datastoreを使っているメンバーが多く、そのメンバーが一定数メルペイに異動したので、GoogleらしいDBには多少慣れていました。だから、皆あまり気にすることなく開発していました。

@apstndb:水平分散されるCloudのマネージドデータベースという点で共通点があったんですよね。ローンチ後もCloud Spannerの機能追加もあったようですが、その後についてはどうでしょうか。

@sinmetal:我々の運用上の判断に影響を与えたのは、SLA 1NodeやCPU Priority、Granular instance sizingです。マイクロサービスごとにSpannerを立てるか、マンションのように1つのSpanner Instanceに複数のマイクロサービスを同居させるか否かについては変化があったと思います。

@apstndb:1Node SLAがリリースされるまでは、1NodeのCloud Spanner InstanceにはSLAが提供されませんでした。3Node以上からプロダクションインスタンスという立ち位置になりリージョナルインスタンスであれば99.99%SLAが提供されるようになっていて、3Node未満の小さいインスタンスにはSLAが提供されませんでした。

ですので、プロダクション向けに使う場合は規模の小さいマイクロサービスは3Node以上のインスタンスにみんなで同居する手段を取らざるを得なかったのが当時の問題でした。今では、1マイクロサービス1インスタンスができるようになりましたね。

@sinmetal:dev環境は月額最低8000円ちょっとくらいで使用できると思うので、dev環境に各マイクロサービスを立ててもいいかと考えました。

@apstndb:必ずしも開発環境でSLAは必要ありませんが、同居しているといろいろなトラブルが起こりますよね。

@sinmetal:開発用DBはSREチームが作ってくれた共有のSpanner Instanceにみんなで同居しています。開発用なので開発者には強い権限が与えられており、コンソールのボタンを押すとインスタンスを削除できます。過去に誰かが間違えてインスタンスを削除して、同居しているマンションごと消滅したことがありましたね。

@apstndb:その後Granular instance sizingもあって、開発用も1チームごとにインスタンスを用意しやすくなったので、その問題は解決されそうですね 。

@sinmetal:あとはローンチ後からGoogleにリクエストしていたのは、Backup Restore系やPiTR(Point in Time Recovery)系の機能です。これらの前の手段としては、exportがありました。

@apstndb:BackupとしてのexportをGCS(Google Cloud Storage)に保存して、場合によってはそれをマルチリージョンに保存しましたよね。社内ではBackupというとexportを指すことが多く、混乱することがありますよね。必ずしもRestoreするとは限らないアーカイブのようなものもあります。

@sinmetal:分析用にBigQueryにもっていくものもあるので、さまざまなものがSpannerからデータを吸い出しています。

PiTRについて、もともとずっと直近1時間は指定した時刻でデータを引き出せたのですが、何らかの方法で7日間まで伸ばせるようになったんですかね。

@apstndb:はい。Cloud SpannerはMultiVersion Concurrency Controlなので、複数versionをストレージに保存しています。ずっと保存していても無駄ですが、ある程度古いデータでもExact stalenessでクエリができるようになっていて、保存期間を7日まで増やせるようにしたことでPiTRも実現しています。

PiTRはクエリができるしバックアップ対象にも指定できます。exportのデータフローのスナップショットタイムとしても指定することが可能です。使い勝手の良い、理想的な機能ですね。

@sinmetal:日時バッジにおいて0時00分のデータで処理したい場合も「指定すれば0時00分のデータになるからそれでいいや」といった感じで使いますね。

@apstndb:今までexportするときは複数のDBを跨ぐバックアップ間でタイムスタンプが合わないことがあり、それを見て見ぬふりをしていました。スナップショットタイムを指定できるようになったことで、PiTRの中で全部スナップショットタイムを指定できるようになり、タイムスタンプを合わせられるようになった点は大きいですね。

@sinmetal:我々にはDBがたくさんあるので各々でバックアップを取るとバラバラの時刻になってしまい、Restoreしたときにマイクロサービス全体のデータの整合性がイマイチになる可能性があります。でもこれなら時刻が合うのでいいですね。

@apstndb:TrueTime的にはタイムスタンプをきちんと合わせることが重要なので、いいことだと思います。過去にCloud SpannerインスタンスのGoogle側の時計とGKEのGCEインスタンスの中の時計がずれていて、何度かトラブルが起こりました。

@sinmetal:たとえばGCE側のメンテナンスで“Stop the World”が起こると時計がずれやすいですよね。

@apstndb:Cloud Spanner側にはTrueTimeがあって信用できるので、基本的にデータベース側の時間を信じるといいです。

@sinmetal:それから、CPU Priorityも地味に便利です。すべてこれで解決できるわけではありませんが、リードレプリカを作れないので、ある程度CPU Priorityを指定することで重めのクエリの優先度を下げられるのが嬉しかったです。

@apstndb:「latency sensitiveなマイクロサービスが受けられるリクエストのクエリを絶対に邪魔しないで、バッチやBackupのためのexportを走らせたい」という需要があります。元々は Priority を指定することはできずに Cloud Dataflowから実行されたものだけなぜか低い優先度で走るのがCloud Spannerの仕様でした。

我々ではコントロールできなくて、Goで書いたバッチが普通のマイクロサービスのクエリと同じ優先度で実行されるとマイクロサービスが邪魔をしてしまうのです。このあたりはリクエストを出していたので、実装されて嬉しいです。

@sinmetal:Spanner Emulatorも便利で、これが無いときはいろいろな手を使っていました。

@apstndb:Cloud SpannerはOSSではないので、Cloudを使わないと動作確認できません。当時はGranular instance sizingもありませんでしたし、開発環境を作るのが大変で動作確認できる環境の用意も難しかったですね。

以前は自分たちで作ったEmulatorもありましたが、公式のもので交換性も高いので、重宝しています。

@sinmetal:世界的に我々以外にとってもSpannerを使いやすくなりました。CIやローカル環境など、やりやすいと感じる人は増えたと思います。

@apstndb:少し困るのはCloud Spannerサービスに機能が追加されたときにCloud Spanner Emulatorに実装されるまで時間差があることです。大体半年くらいかかります。

@sinmetal:Release Historyには大きめの変更のみ記載していますが、細かな変化はもっとたくさんあります。

@apstndb:BigQueryと同じフロントエンドを使っているので SQL構文にも新しい機能が追加されます。実装されるまでに時間がかかるので、プロダクションではすぐに使えないのが少し困りますね。

@sinmetal:新機能がすぐに Emulator で使えるように同時にリリースするためにEmulatorに実装されてからでないとリリースされないのも微妙なので、仕方ないですね。

最近ですと、Change Streamが登場しました。

@apstndb:Cloud Spanner側で、トランザクションでの何らかの変更をAPIを通して一つずつ出力できる機能です。実際に使う場合はApache BeamのSDKを使って取得します。

さらに言えば公式のCloud Dataflowテンプレートを使ってGCSや BigQueryに出力する機能も用意されています。他にも使い道がないか、社内で検討しています。

@sinmetal:ただストリームデータを扱うのは難しいので、苦労しそうです。

@apstndb:Cloud Dataflow テンプレートを通して使うときはテンプレートにパラメータを渡すだけなので簡単ですが、内部的にはパーティションごとに一つのストリームとして取得しなければなりません。さらにパーティションがリアルタイムで分割されるので、それを追跡しなければならず、自前で扱うのがかなり難しいのです。

データフローを使うとしてもApache Beamを学習しなければなりません。現時点では Change Stream connector を使うには JavaからApache Beamを使わないといけないので、普通のマイクロサービスの開発者が活用するのは難しそうです。

@sinmetal:そうですね。内容が生々しすぎて、これを完璧に理解できればSpannerのSREになれるかもしれないと思いました。

このように、これまでメルペイもいろいろな機能をローンチしていますが、Spannerに便利な機能が追加されたら今までと方法を変えることはありました。

@apstndb:すでにいくつも採用している機能がありますけど、CUP Priorityをとりあえず入れるようにしていますからね。

google-cloud-goとの付き合い

@sinmetal:先ほどのRelease Historyは、Spannerのサーバー側機能のお話でした。もうひとついろいろな出来事があったのが、Client Librariesです。2019年に「Spannerとの戦いの日々」というタイトルでアーキテクトチームの@kazegusuriさんがブログを更新していました。

参考資料
メルペイでのSpannerとの戦いの日々:
https://engineering.mercari.com/blog/entry/2019-04-18-090000/
Google Cloud Client Libraries for Go:
https://github.com/googleapis/google-cloud-go

@apstndb:Googleが新しく出したサービスのClient Librariesですのでどうしても成熟していませんし、google-cloud-goでSpannerを使っている人が少ないですからね。

@sinmetal:google-cloud-goの立ち位置も、Spannerの製品の一部なのかSpannerのAPIを使うサンプル実装なのか「どっちやねん感」がありました。

他の会社さんではgoogle-cloud-goやClient Librariesを使わずに実装している場合もあります。でもさすがに面倒なので、これを使うために品質を担保してほしいという話はありました。

@apstndb:今はgoogle-cloud-goに限らずCloud SpannerのClient Libraries専門の人がいるみたいです。

@sinmetal:その方があげてくださったPull Requestを、頼りにしています。

今日も弊社の誰かがあげたPull Requestがマージされて「このような機能が入って便利になる」という話をしていました。昔はあまり受け付けてくれるイメージはありませんでしたね。

@apstndb:google-cloud-goは確かGitHub上でPull Requestを受け付けていませんでした。Gerritでチェンジリストを送ってレビューしてもらわないと機能を追加してもらえません。あまりgoogle-cloud-goのspannerパッケージの専任のメンテナがいるわけではないのか、放置されている印象はありましたね。

@sinmetal:Librariesを作っている人はSpannerに詳しいのですが、Spannerを使う側に詳しいわけではないので、微妙に話がかみ合わなくてIssueでウダウダ言うこともありました。Googleの中の人とミーティングを組んでもらって話し合ったことがあります。

@apstndb:「Forkしないとどうにもならない」という判断もありますよね。

@sinmetal:最初はリトライするときの待ち時間が最低1秒固定になっていて、「1秒はさすがに長いな」と思いました。定数でベタ書きしているだけなのでForkして書き換えれば別の値にできますが、「書き換えていいのか」という話もしていました。

@apstndb:スライドにも記載がありますが、Session Pool周りが変わっています。当時Session Poolの中のSessionの寿命を延ばすためのキープアライブの処理は何もありませんでした。定期的にsessions.get RPCを使ってSession Poolの中にあるSessionが生きているかどうかを確認して、生きていなかったら処分していました。

たまにクエリを投げるときに生きていないSessionをつかんでエラーになることがあります。でもリトライするために2秒待つ必要があったから「Session Poolは安定しない」という話題があり、Session Pool周りのデフォルト設定は使い物にならないし、我々は保守的な設定をせざるを得ませんでした。

@sinmetal:Google側は「リトライすれば最終的には成功するからいいだろう」という考えだと思います。でもこちらとしては、リトライには時間がかかるので困ります。

@apstndb:そんなこんなでSession Pool周りは2年前くらいに大幅に変わって、我々が抱えていた問題はだいぶ解決されましたね。

@sinmetal:2020年前半は我々がマインスイーパーのように歩くとどんどん爆発していくみたいな感じでしたね。gRPC側にも変化があり、SpannerのClient Libraries側の動きがかみ合わなくてレイテンシーが増すパターンがありました。

@apstndb:gRPC Client LibrariesとGoogle Client Librariesの基礎の、例えば認証周りやgoogle-cloud-goのSpanner Client Librariesの問題があります。それらが積み重なっていろいろなレイヤーで問題が起きている状態でしたね。

でも、最近は落ち着きました。これから使い始める人は、デフォルトの設定でもだいたい大丈夫だと思います。

@apstndb:我々が設定し直した部分も、むしろデフォルトに戻したほうがいいのではないかという話も増えてきました。

@sinmetal:Session Poolの状態をOpenCensusで出力できるようになって、Sessionをいくつ使っているのかが見られるようになりました。

@apstndb:もしSession Poolコンフィグレーションが適切ではなかったとしてもSession Poolの状態がMetricsとして見られるようになっているから「これが足りない」などの議論ができるようになりました。

google-cloud-goは組み込みでOpenCensusにトレースを出してくれるので、「CreateSessionが投げられているからSession Poolが小さいのではないか」などの議論もできます。このあたりのgoogle-cloud-go側のオブザーバビリティの向上によって、我々はだいぶメリットを受けています。

@sinmetal:最初から全部あればよかったんですけどね。(笑)

@apstndb:当時はOpenCensusも全然成熟してなかったですよね。もともとはStackdriver Monitoring(現在の Cloud Monitoring)のクライアントライブラリがあって、そもそも我々はモニタリングツールとしてCloud Monitoringはメインで使っていなくてDatadogのClient Librariesを使っているので、OpenCensusがDatadog Exporterを実装したことでMetricsを活用できるようになったのは嬉しいです。

今後OpenCensusからOpen Telemetryへの移行もあると思うけれど、それはお楽しみですね。

Queryのパフォーマンス・チューニング

@sinmetal:続いて、ここ1年でよく話題に上がるようになったQueryのパフォーマンス・チューニング系の話です。

参考資料
Cloud Spanner の実行計画の活用に関する取り組み | メルカリエンジニアリング:
https://engineering.mercari.com/blog/entry/20201210-cloud-spanner-query-plan/

@apstndb:他のRDBMSであれば実行計画はかなり詳細に見ると思うのですが、Cloud Spannerは実行計画についての議論があまり公開されたところでなされていないので、みんな手探り状態です。

ドキュメントをよく見るとオペレーターの動きやパラメータなどについて書いてあるのですが、実行計画のCloud コンソールのビューワーは全く見えないことが多かったのです。だからレビューするために必要な情報がなく、他のデータベースで行っているようなパフォーマンスチューニングができない状態が長く続いていました。

実は実行計画の生データをよく見ると、ドキュメントに書かれているより更に詳しい情報がJSONもしくは Protocol Buffers として出力されています。このため可視化ツールを作ると必要な詳しい情報が取れます。

また、Google Cloud コンソールのプランビジュアライザーの新しいversionだと前のversionでは見えなかった情報が出力されます。でもこれはスクリーンショット以外に共有方法がありません。クリックしないと表示されない箇所に詳細が記載されているので、それを見ながらレビューするのはかなり難しいと思います。私は普段使わないです。

@sinmetal:私も使っていません。apstndbさんが作ったツールの方が便利です。文字で出力できるので、SlackやGitHubのIssueやプルリクエストにもこれを貼れば済みます。

公式のプランビジュアライザーはアップデートされて見栄えが良くなったとは思いますが、あまり参照することはありません。ツリー構造がわかりやすいというメリットはありますが、詳細は見れませんからね。

@apstndb:プランビジュアライザーのいいところは、リモートコールが点線になっていて、リモートコールを跨いだタイムラインが右下に出力されることです。他のビジュアライズ方法にはない特徴です。どの可視化方法も一長一短なので「これが見たいときにはこの方法がいい」というのはありますが、我々はGitHub上でレビューをしたいのでテキストが望ましいです。

公式のプランビジュアライザーは今の実行計画の生データをJSONでダウンロードできるので、おそらくこれをサポート側に送ればサポート側で再現してビジュアライズすることができるはずです。でもそれをビジュアライズする方法は公式には提供されていません。一応JSONをダウンロードして解釈するツールに飲ませれば可視化できます。

とりあえずツールを作ったことでかなり実行計画への理解が進みました。今は実行計画のレビューをできる限りするようにしています。optimizer versionの変更があるときも意識するようにはなりました。

@apstndb:クエリオプティマイザのバージョンはリリース当時がversion1、インデックスの選択の最適化が実装されたのがversion2でした。version3はもともとサイレントでリリースされたんです。リリースノートに書かれる前にversionが上がって、クエリの挙動も変わって混乱しました。あまり詳しくは言えませんがトラブルにも発展し、Googleとやりとりして一旦version3のリリースが差し止めになりましたね。

この時のやりとりでは主に「サイレントでversionが上がると困るからリリースポリシーを決めてほしい」という話をしました。結果、今はリリースノートに「リリースされた」と載った後で、30日の評価期間を経てデフォルトになります。それまでに検証して、versionを上げるか古いversionに固定するかを選べるようになりました。

2022年8月現在のドキュメント中にはデフォルトがversion4と書いてあり、version5は最新だけどデフォルトではありません。この期間中に検証し、version5に上げるかを選べる状況になっているので、我々としては安心です。(セッション後にversion5がデフォルトとなりました。)

参考資料
Cloud Spanner クエリ オプティマイザーのバージョン:
https://cloud.google.com/spanner/docs/query-optimizer/versions?hl=en

@sinmetal:いったん我々はversion4で固定しておく予定です。なぜversion5に変更しないかという話をしようと思ったのですが、そろそろお時間になりましたので、気になる方はTwitterで質問していただければと思います。

LockやTagの話もしようと思ったのですが、時間になってしまったのでここで終えたいと思います。

参考資料
Cloud Spanner のロックについて:
https://zenn.dev/apstndb/articles/a62ac78b3b91bb

参考資料
SchemaからGo CodeをGenerate:https://github.com/cloudspannerecosystem/yo
Schema management tool:https://github.com/cloudspannerecosystem/wrench
TestのためにDBをPoolし、払い出す:https://github.com/cloudspannerecosystem/spool
Spanner Emulator:https://github.com/gcpug/handy-spanner

ご清聴ありがとうございました。

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加