こんにちは。メルコインでバックエンドエンジニアをしているiwataです。
この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。
tl;dr
- バッチ処理のSLO定義って難しい… そんな悩みを解決するSLO定義方法
- BigQueryとSpanner External Datasetを活用した具体的な監視方法の紹介
- メルコインの安定稼働を支える技術
最近ではビットコインやイーサリアムを積み立てる機能を開発していました。
積立の開発では積立日にバッチ(以下、積立バッチ)を起動することでビットコインなどの仮想通貨の購入処理を実行するようにしました。
積立バッチはお客さまの資産をあつかうとても重要なバッチです。設定された積立日に確実に処理を実行し終える必要があります。このようにシステムの信頼性を考える上で広く認識されている考え方がSLOです。それではバッチ処理におけるSLO定義とはなんでしょうか?
バッチ処理においてSLOを定義することの難しさ
一般的にSLOで用いられるSLIとしてはAvailability(可用性)とLatencyがあります。
前者はエラーレートの逆数として算出可能であり、APIがエラーをどのくらいの割り合いで返しているかで監視することが多いです。
後者はAPIの応答時間を99パーセンタイルなどの統計値を基に監視します。
いずれの指標もAPIであればその定義も分かりやすく、監視方法も確立されています。
ではバッチ処理についてあてはめるとどうでしょうか?
バッチのAvailabilityといった場合に、実行時の終了コードだけをみればよいのでしょうか? それともバッチで一括処理するデータひとつひとつのエラーレートをみればよいのでしょうか? またLatencyについてはバッチ処理の実行時間だけをみればよいのでしょうか? それともこちらもデータひとつひとつの処理時間をみればよいのでしょうか?
一方でSLOの設計においてはCUJ(Critical User Journey)に代表されるように、ユーザー視点で考えることが大切です。バッチ処理によってお客さまは何を期待しているのでしょうか。
これらのことを考える上で以下の資料がとても参考になりました。
バッチ処理のSLOをどう設計するか – Speaker Deck
スライドにあるようにバッチ処理で担保したい信頼性をデータの「納期」(デッドライン)と「品質」という観点で整理しました。
積立においてお客さまが期待することは「積立日に積立処理が完了していること」となるはずです。
- 積立日=デッドライン
- 積立処理完了=データ品質
すなわち「積立日の23時59分59秒までにすべての注文処理が完了(残高不足などによる失敗も含む)」をSLOとして定義しました。積立においてはバッチ実行時にタイムアウトなど一時的なエラーが発生した場合に別プロセスで自動リトライする仕組みもあったりしますが、この定義を用いれば別プロセスであってもカバーできます。お客さまからみれば例え何回リトライしていようが、その日中に処理が完了していれば問題ないとみなせるからです。
以降では具体的な監視方法を紹介します。
BigQueryを使った監視
メルコインではデータベースとしてCloud Spanner(以下、Spanner)を使っています。
社内では分析用途で使うために、SpannerのデータをBigQueryに定期的に同期するパイプラインが用意されています。Spannerへの負荷を考慮しなくて済むように、監視クエリはBigQueryに対して実行します。
またBigQueryに対して定期的にクエリを実行し、その結果をDatadogから監視する仕組みも構築されているためこれを用いて実現しました。
詳細は省きますが下図のような仕組みが構築されています。
ロゴ出典: Slack, Datadog, GitHub, Goolge Cloud
簡単に説明すると、事前に定義しておいたクエリをBigQuery上で定期的に実行し、その結果をカステムメトリクスとしてDatadogに送信しています。クエリ実行した結果のレコード数がカスタムメトリクスとして送信されるので、Datadog上でメトリクスモニターを定義して監視できます。
例えば積立であれば未処理のレコードを返すクエリを定義し、デッドラインである23時59分59秒以降にカスタムメトリクスが1以上であればSLO違反に気づける、という具合です。実際には違反前に気づきたいので十分に余裕をもった時間で気づけるよう監視しています。
Spanner External Datasetの利用
単純な用途でこれまで紹介したツール郡を用いることで監視できていました。ところがSpannerに直接クエリを実行せず、BigQueryを使うことで以下のような問題があります。
- SpannerからBigQueryへの同期がリアルタイムでない
- 同期処理がテーブル単位で実行される
SpannerからBigQueryへの同期がリアルタイムでない
同期用のパイプラインは1時間に一回実行されており、リアルタイムにデータが同期されているわけではありません。これによって検知に数時間かかってしまいます。このタイムラグを許容できないケースも考えられます。
同期処理がテーブル単位で実行される
同期用のパイプラインはテーブル単位で設定し実行されます。したがって、任意のタイミングでBigQuery上の複数のテーブル間には整合性が担保されていません。JOIN
した結果を用いて監視をおこないたい場合にはこれは致命的です。
Spanner External Dataset
これらの課題を解決するために一部のクエリではSpanner External Datasetを使いました。External Datasetを使うことで以下のようなメリットがあります。
- BigQueryへの同期は必要なく、Spannerに直接クエリできるのでタイムラグとテーブル間の不整合がなくなる
- Data Boostがつねに有効なのでSpannerへの負荷を考えなくてもよい
また同じような機能としてSpanner Federated Queriesがありますが、EXTERNAL_QUERY関数が読みづらいなどの理由でExternal Datasetを採用しました。
External Datasetの利用方法
最後にTerraformを使った利用方法を載せておきます。
google_bigquery_dataset
resource "google_bigquery_dataset" "spanner_external" {
provider = google-beta
project = {your-gcp_project_id}
dataset_id = "spanner_external"
location = "US"
external_dataset_reference {
external_source = "google-cloudspanner:/projects/{your-gcp_project_id}/instances/{your-spanner.google_spanner_instance_name}/databases/{your-database-name}"
connection = ""
}
}
設定値は適宜置き換えてもらえばよいですが、connection
だけオフィシャルドキュメントにあるように空文字で設定する必要があるので注意が必要です。
google_bigquery_dataset_access
resource "google_bigquery_dataset_access" "access_spanner_external" {
project = {your-gcp_project_id}
dataset_id = google_bigquery_dataset.spanner_external.dataset_id
role = "roles/bigquery.dataViewer"
user_by_email = {your-google_service_account.email}
}
クエリを実行するService Accountに対して上記で作成したExternal Datasetへのアクセス権を付与します。
google_spanner_database_iam_member
resource "google_spanner_database_iam_member" "monitor_can_read_database_with_data_boost" {
project = {your-gcp_project_id}
instance = {your-spanner.google_spanner_instance_name}
database = {your-database-name}
role = "roles/spanner.databaseReaderWithDataBoost"
member = "serviceAccount:{your-google_service_account.email}"
}
External Dataset経由でSpannerにもアクセスするので対象のデータベースに対してのspanner.databaseReaderWithDataBoostロールを付与します。ちなみにこのIAMロールは最近追加されました。Data Boostを使うにはこれまで別途カスタムロールの作成が必要だったり面倒だったのですが、いまではこのロールを割り当てるだけでよくなりました。
まとめ
バッチのSLO定義について書きました。
バッチでは「デッドライン」と「データ品質」を基にSLOを定義することでうまく運用できています。
データ品質を監視する方法としてBigQueryに対して定期的にクエリを実行する手法を採用しています。BigQueryとSpannerとの連携についてはExternal Datasetが提供されるようになったことで課題が解消されています。
この記事が読んでいただいた方の運用の手助けになれば幸いです。
次の記事は masamichiさんです。引き続きお楽しみください。