🏷

Cloud Spanner でのタグ付けのススメ

2023/09/01に公開

tl;dr

  • Spanner でリクエストタグをつけておくとモニタリングのときに集約されるので便利です
  • タグはコード上の位置を特定出来るよう付けるべきですが、長さに制限があるので命名に多少コツがあります

概要

Cloud Spanner ではトランザクションやリクエストにタグをつけることができます。
データベースへのアクセスにメタ情報を付けてモニタリングに使おうという発想は Sqlcommenterというライブラリにもあり、Cloud Spanner はタグ付けにネイティブに対応しているためクライアントライブラリの機能として実装されています。

トランザクションタグかリクエストタグか

タグはトランザクションとリクエストのいずれにも付与できます。
単発の読み取り処理などはトランザクション内で実行しないため、リクエストタグをつけることになります。

トランザクションは開始時にトランザクションタグをつけることができ、その中の個別のリクエストにリクエストタグをつけることも可能です。

トランザクションタグはロック競合の分析、リクエストタグは CPU 時間などの負荷を分析するために付けるが良いでしょう。

タグの付け方

タグはリクエストの発行元がどのアプリケーションのどの部位から行われたかを特定する目的があります。そのため、アプリケーションのコンポーネントの名前や関数名、行数などを付けて一意性を確保されると良いでしょう。ユーザーアクションを起因に実行される処理では、ユーザーアクションの名前をタグに含めるというのも有効です。

リクエストタグの付与例:

client.execute(
  "SELECT SingerId, AlbumId, MarketingBudget FROM Albums",
  request_options: { tag: "app=concert,env=dev,action=select" }
).rows.each do |row|
  puts "#{row[:SingerId]} #{row[:AlbumId]} #{row[:MarketingBudget]}"
end

Spanner で利用可能な REPL である spanner-cli からもタグを付けたトランザクションが実行できます。テストなどにご利用ください

spanner> begin RW TAG tx1;
Query OK, 0 rows affected (0.02 sec)

spanner(rw txn)> select count(*) from counts;
+----------+
|          |
+----------+
| 31059600 |
+----------+
1 rows in set (56.05 secs)

spanner(rw txn)> commit;
Query OK, 0 rows affected (0.02 sec)

spanner-cli ではトランザクションについてタグを付けて実行したトランザクション内の個別のクエリーについて自動的に同じタグがリクエストに付与されます。
この動作は spanner-cli 特有の動作で、通常のクライアントドライバから実行される場合はトランザクションとリクエストは個別にタグを付与ください。

タグの効果

Cloud Spanner では組み込みの統計テーブルが存在します。
MySQL での performance_schema や PostgreSQL の統計情報ビューに似た概念です。

この統計テーブルは集計時にタグに基づき CPU 使用率の上位のクエリなどを集計するため、どのタグがついているクエリーがより CPU 使用率を使っているかなどの調査が可能となります。
統計テーブルは SQL でアクセスが可能ですし、Query Insights というウェブコンソールの機能でもアクセスが可能です。対話的に問題を調査する際には Query Insights が便利です。

Query Insights はインスタンス全体の階層で見ると CPU 使用率がプロットされているのみです。注目したいデータベースを選択するとタグを使って集約したクエリー毎の負荷の推移などが見えます。

Query Insightsでの上位N個のクエリとタグ
この例では、4つのリクエストが上位に見えます。それぞれのリクエストについてグラフの表示範囲での実行回数や平均レイテンシ、平均スキャン行数などが集計されています。
この例では、action=table_sampling のタグがついている処理が実行回数は少ないものの、非常に多くの行をスキャンしているため CPU 時間を使っている事がわかります。一方でaction=single_read はスキャン行数と応答行数も 1 なので非常に単純なクエリーであることが分かりますが、実行回数が多い事がわかります。
CPU 使用率の観点では、前者の方が負荷の面で課題であるため、スキャン行数の削減やキャッシュすることによる実行回数の削減を検討すれば負荷対策となるわけです。

Transaction Insights(コンソール上でのメニューでは「トランザクション分析情報」)ではトランザクションの切り口で負荷の内容が分析可能です。
Transaction Insightsでのトランザクション毎の平均レイテンシ
この例では、2つのトランザクションが結果に現れています。
action=updates は平均レイテンシが大きいことが分かります。
また、トランザクション中で読み取り・書き込みを行ったテーブルとそのカラムについても集計されているため、トランザクションが想定通りの範囲のアクセスを行っているかの確認が行なえます。今回の例では対象テーブルの _exists カラムを読み取って存在確認をしている事がわかります。

updates トランザクションでは内部で update リクエストを、add トランザクションでは insert リクエストを実行していました。Query Insights 上でそれぞれのリクエストの処理時間はほぼ同じぐらい(3.74ms と4.69ms)ですが、Transaction Insights では2つのトランザクションのレイテンシには大きな差がありました(15.88s と 0.02s)。
アプリケーションの動作として、updates トランザクションでは 1000 回の update を実行していました。スループットの観点では1回のトランザクションである程度まとめて更新を行ったほうが有利ですが、レイテンシが問題になる場合はもう少し小さい単位でトランザクションを commit した方が良いかもしれません。今回の処理ではロックが衝突していませんでしたが、ロック同士の衝突の観点でも小さいトランザクションの方が有利な場合があります。

注意点

タグの長さの上限

タグに指定出来る文字列は ASCII 文字のみで50文字までという制限があります

タグとしてソースコードのフルパスと行数などをそのまま使うと制限を超過する場合があります。この長さ制限を超過すると切り詰められるため、リクエスト元を特定するという目的を達成できない可能性あるためご注意ください。

対応しているドライバ

Cloud Spanner へアクセスする方法はクライアントライブラリ、ORMとフレームワークドライバ、RPC API、REST APIの4通りあります。クライアントライブラリは Spanner 用に作られているため主な機能をすべて網羅しています

一方で、各言語の提供する DB アクセス共通化の仕組みやフレームワーク(例えば、Active Record や Hibernate など)で動作するドライバはいくつかの機能制限があります。リクエストタグ付けについてはいくつかのフレームワークドライバでサポートされていません。

具体例として Go 言語の database/sql へのドライバはこの記事で紹介しているリクエストへのタグ付けにそのままでは対応しておりません。ただし、database/sql のドライバは Spanner クライアントライブラリへのラッパとして実装しているため、元のドライバインターフェイスで設定を指定することは可能です。各ドライバでどのような制限があるか、回避策の有無などについてはドライバのマニュアルをご参照ください。

参考情報

GitHubで編集を提案
Google Cloud Japan

Discussion