分割統計資料

本頁說明如何偵測及偵錯資料庫中的熱點。您可以使用 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 提供的下列單一讀取方法不支援 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 就不會擷取統計資料。如果 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百分比。50% 的百分比表示存在溫和或熱門的 | 分割 |CPU_USAGE_SCORE
AFFECTED_TABLES STRING ARRAY 所含資料列可能儲存於分割的資料表

分割起始和分割限制鍵

分割是資料庫的連續資料列範圍,由「開始」和「限制」鍵定義。分割可以是單一資料列、窄資料列範圍或寬資料列範圍,且分割可包含多個資料表或索引。

SPLIT_STARTSPLIT_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);

假設您的鍵空間如下所示:

主鍵
<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 的資料列開始。分割會包含 split_start<end> 的所有資料列,也就是資料庫鍵空間的結尾。split_start 後方所有資料表 (例如 Users(4)) 的資料列都會納入分割。
<begin> Users(9) UsersByFirstNameUsersThreadsMessagesMessagesIdx 分割作業會從資料庫鍵空間的開頭 <begin> 開始,並在 Users 資料列的前一個資料列 (含 UserId=9) 結束。因此,分割會包含 Users 前的所有資料表資料列,以及 Users 資料表中 UserId=9 前的所有資料列,以及交錯式資料表的資料列。
Messages(3,"a",1) Threads(10) UsersByFirstNameUsersThreadsMessagesMessagesIdx 分割作業從 Messages(3,"a", 1) 開始交錯於 Users(3),並在 Threads(10) 前方的資料列結束。Threads(10) 是截斷的分割索引鍵,屬於交錯在 Users(10) 中的 Threads 資料表任何索引鍵的前置字串。
Users() <end> UsersByFirstNameUsersThreadsMessagesMessagesIdx 分割會從 Users() 的截斷分割鍵開始,該鍵會先於 Users 資料表的任何完整鍵。分割作業會持續進行,直到資料庫中可能的鍵空間結束為止。因此,affected_tables 會涵蓋 Users 資料表、其交錯資料表和索引,以及使用者之後可能出現的所有資料表。
Threads(10) UsersByFirstName("abc") UsersByFirstNameUsersThreadsMessagesMessagesIdx 分割作業會從 Threads 列的 UserId = 10 開始,並在索引 UsersByFirstName 處結束,也就是 "abc" 前方的鍵。

尋找熱門分割的查詢範例

下列範例顯示可用來擷取熱分割統計資料的 SQL 陳述式。您可以使用用戶端程式庫、gcloud 或 Google Cloud 主控台執行這些 SQL 陳述式。

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.cpu_usage_score >= 50
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

PostgreSQL

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

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

如果先前的查詢結果等於 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";

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

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;

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

先前的 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

從這個結果表格中,我們可以看到有兩個分割發生熱點。Spanner 依負載進行分割可能會嘗試解決這些分割上的熱點。不過,如果結構定義或工作負載中存在有問題的模式,系統可能就無法這麼做。如要偵測是否有需要介入處理的分割,建議至少追蹤分割 10 分鐘。舉例來說,下列 SQL 會追蹤過去十分鐘內的第一個分割。

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";

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

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;

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

先前的 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";

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

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;

使用 2024-05-18T17:40:00Z 格式,將 interval_end_date_time 替換為間隔的日期和時間。

單一熱門資料列

在下列範例中,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 年 5 月 16 日下午 5 點 40 分 (UTC),較大的分割進一步拆分成較小的分割,因此 CPU_USAGE_SCORE 統計資料減少。Spanner 可能不會將資料列分割成個別資料列。分割區會反映導致高 CPU_USAGE_SCORE 統計資料的工作負載。

如果發現熱點持續超過 10 分鐘,請參閱熱點緩解最佳做法

減輕熱點影響的最佳做法

如果負載平衡無法減少延遲,下一步是找出熱點的原因。之後,您可以選擇減少熱點工作負載,或是最佳化應用程式結構和邏輯,避免出現熱點。

找出原因

  • 使用「鎖定和交易洞察」,找出鎖定等待時間較長的交易,且資料列範圍的開始鍵位於熱門分割區內。

  • 使用查詢洞察找出從含有熱門分割的資料表讀取資料,且最近延遲時間增加,或延遲時間與 CPU 比率較高的查詢。

  • 使用「最耗時的有效查詢」,找出從包含熱門分割的資料表讀取資料,且延遲時間超出預期的查詢。

請注意以下特殊情況:

  • 請檢查存留時間 (TTL) 最近是否已啟用。如果舊資料有大量分割,TTL 可能會在大量刪除期間提高 CPU_USAGE_SCORE 層級。在這種情況下,初始刪除作業完成後,問題應會自動解決。

最佳化工作負載

  • 遵循 SQL 最佳做法。請考慮過時的讀取作業、未先執行讀取作業的寫入作業,或新增索引。
  • 遵循結構定義最佳做法。請確保結構定義的設計可處理負載平衡,並避免資源使用率不均。

後續步驟