本頁說明如何偵測及偵錯資料庫中的熱點。您可以使用 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 Studio 頁面 位於 Google Cloud 控制台中。
gcloud spanner databases execute-sql
指令。executeSql
或executeStreamingSql
方法。
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_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)
是插入 Users(10)
中任何 Threads
列的前置字元。
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 |
分割作業從 Messages(3,"a", 1) 開始交錯於 Users(3) ,並在 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 開始,並在索引 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
層級。在這種情況下,初始刪除作業完成後,問題應會自動解決。
最佳化工作負載
後續步驟
- 瞭解結構定義設計最佳做法。
- 瞭解 Key Visualizer。
- 查看結構定義設計範例。
- 瞭解如何使用分割洞察資訊主頁偵測熱點。