🔧

Spannerの運用について

2024/10/31に公開

はじめに

Spannerはマネージドデータベースとして提供されているサービスです。そのためパッチ適用やハードウェアのメンテナンスなど、データベースで日常的に発生する運用面での作業の多くは自動的に実施されます。

一方で、データベースの運用という観点ではそれ以外の必要な作業や考慮するべき事項、監視ポイントなどがあります。一般的なリレーショナルデータベース(以下、RDB)と同様の操作もありますが、Spanner特有の概念も存在します。これらについて、RDBとの差分を中心に解説します。理由が明らかな内容についてはその背景となる理由も極力記載しました。

ストレージ

ストレージサイズ

Spannerではストレージサイズはテーブルに書き込んだデータの量に応じて自動的に拡張されます。そのため、一般的な意味でストレージの残り容量を監視する必要はありません。ただし、1ノードあたり10TiBというストレージの上限はあるため、これに到達していないかは注意が必要です。
通常のワークロードではノードあたりのストレージサイズの上限に達する前に、コンピュートノードの性能上限に達することが多いため、ノード数を増やすことになり、ストレージの上限に達することは少ないでしょう。

ストレージの使用量はCloud Monitoringのメトリクスとして確認や監視が可能です。前述の通りストレージの使用量は実際のデータ保持量に応じているためテーブルからデータを削除するとストレージの使用量も減少しますが、実際に使用量が減少するまでにはある程度タイムラグがあります。これはテーブルからの削除操作(DELETE文など)を行ったあとにバックグランド処理としてコンパクション(Compaction)が実行されることでストレージの使用量に反映されるためです。削除直後にストレージの使用量が縮まないことは期待される動作であるため、しばらく時間をおいてメトリクスを確認してください。

バックアップ

Spannerのストレージとして Colossus という分散ストレージシステムを利用しているため、ストレージ自体がコンポーネント単位の物理障害に対して高い耐久性があります。一方で、リージョン単位の障害やアプリケーションの誤動作や操作ミスによる論理障害などに備えるためのバックアップも可能です。

バックアップはバックアップ元のインスタンスと同じリージョンに取られ、新しいSpannerインスタンスへ復元できます。バックアップはインスタンスのサーバーリソースを使わずにストレージレイヤーから取得されるため、取得中もSpannerインスタンスのパフォーマンスへの影響はありません。RDBの物理バックアップと同様の考え方です。

一般的なRDBのバックアップと異なりバックアップの有効期間は最大1年であることに注意してください。 1年以上保存したい場合は、インポート・エクスポートを使用してください。

インポート・エクスポート

SpannerはDataflowを使ってテーブルの内容をインポート・エクスポートする機能があります。RDBでの論理バックアップに近い概念ですが、エクスポートされる形式はCSVかAvroとなり、エクスポート先はGoogle Cloud Storage(以下、GCS)のバケットになります。エクスポートしたデータので二次利用のためにCSV形式が必要であるとき以外は、Avro形式を使う方がオススメです。

Dataflowは汎用的なETLなどを行うことができるサービスですが、Spannerのインポート・エクスポートという基本的な機能を使う範囲では新たにDataflowのテンプレートを独自作成する必要はありません。

バックアップとインポート・エクスポートのどちらを使うべきかについては、マニュアルに比較表がありますので参照ください。

ポイントインタイムリカバリ(Point in Time Recovery)

PITRとはデータベースの内容を特定の時刻に戻すという機能です。アプリケーションの誤動作やオペレーションミスによるデータ破損など、論理障害からの回復手段として利用されます。

一般的なRDBでは過去のバックアップとトランザクションログ(binlogやWAL、アーカイブログ)と組み合わせて、稼働中のデータベースとは別にリストア先のインスタンスを作られることが多いでしょう。これに対してSpannerのPITRの考え方は少し違います。
Spannerでは、過去のある時点のデータをクエリできる機能(ステイル読み取り/Stale read)を使って過去の時刻を指定した読み取りを行いその内容をテーブルに書き戻すことで特定の時刻の内容を復元します。

そのため、特定のテーブルやレコードのみ復元するといったことも可能です。これは「特定のユーザーが誤操作を行ってしまい、データを意図しない内容に更新してしまったが、更新前の状態が知りたい」と言った場合です。このような場合にはテーブル全体を復元する必要はなく、特定のレコードの過去の特定時点での状態を知りたい場合にもステイル読み取りは有効です。

ステイル読み取りで参照が可能な過去の時刻は、デフォルトでは現在時刻よりも1時間前までです。設定により最大7日間まで延長することが可能です。過去のデータの参照可能期間を延長すると更新量に応じてストレージの使用量が増加します。

断片化(fragment)対策

