スプリットの統計情報

このページでは、データベースのホットスポットを検出してデバッグする方法について説明します。スプリット内のホットスポットに関する統計情報には、GoogleSQL と PostgreSQL の両方でアクセスできます。

Spanner は、テーブルとインデックスのプライマリキーで並べ替えられた連続したキースペースとしてデータを保存します。スプリットは、一連のテーブルまたはインデックスの行の範囲です。スプリットの開始点はスプリット スタートと呼ばれます。スプリット リミットは、スプリットの終了を設定します。スプリットにはスプリット スタートが含まれますが、スプリット リミットは含まれません。

Spanner でのホットスポットとは、同じサーバーに多くのリクエストが送信され、サーバーのリソースが飽和し、遅延が大きくなる可能性がある状況を指します。ホットスポットの影響を受けるスプリットは、ホット スプリットまたはウォーム スプリットと呼ばれます。

スプリットのホットスポット統計情報(システムでは CPU_USAGE_SCORE として識別されます)は、サーバー上で使用可能なリソースによって制約されるスプリットの負荷の測定値です。この測定値はパーセンテージで示されます。スプリットの負荷の 50% 以上が使用可能なリソースによって制約されている場合、そのスプリットはホットと見なされます。スプリットの負荷の 100% が制約されている場合、そのスプリットはホットと見なされます。

Spanner は、負荷に基づいた分割を使用して、インスタンスのサーバー間でデータ負荷を均等に分散します。ウォーム スプリットとホット スプリットは、ロード バランシングのためにサーバー間で移動できます。また、より小さなスプリットに分割することもできます。ただし、アプリケーションのアンチパターンが原因で、分割を複数回試行しても、Spanner が負荷を分散できない場合があります。そのため、ホットスポットの状態が 10 分以上続く場合は、さらにトラブルシューティングを行い、アプリケーションの変更が必要になる可能性があります。

Spanner のホット スプリット統計情報は、ホットスポットが発生するスプリットの特定に役立ちます。必要に応じて、アプリケーションまたはスキーマを変更できます。この統計情報は、SQL ステートメントを使用して SPANNER_SYS.SPLIT_STATS_TOP_MINUTE システム テーブルから取得できます。

ホット スプリットの統計情報の可用性

Spanner は、SPANNER_SYS スキーマにホットスプリットの統計情報を提供します。SPANNER_SYS データは、GoogleSQL インターフェースと PostgreSQL インターフェースを介してのみ使用できます。このデータには、次の方法でアクセスできます。

Spanner の単一読み取り API は SPANNER_SYS をサポートしていません。

ホット スプリットの統計情報

ホット スプリットを追跡するには、次の表を使用します。

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: 1 分間隔でホット スプリットを示します。

これらのテーブルには以下のプロパティがあります。

  • 各テーブルには、テーブル名で指定された期間の、時間間隔が重複しないデータが含まれています。
  • 時間間隔は時刻に基づきます。

    • 1 分の間隔は現在の分が終わる際に終了します。
  • 各間隔が経過すると、Spanner はすべてのサーバーからデータを収集し、その後すぐに SPANNER_SYS テーブルでデータを使用できるようにします。

    たとえば、午前 11 時 59 分 30 秒に SQL クエリで使用可能な最新の時間間隔は次のとおりです。

    • 1 分: 午前 11:58:00~11:58:59
  • Spanner は統計情報をスプリットごとにグループ化します。

  • 各行には、指定された期間中に Spanner が統計情報をキャプチャした各スプリットのホットまたはウォームの度合いを示すパーセンテージが含まれています。

  • 使用可能なリソースによって制約されているスプリットの負荷が 50% 未満の場合、Spanner は統計情報をキャプチャしません。期間中にホット スプリットのすべてを保存できない場合、システムは指定された期間中に CPU_USAGE_SCORE の割合が最も高いスプリットを優先します。スプリットが返されない場合、ホットスポットがないことを意味します。

テーブル スキーマ

