本頁說明如何偵測及偵錯資料庫中的熱點。您可以使用 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 介面取得。您可以透過下列方式存取這項資料:
- Google Cloud 控制台中資料庫的 Spanner Studio 頁面
gcloud spanner databases execute-sql
指令executeQuery
API
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 |
所含資料列可能儲存於分割的資料表 |
分割起始和分割極限鍵
分割是資料庫的連續資料列範圍,並由其 start 和 limit 鍵定義。分割作業可以是單一資料列、狹窄的資料列範圍或寬廣的資料列範圍,且分割作業可以包含多個資料表或索引。
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);
假設您的鍵空間如下所示:
主鍵 |
---|
<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_START
和 SPLIT_LIMIT
可能會指出資料表或索引的資料列,也可以是 <begin>
和 <end>
,代表資料庫鍵空間的邊界。SPLIT_START
和 SPLIT_LIMIT
也可能包含經過截斷的鍵,也就是資料表中任何完整鍵之前的鍵。例如,Threads(10)
是 Threads
列在 Users(10)
中交錯時的前置字元。
SPLIT_START | SPLIT_LIMIT | AFFECTED_TABLES | 說明 |
---|---|---|---|
Users(3) |
Users(10) |
UsersByFirstName 、Users 、Threads 、Messages 、MessagesIdx |
分割作業會從含有 UserId=3 的行開始,並在含有 UserId = 10 的行之前結束。分割內容包含 Users 表格列,以及 UserId=3 到 10 的所有交錯表格列。 |
Messages(3,"a",1) |
Threads(3,"aa") |
Threads 、Messages 、MessagesIdx |
分割作業會從包含 UserId=3 、ThreadId="a" 和 MessageId=1 的資料列開始,並在 UserId=3 和 ThreadsId = "aa" 鍵的資料列前結束。分割作業包含 Messages(3,"a",1) 和 Threads(3,"aa") 之間的所有資料表。由於 split_start 和 split_limit 在同一個頂層資料表列中交錯,因此分割作業會包含起始和限制之間的交錯資料表列。請參閱schemas-overview,瞭解交錯式資料表如何並存。 |
Messages(3,"a",1) |
<end> |
UsersByFirstName 、Users 、Threads 、Messages 、MessagesIdx |
分割作業會從訊息資料表中鍵為 UserId=3 、ThreadId="a" 和 MessageId=1 的資料列開始。分割作業會主控從 split_start 到 <end> 的所有資料列,也就是資料庫鍵空間的結尾。分割作業會納入 split_start 後方的所有資料表資料列,例如 Users(4) 。 |
<begin> |
Users(9) |
UsersByFirstName 、Users 、Threads 、Messages 、MessagesIdx |
分割作業會從 <begin> 開始,也就是資料庫鍵空間的開頭,並以 Users 行前 UserId=9 為結束。因此,分割作業會包含 Users 之前的所有表格資料列,以及 Users 資料表中 UserId=9 之前的所有資料列,以及其交錯式資料表的資料列。 |
Messages(3,"a",1) |
Threads(10) |
UsersByFirstName 、Users 、Threads 、Messages 、MessagesIdx |
分割作業從 Users(3) 中交錯的 Messages(3,"a", 1) 開始,結束於 Threads(10) 前面的資料列。Threads(10) 是截斷的分割鍵,是 Users(10) 中交錯的 Threads 資料表的任何鍵的前置字串。 |
Users() |
<end> |
UsersByFirstName 、Users 、Threads 、Messages 、MessagesIdx |
分割起始於 Users() 的截斷分割鍵,該鍵位於 Users 資料表的任何完整鍵之前。分割範圍會延伸至資料庫中可能的鍵空間結尾。因此,affected_tables 會涵蓋 Users 資料表、其交錯資料表和索引,以及使用者之後可能會顯示的所有資料表。 |
Threads(10) |
UsersByFirstName("abc") |
UsersByFirstName 、Users 、Threads 、Messages 、MessagesIdx |
分割作業會從 Threads 列開始,UserId = 10 為 UserId = 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
等級。在這種情況下,初始刪除作業完成後,問題應會自動解決。
改善工作負載
後續步驟
- 瞭解結構定義設計最佳做法。
- 瞭解 Key Visualizer。
- 查看結構定義設計範例。
- 瞭解如何使用分割洞察資訊主頁偵測熱點。