多くのRDBではテーブルやインデックスの作成後にレコードに対して追記・削除・更新を繰り返すとデータの物理レイアウトが断片化を起こしパフォーマンス低下を招くことがあります。この対策として、定期的なインデックスを再構築してメンテナンスを行う場合があります。

Spannerではこのような明示的な操作は不要です。Spannerでは、LSM treeと呼ばれるデータ構造を使用しています。このデータ構造では、追記はもちろん、削除・更新も追記として処理されます。そのため、更新にともなうデータファイルに断片化が発生しません。

Spannerで断片化に近い事象としては、大量のデータを一気にロードしたり削除したときが考えられます。さきほどの説明の通りこれらの操作は追記によって実現されます。もちろん削除操作を追記しただけではデータの占有サイズが大きくなる一方であるため、バックグラウンドタスクとしてコンパクションが実施されます。これによりデータが再編成されます。コンパクションの操作はシステムタスクとして実装されており、中間の優先度で実行されています。そのため、大量のデータ操作をしてからある程度の時間をおいてシステムタスクがCPU時間を使用する様子が観測されることがあります。ある意味定常的に詰め直しが発生しているとも言えます。この仕組みによってデータベース管理の一環として管理者による定期的なインデックスの再編成は必要ありません。

クエリーオプティマイザー

Spannerには一般的なRDBと同様にクエリーオプティマイザーが存在し、オプティマイザーが実行計画を策定してそれに基づき実行されます。オプティマイザーには新しい機能を実装するためにバージョニングされています。2024年10月時点での最新のオプティマイザーはバージョン7で、デフォルトではバージョン6が選択されています。各バージョンのオプティマイザーの詳細は変更履歴にありますが、最近のバージョンはコストベースオプティマイザーとなっています。

コストベースオプティマイザーは現在多くのRDBで広く採用されている技術です。テーブルやインデックスへのアクセス処理がどの程度のコスト(CPUやメモリ、ストレージなど)がかかるかを合計して実行計画の効率を評価するものです。それ以前に使われていたルールベースより実際のテーブルの状況に即した最適な実行計画が得られる事が多いですが、統計情報が変化すると実行計画が変わることが知られています。

オプティマイザーは今後とも更新され、新バージョンはリリース後しばらくはデフォルトでは選択されず、オプトインで明示的に選択することで利用できます。

新バージョンは全体的なパフォーマンス向上を目指していますが、特定のクエリーのパフォーマンスが低下する場合もあります。そのような挙動の変化を避けたい場合には、オプティマイザーのバージョンを固定することも可能です。指定の上書き関係は以下の通りです。

  • 優先度:低
  • Spannerのデフォルト
  • データベースのオプション(ALTER DATABASEで指定)
  • クライアントアプリで設定
  • 環境変数で設定
  • クライアントのセッション単位での設定
  • ステートメントヒント
  • 優先度:高

プロダクション環境などでバージョンを固定したい場合は、データベースのオプションで指定しておくと良いでしょう。その上で、特定の処理で上書きしたいときに、アプリケーションから指定する、あるいは環境変数で指定し動作を確認するなどが良いのではないでしょうか。

実行計画

実行計画が意図しないインデックスを使用している場合に、特定のインデックスの使用を強制したい場合があります。そのような場合にはヒント句を使います。ヒント句にはテーブルへのアクセス方法を指定するヒント、JOINのアルゴリズムを指定するヒントなどがあります。

すべてのクエリーでヒント句を個別に付けていくのは現実的ではないので、とくに遅いクエリーや効率の良くないクエリーへピンポイントで対応するのが現実的でしょう。

統計情報

Spannerのオプティマイザーが使う統計情報は自動的に作成され、更新されていきます。基本的には自動更新に任せて大丈夫ですが、こちらも特定のバージョンで固定することも可能です。

大量にデータをロードした直後など、テーブルの内容の大きな変化に統計情報の更新が追いついていない場合には手動でANALYZEコマンドを実行することで統計情報の収集開始を指示することも可能です。

サイジング

Spannerではノード数の増減によって全体の処理能力をスケールアウト・インできます。
1ノードは1000 PU(Processing Units/処理ユニット)とも呼ばれ、1ノードの性能の目安が提供されています。ストレージは前述の通り自動的にスケールするため、サイジングのパラメーターはノード数のみです。

たとえば、リージョナル構成(1つのリージョンのみを使う構成)で1ノードあたり最大で22,500 QPSの読み取りか3,500 QPSの書き込み処理能力があります。これはスキーマと処理内容などがベストプラクティスに沿った設計の場合にノードの能力を使い切ったときの数値です。実際のアプリケーションではこれよりも複雑な処理内容も含むため、小さい値でCPU使用率などが増設を推奨する基準に到達したりレイテンシが悪化することがあります。実際のワークロードを実行したときの性能がノード数に対して適切であるかのおおよその比較対象として参照ください。