次の表に、次の統計情報のテーブル スキーマを示します。

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
列名 タイプ 説明
INTERVAL_END TIMESTAMP スプリットがホットであった時間間隔の終わり
SPLIT_START STRING スプリットの行範囲の開始キー。スプリット スタートは <begin> の場合もあります。これはキースペースの開始を示します。
SPLIT_LIMIT STRING スプリットの行範囲の制限キーです。制限: キーは <end> で、キースペースの終了を示します。
CPU_USAGE_SCORE INT64 スプリットの CPU_USAGE_SCORE のパーセンテージ。CPU_USAGE_SCORE のパーセンテージが 50% の場合、ウォームまたはホット スプリットが存在することを示します。
AFFECTED_TABLES STRING ARRAY スプリットに行が保存される可能性のあるテーブル。

スプリット スタート キーとスプリット リミット キー

スプリットはデータベースの連続した行範囲であり、スタートキーとリミットキーで定義されます。スプリットは、単一の行、狭い行範囲、広い行範囲にできます。また、スプリットには複数のテーブルまたはインデックスを含めることができます。

SPLIT_START 列と SPLIT_LIMIT 列は、ウォーム スプリットまたはホット スプリットの主キーを識別します。

サンプル スキーマ

次のスキーマは、このページのトピックのサンプル テーブルです。

GoogleSQL

CREATE TABLE Users (
  UserId INT64 NOT NULL,
  FirstName STRING(MAX),
  LastName STRING(MAX),
) PRIMARY KEY(UserId);

CREATE INDEX UsersByFirstName ON Users(FirstName DESC);

CREATE TABLE Threads (
  UserId INT64 NOT NULL,
  ThreadId INT64 NOT NULL,
  Starred BOOL,
) PRIMARY KEY(UserId, ThreadId),
  INTERLEAVE IN PARENT Users ON DELETE CASCADE;

CREATE TABLE Messages (
  UserId INT64 NOT NULL,
  ThreadId INT64 NOT NULL,
  MessageId INT64 NOT NULL,
  Subject STRING(MAX),
  Body STRING(MAX),
) PRIMARY KEY(UserId, ThreadId, MessageId),
  INTERLEAVE IN PARENT Threads ON DELETE CASCADE;

CREATE INDEX MessagesIdx ON Messages(UserId, ThreadId, Subject),
INTERLEAVE IN Threads;

PostgreSQL

CREATE TABLE users
(
   userid    BIGINT NOT NULL PRIMARY KEY,-- INT64 to BIGINT
   firstname VARCHAR(max),-- STRING(MAX) to VARCHAR(MAX)
   lastname  VARCHAR(max)
);

CREATE INDEX usersbyfirstname
  ON users(firstname DESC);

CREATE TABLE threads
  (
    userid   BIGINT NOT NULL,
    threadid BIGINT NOT NULL,
    starred  BOOLEAN, -- BOOL to BOOLEAN
    PRIMARY KEY (userid, threadid),
    CONSTRAINT fk_threads_user FOREIGN KEY (userid) REFERENCES users(userid) ON
    DELETE CASCADE -- Interleave to Foreign Key constraint
  );

CREATE TABLE messages
  (
    userid    BIGINT NOT NULL,
    threadid  BIGINT NOT NULL,
    messageid BIGINT NOT NULL PRIMARY KEY,
    subject   VARCHAR(max),
    body      VARCHAR(max),
    CONSTRAINT fk_messages_thread FOREIGN KEY (userid, threadid) REFERENCES
    threads(userid, threadid) ON DELETE CASCADE
  -- Interleave to Foreign Key constraint
  );

CREATE INDEX messagesidx ON messages(userid, threadid, subject), REFERENCES
threads(userid, threadid);

キースペースが次のようなものであるとします。

PRIMARY KEY
<begin>
Users()
Threads()
Users(2)
Users(3)
Threads(3)
Threads(3,"a")
Messages(3,"a",1)
Messages(3,"a",2)
Threads(3, "aa")
Users(9)
Users(10)
Threads(10)
UsersByFirstName("abc")
UsersByFirstName("abcd")
<end>

スプリットの例

以下は、スプリットがどのようなものかを示す例です。

