分割統計資料

本頁說明如何偵測及偵錯資料庫中的熱點。您可以使用 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 就不會擷取統計資料。如果 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 所含資料列可能儲存於分割的資料表

分割起始和分割極限鍵

分割是資料庫的連續資料列範圍,並由其 startlimit 鍵定義。分割作業可以是單一資料列、狹窄的資料列範圍或寬廣的資料列範圍,且分割作業可以包含多個資料表或索引。

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)Threads 列在 Users(10) 中交錯時的前置字元。

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 分割作業從 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 分割作業會從 Threads 列開始,UserId = 10UserId = 10,結束於 "abc" 前鍵的索引 UsersByFirstName

查找熱門分割的查詢範例

以下範例顯示可用來擷取熱門分割統計資料的 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.utilization >= 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.utilization >= 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-05-16T17:40:00Z 的較大分割區塊會進一步細分為較小的分割區塊,因此 CPU_USAGE_SCORE 統計資料會減少。Spanner 可能不會將資料分割成個別資料列。這些分割作業會反映導致 CPU_USAGE_SCORE 統計資料偏高的負載。

如果您發現熱門分割持續超過 10 分鐘,請參閱減少熱點的最佳做法

緩解熱點的最佳做法

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

找出原因

  • 使用鎖定和交易深入分析,找出鎖定等待時間過長的交易,其中資料列範圍開始鍵位於熱門分割區內。

  • 使用查詢深入分析功能,找出從包含熱門分割的資料表讀取資料的查詢,並且最近延遲時間增加,或延遲時間與 CPU 的比例較高。

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

請留意以下特殊情況:

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

改善工作負載

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

後續步驟