ノード数の追加はダウンタイムなく実行できますので、負荷の上昇に応じて随時追加されるのが良いでしょう。

ノードの削除もダウンタイムなしで可能ですが、一気に大幅な削減を行うとアプリケーションの性能に影響を与える可能性があるため、段階的に削減するのが推奨されます。たとえば、50ノードから10ノードに削減する場合、一度に削減するノード数を全体の10%以下(この例の場合には、5ノードづつ)に抑え、削減の間隔を10分以上空けることがオススメです。これは、削除対象のノードが担当しているデータを他のノードに引き継ぐ処理などがあるためです。また、データを引き継ぐ先のノードが集中して忙しくなるという問題もあります。レイテンシーへの影響を少なくするため、ゆっくりと削減してください。

オートスケーラー

ノード数のサイジングに関しては、オートスケーラーを使うという方法もあります。マネージドオートスケーラーがEnterprise Edition以上で提供されています。

オートスケーラーは高優先度のCPU使用率とストレージの使用量に応じて、ノード数の増減を制御します。マネージドオートスケーラーに制御を任せてしまうという方法はコスト最適と突然のワークロード増加への対応の観点で有効です。

マネージドオートスケーラーで設定可能なパラメーターはCPU使用率の目標値、ストレージ使用率の目標値、ノード数の下限と上限の4つだけです。CPU使用率の目標値は多くの場合デフォルトのままでお使いになると良いでしょう。これはSpannerの推奨値であるリージョナル構成で65%が指定されています。ストレージについても同様です。そのため、利用場面に応じて考える必要があるのはノード数の下限でしょう。普段のワークロードの状況を見つつ、平常時のワークロードが一番小さい時間帯のノード数を指定されると良いです。ただし、事前に予測可能なイベントに備えてノード数を事前に増やしておきたい場合には、ノード数の下限を引き上げておきイベントを迎えるのが望ましいでしょう。

注意点としては、現時点(2024年11月)ではマネージドオートスケーラーは1ノード(=1000 PU)を最小コンピュートサイズとしているという点と、上限が最小ノード数の10倍という点です。平常時の最小ノード数の10倍を超えてワークロードが大きくなることが予想される場合には、10倍を上限としてそれ以上はスケールしないという点にご注意ください。

これらの制限を回避したい、あるいはStandard Editionを使っている場合にはOSS版のオートスケーラーを使うという方法もあります。これは、Cloud SchedulerとPub/Sub、Cloud Run functionsとFirestoreを組み合わせた仕組みです。コンポーネントが多くて複雑そうに見えますが、いずれもサーバーレスのサービスですので一度動き始めると管理する必要がある部分はそれほどありません。こちらのメリットはスケールのロジックがコード上で直接読むことができるので、自分たちの運用ポリシーに合わせて改修することも可能な点です。

テーブル定義の変更(ALTER TABLE)

Spannerではダウンタイム無しでテーブル定義が変更できます。つまりスキーマ変更中に対象のテーブルへの読み書きも可能です。

一方で、既存の大規模なテーブルへのセカンダリインデックスの追加や既存データの検証が必要な制約の追加(たとえば既存のカラムに対して、NOT NULLを追加するなど)は処理に長い時間がかかります。そのような場合には、ALTER TABLEを実行する前に一時的にでもノードを増やしておくことが有効です。これはテーブルからインデックスへのデータの書き込み(この処理のことをバックフィルと呼びます)やデータの検証を複数のノードで分散処理されて、完了までの時間が短縮できます。

また、複数のALTER TABLEを実行する場合には、まとめて実行することも有効です。これは、Spannerがスキーマをバージョン管理しており、DDL毎にスキーマバージョンを更新するのではなくまとめて1回の更新とすることで、バージョン更新に関連する処理を削減できるためです。

コネクション数

Spannerではクライアントとの通信にgRPCを使います。gRPCは1つのHTTP/2の接続を複数のチャネルに多重化することができ、Spannerクライアントは1つのチャンネルを1つのセッションとして扱います。Spannerのクライアントライブラリはセッションをプールすることで、新規のセッションを作る必要を減らそうとします。これはRDBでのコネクションプールと似た概念です。

セッションプールは言語毎のクライアント実装によって差はありますが、プールする数などをチューニングできます。テールレイテンシが高くなっている原因として新規のコネクションを頻繁に行っていることが疑われる場合、プール数を大きくするなどの変更は有効です。

モニタリング

SpannerではさまざまなメトリクスがCloud Monitoringに記録されます。CPU使用率やリクエスト数や行スキャン数、レイテンシなどです。
これらのメトリクスへの基本的な考え方はRDBと同様ですが、その数値の解釈について特有の考え方などもあります。ここでは差分の解説します。

CPU使用率