SPLIT_STARTSPLIT_LIMIT は、テーブルまたはインデックスの行を表す場合や、<begin><end> でデータベースのキースペースの境界を表す場合があります。SPLIT_STARTSPLIT_LIMIT には、テーブル内の完全なキーの前に配置される、切り捨てられたキーも含まれる場合があります。たとえば、Threads(10)Users(10) にインターリーブされた Threads 行の接頭辞です。

SPLIT_START SPLIT_LIMIT AFFECTED_TABLES 説明
Users(3) Users(10) UsersByFirstNameUsersThreadsMessagesMessagesIdx スプリットは UserId=3 の行から始まり、UserId = 10 の行の前の行で終了します。スプリットには、Users テーブルの行と、UserId=3~10 のすべてのインターリーブされたテーブルの行が含まれます。
Messages(3,"a",1) Threads(3,"aa") ThreadsMessagesMessagesIdx スプリットは、UserId=3ThreadId="a"MessageId=1 の行から始まり、UserId=3ThreadsId = "aa" のキーを持つ行の前の行で終了します。スプリットには、Messages(3,"a",1)Threads(3,"aa") の間のすべてのテーブルが含まれます。split_startsplit_limit は同じ最上位テーブル行にインターリーブされているため、スプリットには開始と上限の間にインターリーブされたテーブル行が含まれます。インターリーブ テーブルがどのように配置されるかについては、schemas-overview をご覧ください。
Messages(3,"a",1) <end> UsersByFirstNameUsersThreadsMessagesMessagesIdx スプリットは、キーが UserId=3ThreadId="a"MessageId=1 の行から messages テーブルで開始されます。スプリットには、split_start から <end>(データベースのキースペースの終了)までのすべての行がホストされます。split_start の後のテーブルのすべての行(Users(4) など)がスプリットに含まれます。
<begin> Users(9) UsersByFirstNameUsersThreadsMessagesMessagesIdx スプリットは、<begin>(データベースのキースペースの先頭)から始まり、UserId=9 を含む Users 行の前の行で終了します。したがって、スプリットには、Users の前のすべてのテーブル行、UserId=9 の前の Users テーブルのすべての行、およびそのインターリーブ テーブルの行が含まれます。
Messages(3,"a",1) Threads(10) UsersByFirstNameUsersThreadsMessagesMessagesIdx スプリットは Users(3) にインターリーブされた Messages(3,"a", 1) で始まり、Threads(10) の前の行で終わります。Threads(10) は、Users(10) でインターリーブされた Threads テーブルの任意のキーの接頭辞である、切り捨てられたスプリットキーです。
Users() <end> UsersByFirstNameUsersThreadsMessagesMessagesIdx スプリットは、Users テーブルの完全なキーの前に位置する、Users() の切り捨てられたスプリットキーから始まります。スプリットは、データベースで使用可能なキースペースの最後まで延長されます。したがって、affected_tables には、Users テーブル、そのインターリーブ テーブルとインデックス、ユーザーの後に表示される可能性があるすべてのテーブルが含まれます。
Threads(10) UsersByFirstName("abc") UsersByFirstNameUsersThreadsMessagesMessagesIdx スプリットは UserId = 10Threads 行から始まり、"abc" の前のキーのインデックス UsersByFirstName で終わります。

ホット スプリットを見つけるクエリの例

次の例は、ホット スプリットの統計情報を取得するために使用できる SQL ステートメントを示しています。これらの SQL ステートメントは、クライアント ライブラリ、gcloud、または Google Cloud コンソールを使用して実行できます。

GoogleSQL

SELECT t.split_start,
       t.split_limit,
       t.cpu_usage_score,
       t.affected_tables,
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end =
  (SELECT MAX(interval_end)
  FROM    SPANNER_SYS.SPLIT_STATS_TOP_MINUTE)
ORDER BY  t.cpu_usage_score DESC;

PostgreSQL

SELECT t.split_start,
       t.split_limit,
       t.cpu_usage_score,
       t.affected_tables
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end = (
  SELECT MAX(interval_end)
  FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
)
ORDER BY t.cpu_usage_score DESC;

クエリ出力は次のようになります。

SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE AFFECTED_TABLES
Users(13) Users(76) 82 Messages,Users,Threads
Users(101) Users(102) 90 Messages,Users,Threads
Threads(10, "a") Threads(10, "aa") 100 Messages,Threads
Messages(631, "abc", 1) Messages(631, "abc", 3) 100 Messages
Threads(12, "zebra") Users(14) 76 Messages,Users,Threads
Users(620) <end> 100 Messages,Users,Threads

ホット スプリット統計情報のデータ保持

Spanner は、少なくとも次の期間中に各テーブルのデータを保持します。

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: 過去 6 時間を対象とする間隔。

ホット スプリットの統計情報を使用してホットスポットのトラブルシューティングを行う

このセクションでは、ホットスポットを検出してトラブルシューティングする方法について説明します。

調査する期間を選択する

Spanner データベースのレイテンシ指標を確認して、アプリケーションでレイテンシと CPU 使用率が高かった期間を確認します。たとえば、2024 年 5 月 18 日午後 10 時 50 分頃に問題が発生したことが示されます。

永続的なホットスポット化を確認する

Spanner は負荷に基づいた分割によって負荷を分散するため、ホットスポット化が 10 分以上続いているかどうかを調査することをおすすめします。次の例に示すように、SPANNER_SYS.SPLIT_STATS_TOP_MINUTE テーブルをクエリすることで確認できます。

GoogleSQL

SELECT Count(DISTINCT t.interval_end)
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.utilization >= 50
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

PostgreSQL

SELECT COUNT(DISTINCT t.interval_end)
FROM   SPLIT_STATS_TOP_MINUTE t
WHERE  t.utilization >= 50
  AND  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

前のクエリの結果が 10 の場合、データベースでホットスポット化が生じているため、さらにデバッグが必要になる可能性があります。

CPU_USAGE_SCORE レベルが最も高いスプリットを見つける

この例では、次の SQL を実行して、CPU_USAGE_SCORE レベルが最も高い行範囲を見つけます。

GoogleSQL

SELECT t.split_start,
       t.split_limit,
       t.affected_tables,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.cpu_usage_score >= 50
  AND  t.interval_end = "interval_end_date_time";

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

PostgreSQL

SELECT t.split_start,
       t.split_limit,
       t.affected_tables,
       t.cpu_usage_score
FROM   SPLIT_STATS_TOP_MINUTE t
WHERE  t.cpu_usage_score = 100
  AND  t.interval_end = 'interval_end_date_time'::timestamptz;

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

上記の SQL は次のように出力します。

SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE AFFECTED_TABLES
Users(180) <end> 85 Messages,Users,Threads
Users(24) Users(76) 76 Messages,Users,Threads

この結果のテーブルから、2 つのスプリットでホットスポットが発生したことがわかります。Spanner の負荷に基づいた分割では、これらのスプリットでホットスポットを解決しようとすることがあります。ただし、スキーマまたはワークロードに問題のあるパターンがある場合は、そうできない場合があります。介入が必要なスプリットがあるかどうかを検出するには、スプリットを 10 分以上追跡することをおすすめします。たとえば、次の SQL は過去 10 分間の最初のスプリットを追跡します。

GoogleSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.split_start = "users(180)"
  AND  t.split_limit = "<end>"
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

PostgreSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.split_start = 'users(180)'
  AND  t.split_limit = ''
  AND  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

上記の SQL は次のように出力します。

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-18T17:46:00Z Users(180) <end> 85
2024-05-18T17:47:00Z Users(180) <end> 85
2024-05-18T17:48:00Z Users(180) <end> 85
2024-05-18T17:49:00Z Users(180) <end> 85
2024-05-18T17:50:00Z Users(180) <end> 85

スプリットは過去数分間、ホットの状態になっていたようです。Spanner の負荷に基づいた分割によってホットスポットが軽減されていることを確認するために、スプリットを長時間観察することもできます。Spanner でロード バランシングをさらに進められない場合もあります。

たとえば、SPANNER_SYS.SPLIT_STATS_TOP_MINUTE テーブルにクエリを実行します。次のシナリオの例をご覧ください。

GoogleSQL