SpannerのCPU使用率は全体の平均CPU 使用率がCloud Monitoringメトリクスとして提供されています。
CPU使用率にはユーザー・システムの区分と優先度について高・中・低の3つの区分で集計されます。そのため全体としては6種類あります。通常はユーザーの優先度高がアプリケーションからのクエリや更新処理の実行用として使用されるため、負荷の大半はこちらになるでしょう。

推奨される最大の CPU 使用率が一般的なRDBよりも低いことに注意してください。たとえば、リージョナル構成では高い優先度の合計が65% 以下を維持することを推奨しています。これを超える場合にはノードを追加することで対応します。一般的なRDBではCPU使用率は上限である100%まで使い切れますが、Spannerは分散データベースであるという特徴から推奨CPU使用率を超えたあたりから後述のテールレイテンシが上昇するなどの事象が観測されることがあります。

レイテンシ

Spannerではリクエスト(クエリやDML)の処理にかかったレイテンシをCloud Monitoringのメトリクスとして提供します。レイテンシはウェブコンソール上で50パーセンタイル(p50)と99パーセンタイル(p99)が確認可能です。p50レイテンシはアプリケーションの全般的なレスポンス傾向を判断する指標として重要です。

p99レイテンシについては、とくにテールレイテンシ(tail latency)と呼ぶ場合もあります。テールレイテンシの原因調査については一本の独立した記事があるほどさまざまな原因が考えられます。多くの場合は明らかな原因があり、回避できます。記事にもある通り、Spannerは分散システムであるという性質から、構成コンポーネントの一部が交換される場合に交換されるコンポーネントで処理してた内容別のコンポーネントを引き継ぐなどを行うことがあり、短時間のテールレイテンシの上昇が完全には避けられないこともあります。テールレイテンシの上昇が継続時間で数分程度の過渡的な事象なのか、継続的に発生している事象であるかは原因と対策を判断する上で重要です。継続的に発生している場合にはクエリの実行効率が悪くなっている、あるいはCPUなどのリソース不足になっている可能性があります。

Cloud Monitoringのメトリクスとして得られるレイテンシはSpanner自身が報告する指標です。一方でアプリケーション側の観点ではリクエストの送信側での測定であるため、2つの指標が一致しない場合、原因はその間にある可能性があります。次節の通りクライアントライブラリはSpannerとの接続を使い回す仕組みがありますが、プールしている接続が不足した場合には新規に接続を行う必要が生じ、これが大きなレイテンシとなる場合があります。

トランザクションへの参加者数(Number of Participants)

Spannerでは、テーブルをスプリットと呼ばれる単位に分割することでスケーラビリティを向上させています。アプリケーションは、トランザクションがいくつのスプリットにアクセスしているかを意識する必要はありません。しかし、パフォーマンスの観点では、このスプリットへのアクセス数をモニタリングする意味があります。トランザクションで変更を行ったスプリットの数は、Spannerでは「参加者数(Number of Participants)」と呼ばれます。

プライマリキーで特定の1行を指定し、書き込みをする場合(いわゆるPK一本釣り)は単一スプリットへの書き込みとなるため参加者数は1となり処理も単純です。しかし、実際のトランザクションでは複数の行にまたがり処理を行う場合もあります。このような処理ではスプリット間でコーディネーションを行う必要があり、この処理は単一スプリットへの書き込みに比べてコストが高くなります。

そのため、Spanner上で実行されているトランザクションがいくつのスプリットに対しての処理だったかを知ることはトランザクションの高いレイテンシを調査する上での手がかりとなります。トランザクションがいくつのスプリットに処理を行ったかは、Cloud Monitoringのtransaction_stat/total/participantsというメトリクスから得ることができます。時間的推移はこちらをモニタリングすると良いでしょう。
純粋に性能の観点では1に近い数値が記録されることが理想的ですが、複数のスプリットへの同時更新がアプリケーションの内容から必要性があり行われているであれば、ある程度大きい数値が記録されることは異常ではありません。バッチ処理などを実行しているわけではないのに、特定の時間で非常に大きい数値が記録されている、あるいは中長期的に数値が上昇しているなどの傾向を掴むために利用ください。

もう1つの確認方法はトランザクションの統計情報テーブルから得る方法です。この統計情報は時間区切りでSpanner上のテーブルとして記録されており、そのうちでAVG_PARTICIPANTSカラムが該当の情報になります。特定のトランザクションが遅いときの原因調査や特定の時間で参加者が大きくなっている場合にトランザクションを特定するときに使用できます。

まとめ

主に運用の観点でSpannerに関して、知っておいて欲しい事項をご紹介しました。
Spannerは分散データベースであるため伝統的なRDBとは違う部分も多いですが、データベース全般に共通する仕組みは比較的共通しています。
熟練したDBAの方であれば、その知識を活かして運用することができると思います。

GitHubで編集を提案
Google Cloud Japan

Discussion