SELECT t.interval_end,
      t.split_start,
      t.split_limit,
      t.cpu_usage_score
FROM  SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE t.interval_end >= "interval_end_date_time"
      AND t.interval_end <= "interval_end_date_time";

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

PostgreSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t._cpu_usage
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

interval_end_date_time は、2024-05-18T17:40:00Z 形式で期間の日時に置き換えます。

単一のホット行

次の例では、Threads(10,"spanner") が 10 分以上ホット状態のままの単一行のスプリットにあることがわかります。これは、人気のある行に永続的な負荷がかかっている場合に発生する可能性があります。

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:41:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:42:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:43:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:44:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:45:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:46:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:47:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:48:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:49:00Z Threads(10,"spanner") Threads(10,"spanner1") 100
2024-05-16T20:50:00Z Threads(10,"spanner") Threads(10,"spanner1") 100

この単一キーはこれ以上分割できないため、Spanner はこの単一キーの負荷を分散できません。

ホットスポットの移動

次の例では、時間の経過とともに負荷が連続したスプリット間を移動し、時間間隔ごとに新しいスプリットに移っています。

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(1,"a") Threads(1,"aa") 100
2024-05-16T20:41:00Z Threads(1,"aa") Threads(1,"ab") 100
2024-05-16T20:42:00Z Threads(1,"ab") Threads(1,"c") 100
2024-05-16T20:43:00Z Threads(1,"c") Threads(1,"ca") 100

これは、キーを単調増加順に読み取りまたは書き込むワークロードが原因で発生する可能性があります。Spanner は、このアプリケーション動作の影響を軽減するために負荷を分散できません。

通常のロード バランシング

Spanner は、スプリットを追加するかスプリットを移動させることで、負荷を分散しようとします。次の例は、その例を示しています。

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(1000,"zebra") <end> 82
2024-05-16T20:41:00Z Threads(1000,"zebra") <end> 90
2024-05-16T20:42:00Z Threads(1000,"zebra") <end> 100
2024-05-16T20:43:00Z Threads(1000,"zebra") Threads(2000,"spanner") 100
2024-05-16T20:44:00Z Threads(1200,"c") Threads(2000) 92
2024-05-16T20:45:00Z Threads(1500,"c") Threads(1700,"zach") 76
2024-05-16T20:46:00Z Threads(1700) Threads(1700,"c") 76
2024-05-16T20:47:00Z Threads(1700) Threads(1700,"c") 50
2024-05-16T20:48:00Z Threads(1700) Threads(1700,"c") 39

ここでは、2024-05-16T17:40:00Z の大きなスプリットがさらに小さなスプリットに分割され、その結果、CPU_USAGE_SCORE 統計情報が減少しました。Spanner は、個々の行にスプリットを作成しない場合があります。スプリットは、CPU_USAGE_SCORE 統計情報の高騰の原因となるワークロードをミラーリングします。

ホット スプリットが 10 分以上持続している場合は、ホットスポットを軽減するためのベスト プラクティスをご覧ください。

ホットスポットを軽減するためのベスト プラクティス

ロード バランシングでレイテンシが軽減しない場合は、ホットスポットの原因を特定します。その後、ホットスポット ワークロードを減らすか、アプリケーション スキーマとロジックを最適化してホットスポットを回避します。

原因を特定する

  • ロックとトランザクションの分析情報を使用して、行範囲の開始キーがホットスプリット内にあるロック待機時間が長いトランザクションを探します。

  • Query Insights を使用して、ホット スプリットを含むテーブルから読み取られ、最近レイテンシが増加しているクエリ、またはレイテンシと CPU の比率が高いクエリを探します。

  • 最も古いアクティブなクエリを使用して、ホット スプリットを含むテーブルから読み取られ、予想よりもレイテンシが高いクエリを探します。

注意すべき特殊なケースは次のとおりです。

  • 有効期間(TTL)が最近有効にされたかどうかを確認します。古いデータからのスプリットが多い場合、TTL により一括削除中に CPU_USAGE_SCORE レベルが上昇する可能性があります。この場合、最初の削除が完了すると、問題は自動的に解決します。

ワークロードを最適化する

次のステップ