本文介紹了PolarDB PostgreSQL版的可維護窗口期垃圾回收功能的使用方法以及示例等內容。
前提條件
支持的PolarDB PostgreSQL版的版本如下:
PostgreSQL 14(內核小版本14.12.24.0及以上)
PostgreSQL 11(內核小版本1.1.42及以上)
您可通過如下語句查看PolarDB PostgreSQL版的內核小版本號:
PostgreSQL 14
SELECT version();
PostgreSQL 11
SHOW polar_version;
背景信息
PolarDB PostgreSQL版與原生PostgreSQL一樣會在后臺啟動自動清理(autovacuum)進程去執行垃圾回收操作,它帶來的收益包括但不限于:
回收老舊版本的數據以減少磁盤空間占用。
更新統計信息以確保查詢優化器能夠選擇最優執行計劃。
防止事務ID回卷,從而有效降低集群不可用的風險。
這些垃圾回收操作比較消耗硬件資源,為了避免過于頻繁地執行自動清理,原生PostgreSQL為其設置了一些觸發條件(詳情請參見自動清理參數配置),只有滿足這些條件時才會啟動清理進程。 由于觸發條件與數據變更行數和數據庫年齡相關,越是在業務的高峰期,數據變更往往越多,事務ID消耗速度越快,自動清理的觸發頻率就越高,導致了如下問題:
資源使用過高問題:在業務高峰期間自動清理進程頻繁執行垃圾回收并占用大量CPU和I/O,與業務讀寫請求爭搶硬件資源,影響數據庫的讀寫性能。在下圖的示例中,自動清理進程在白天業務高峰期間的CPU使用率和I/O吞吐量在所有進程中排名第一。
鎖表阻塞讀寫問題:自動清理進程在回收空頁的過程中需要短暫持有排它鎖并阻塞單張表上的業務請求,雖然阻塞時間一般不長,但是在業務高峰期間即使很短暫的阻塞也無法接受。
計劃緩存失效問題:自動清理進程收集統計信息并導致原有的執行計劃緩存(Plan Cache)失效,新的查詢需要重新生成執行計劃,可能發生業務高峰期間有多個連接并行生成執行計劃的情況,影響多個連接的業務請求響應時間。
說明PolarDB PostgreSQL版的全局執行計劃緩存功能可以一定程度減少此類問題的影響。
以上問題的核心在于原生PostgreSQL沒有業務低峰期的概念,而現實場景下的業務通常有明顯的高峰期和低峰期。PolarDB PostgreSQL版允許配置一個業務低峰期間的可維護窗口,利用業務低峰期的閑置硬件資源進行積極和充分的垃圾回收,從而降低業務高峰期間的自動清理頻次,將更多的硬件資源留給業務讀寫請求,優化讀寫性能。
預期收益
可維護窗口內的垃圾回收預期可以緩解上文提到的各種問題,此外由于可維護窗口內的清理策略相比于原生PostgreSQL的自動清理更為積極,因此還可能有一些額外的收益。總的來說有如下收益:
資源使用率優化:業務低峰期已經進行了垃圾回收,導致業務高峰期內的自動清理概率大幅下降,資源使用率也相應下降。
數據庫年齡優化:業務低峰期回收更多事務ID,防止事務ID回卷造成的數據庫不可用問題。
統計信息&慢SQL優化:收集更多表的統計信息,幫助優化器選擇更為準確的查詢計劃,減少統計信息過期導致的慢SQL。
鎖表問題優化:業務高峰期內自動清理操作鎖表導致阻塞業務讀寫的問題發生概率降低。
計劃緩存失效問題優化:業務高峰期內自動清理操作導致計劃緩存失效的問題發生概率降低。
使用方法
配置可維護窗口
創建插件。
在postgres數據庫和所有需要執行垃圾回收的數據庫上都需要創建
polar_advisor
插件。CREATE EXTENSION IF NOT EXISTS polar_advisor;
對于已經安裝
polar_advisor
插件的PolarDB集群,可通過以下命令升級:ALTER EXTENSION polar_advisor UPDATE;
時間窗口設置。
執行如下命令設置可維護窗口時間段。
-- 在postgres數據庫執行 SELECT polar_advisor.set_advisor_window(start_time, end_time);
start_time:表示窗口開始時間。
end_time:表示窗口結束時間。
窗口默認會當天生效,之后每天都會自動在窗口時間段內執行垃圾回收操作。
說明僅在postgres數據庫中配置的窗口時間會生效,在其他數據庫上設置窗口時間無效。
窗口時間中的時區偏移量必須與PolarDB集群的時區設置保持一致,否則窗口時間無法生效。
將北京時間(東八區,+08時區)的每天晚上23點到第二天凌晨2點這個時間段作為可維護窗口,該集群每天都會在這個時間段內執行垃圾回收操作,示例如下:
SELECT polar_advisor.set_advisor_window(start_time => '23:00:00+08', end_time => '02:00:00+08');
時間窗口查看。
執行如下命令查看設置的可維護窗口信息。
-- 在 postgres 數據庫執行 -- 查看可維護窗口詳情 SELECT * FROM polar_advisor.get_advisor_window(); -- 查看可維護窗口時間長度,單位為秒 SELECT polar_advisor.get_advisor_window_length(); -- 查看當前時間是否在窗口時間內 SELECT now(), * FROM polar_advisor.is_in_advisor_window(); -- 查看當前時間距離下一次窗口開始時間的時長,單位為秒 SELECT * FROM polar_advisor.get_secs_to_advisor_window_start(); -- 查看當前時間距離下一次窗口結束時間的時長,單位為秒 SELECT * FROM polar_advisor.get_secs_to_advisor_window_end();
示例如下:
-- 查看可維護窗口詳情 postgres=# SELECT * FROM polar_advisor.get_advisor_window(); start_time | end_time | enabled | last_error_time | last_error_detail | others -------------+-------------+---------+-----------------+-------------------+-------- 23:00:00+08 | 02:00:00+08 | t | | | (1 row) -- 查看可維護窗口時間長度 postgres=# SELECT polar_advisor.get_advisor_window_length() / 3600.0 AS "窗口長度/h"; 窗口長度/h -------------------- 3.0000000000000000 (1 row) -- 查看當前時間是否在窗口時間內 postgres=# SELECT now(), * FROM polar_advisor.is_in_advisor_window(); now | is_in_advisor_window -------------------------------+---------------------- 2024-04-01 07:40:37.733911+00 | f (1 row) -- 查看當前時間距離下一次窗口開始時間的時長 postgres=# SELECT * FROM polar_advisor.get_secs_to_advisor_window_start(); secs_to_window_start | time_now | window_start | window_end ----------------------+--------------------+--------------+------------- 26362.265179 | 07:40:37.734821+00 | 15:00:00+00 | 18:00:00+00 (1 row) -- 查看當前時間距離下一次窗口結束時間的時長 postgres=# SELECT * FROM polar_advisor.get_secs_to_advisor_window_end(); secs_to_window_end | time_now | window_start | window_end --------------------+--------------------+--------------+------------- 36561.870337 | 07:40:38.129663+00 | 15:00:00+00 | 18:00:00+00 (1 row)
關閉/開啟窗口。
設置窗口后,窗口默認開啟,會在每天的窗口時間內執行垃圾回收。如果某一天的業務低峰期內不希望集群執行垃圾回收(例如需要手動執行其他運維操作,擔心與垃圾回收沖突),則可以執行以下命令來關閉可維護窗口,等到運維工作結束以后再調用函數重新開啟窗口。
-- 在 postgres 數據庫執行 -- 關閉可維護窗口 SELECT polar_advisor.disable_advisor_window(); -- 開啟可維護窗口 SELECT polar_advisor.enable_advisor_window(); -- 查看窗口是否已開啟 SELECT polar_advisor.is_advisor_window_enabled();
示例如下:
-- 窗口已開啟 postgres=# SELECT polar_advisor.is_advisor_window_enabled(); is_advisor_window_enabled --------------------------- t (1 row) -- 關閉窗口 postgres=# SELECT polar_advisor.disable_advisor_window(); disable_advisor_window ------------------------ (1 row) -- 窗口已關閉 postgres=# SELECT polar_advisor.is_advisor_window_enabled(); is_advisor_window_enabled --------------------------- f (1 row) -- 重新開啟窗口 postgres=# SELECT polar_advisor.enable_advisor_window(); enable_advisor_window ----------------------- (1 row) -- 窗口已開啟 postgres=# SELECT polar_advisor.is_advisor_window_enabled(); is_advisor_window_enabled --------------------------- t (1 row)
其他配置
設置黑名單。
如果配置了可維護窗口,默認情況下數據庫會自行決定在窗口期內對哪些表執行垃圾回收操作,任何一張表都有可能被執行。如果希望某張表不要被選中,則可以通過以下命令將該表加入到黑名單中。
-- 在具體的業務數據庫中執行 -- 將表加入VACUUM & ANALYZE黑名單 SELECT polar_advisor.add_relation_to_vacuum_analyze_blacklist(schema_name, relation_name); -- 驗證表是否在VACUUM & ANALYZE黑名單中 SELECT polar_advisor.is_relation_in_vacuum_analyze_blacklist(schema_name, relation_name); -- 獲取VACUUM & ANALYZE黑名單 SELECT * FROM polar_advisor.get_vacuum_analyze_blacklist();
示例如下:
-- 將 public.t1 表加入黑名單 postgres=# SELECT polar_advisor.add_relation_to_vacuum_analyze_blacklist('public', 't1'); add_relation_to_vacuum_analyze_blacklist --------------------------- t (1 row) -- 查看該表是否在黑名單中 postgres=# SELECT polar_advisor.is_relation_in_vacuum_analyze_blacklist('public', 't1'); is_relation_in_vacuum_analyze_blacklist -------------------------- t (1 row) -- 獲取完整的黑名單列表,查看該表是否在黑名單中 postgres=# SELECT * FROM polar_advisor.get_vacuum_analyze_blacklist(); schema_name | relation_name | action_type -------------+---------------+---------------- public | t1 | VACUUM ANALYZE (1 row)
設置活躍連接數閾值。
為了避免可維護窗口內的垃圾回收操作影響正常業務,系統會自動檢測可維護窗口期間的活躍連接數,超出閾值時將自動取消垃圾回收操作的執行,您可以手動調整該閾值以適應您的業務特性(閾值默認為5~10,具體與集群的CPU核數有關)。
-- 在 postgres 數據庫執行 -- 獲取可維護窗口可以接受的連接數閾值,實際的活躍連接數高于該值就不會執行垃圾回收 SELECT polar_advisor.get_active_user_conn_num_limit(); -- 在業務低峰期內執行 SQL,獲取業務低峰期的實際活躍連接數(或者通過 PolarDB 控制臺->性能監控->高級監控->標準試圖->會話連接->active_session查看) SELECT COUNT(*) FROM pg_catalog.pg_stat_activity sa JOIN pg_catalog.pg_user u ON sa.usename = u.usename WHERE sa.state = 'active' AND sa.backend_type = 'client backend' AND NOT u.usesuper; -- 人為設置活躍連接數閾值,將覆蓋系統默認的閾值 SELECT polar_advisor.set_active_user_conn_num_limit(active_user_conn_limit); -- 取消設置活躍連接數閾值,將恢復使用系統默認的閾值 SELECT polar_advisor.unset_active_user_conn_num_limit();
示例如下:
-- 獲取實例默認活躍連接數閾值,該實例閾值為5(不同實例的閾值可能不同,具體與CPU核數有關) postgres=# SELECT polar_advisor.get_active_user_conn_num_limit(); NOTICE: get active user conn limit by CPU cores number get_active_user_conn_num_limit -------------------------------- 5 (1 row) -- 獲取當前實際活躍連接數,結果為8,大于上面獲取的閾值5,因此系統會認為活躍連接數較多,不能在可維護窗口時間內執行垃圾回收 postgres=# SELECT COUNT(*) FROM pg_catalog.pg_stat_activity sa JOIN pg_catalog.pg_user u ON sa.usename = u.usename postgres-# WHERE sa.state = 'active' AND sa.backend_type = 'client backend' AND NOT u.usesuper; count ------- 8 (1 row) -- 將活躍連接數閾值設為10,大于實際的活躍連接數8,系統會認為實際的活躍連接數8沒有超過閾值10,可以執行垃圾回收 postgres=# SELECT polar_advisor.set_active_user_conn_num_limit(10); set_active_user_conn_num_limit -------------------------------- (1 row) -- 查看活躍連接數閾值,顯示為10,就是上一步手動設置的值 postgres=# SELECT polar_advisor.get_active_user_conn_num_limit(); NOTICE: get active user conn limit from table get_active_user_conn_num_limit -------------------------------- 10 (1 row) -- 取消設置活躍連接數閾值 postgres=# SELECT polar_advisor.unset_active_user_conn_num_limit(); unset_active_user_conn_num_limit ---------------------------------- (1 row) -- 取消設置以后,活躍連接數閾值恢復到默認值5 postgres=# SELECT polar_advisor.get_active_user_conn_num_limit(); NOTICE: get active user conn limit by CPU cores number get_active_user_conn_num_limit -------------------------------- 5 (1 row)
查看結果
可維護窗口內執行的垃圾回收操作的結果和收益都記錄在postgres數據庫的日志表中,保留最近90天的數據。
表結構
polar_advisor.db_level_advisor_log
表保存了數據庫級別的每一輪垃圾回收操作的各項信息。
CREATE TABLE polar_advisor.db_level_advisor_log (
id BIGSERIAL PRIMARY KEY,
exec_id BIGINT,
start_time TIMESTAMP WITH TIME ZONE,
end_time TIMESTAMP WITH TIME ZONE,
db_name NAME,
event_type VARCHAR(100),
total_relation BIGINT,
acted_relation BIGINT,
age_before BIGINT,
age_after BIGINT,
others JSONB
);
參數說明:
參數名稱 | 說明 |
id | 表示表的主鍵,自動遞增。 |
exec_id | 表示執行的輪次,通常每天運行一輪,一輪可以操作多個數據庫,所以當天的多條記錄的 |
start_time | 表示操作開始的時間。 |
end_time | 表示操作結束的時間。 |
db_name | 表示操作的數據庫名稱。 |
event_type | 表示操作類型,當前僅支持 |
total_relation | 表示表中可以被操作的表和索引數量。 |
acted_relation | 表示實際操作的表和索引數量。 |
age_before | 表示操作前的數據庫年齡。 |
age_after | 表示操作后的數據庫年齡。 |
others | 包含較多擴展的統計數據:
|
polar_advisor.advisor_log
表保存了表/索引級別的每一次垃圾回收操作的詳細信息,polar_advisor.db_level_advisor_log
表中的一條記錄對應polar_advisor.advisor_log
表的多條記錄。
CREATE TABLE polar_advisor.advisor_log (
id BIGSERIAL PRIMARY KEY,
exec_id BIGINT,
start_time TIMESTAMP WITH TIME ZONE,
end_time TIMESTAMP WITH TIME ZONE,
db_name NAME,
schema_name NAME,
relation_name NAME,
event_type VARCHAR(100),
sql_cmd TEXT,
detail TEXT,
tuples_deleted BIGINT,
tuples_dead_now BIGINT,
tuples_now BIGINT,
pages_scanned BIGINT,
pages_pinned BIGINT,
pages_frozen_now BIGINT,
pages_truncated BIGINT,
pages_now BIGINT,
idx_tuples_deleted BIGINT,
idx_tuples_now BIGINT,
idx_pages_now BIGINT,
idx_pages_deleted BIGINT,
idx_pages_reusable BIGINT,
size_before BIGINT,
size_now BIGINT,
age_decreased BIGINT,
others JSONB
);
參數說明:
參數名稱 | 說明 |
id | 表示表的主鍵,自動遞增。 |
exec_id | 表示執行的輪次,通常每天運行一輪,一輪可以操作多個數據庫,所以當天的多條記錄的 |
start_time | 表示操作開始的時間。 |
end_time | 表示操作結束的時間。 |
db_name | 表示操作的數據庫名稱。 |
schema_name | 表示操作的數據庫模式名稱。 |
relation_name | 表示操作的數據庫表/索引名稱。 |
event_type | 表示操作類型,當前僅支持 |
sql_cmd | 表示具體執行的操作命令,例如 |
detail | 表示操作的結果詳情,例如 |
tuples_deleted | 表示本次操作中表回收的死元組數量。 |
tuples_dead_now | 表示本次操作后表中遺留的死元組數量。 |
tuples_now | 表示本次操作后表的活元組數量。 |
pages_scanned | 表示本次操作中掃描的頁數。 |
pages_pinned | 表示本次操作中因為緩存被引用而無法刪除的頁數。 |
pages_frozen_now | 表示本次操作后被凍結的頁數。 |
pages_truncated | 表示本次操作中刪除/截斷的空頁數。 |
pages_now | 表示本次操作后表的頁數。 |
idx_tuples_deleted | 表示本次操作中回收的索引死元組數量。 |
idx_tuples_now | 表示本次操作后索引的活元組數量。 |
idx_pages_now | 表示本次操作后索引的頁數。 |
idx_pages_deleted | 表示本次操作中刪除的索引頁數。 |
idx_pages_reusable | 表示本次操作中重新利用的索引頁數。 |
size_before | 表示本次操作前的表/索引大小。 |
size_after | 表示本次操作后的表/索引大小。 |
age_decreased | 表示本次操作前后的表年齡下降大小。 |
others | 表示擴展的統計數據。 |
統計數據
查看近期每一輪垃圾回收記錄的開始時間、結束時間、操作的表/索引數量,示例如下:
-- 在 postgres 數據庫執行 SELECT COUNT(*) AS "表/索引數量", MIN(start_time) AS "開始時間", MAX(end_time) AS "結束時間", exec_id AS "輪次" FROM polar_advisor.advisor_log GROUP BY exec_id ORDER BY exec_id DESC;
結果顯示如下,可以看到最近3輪執行垃圾回收的表數量都在4390左右,執行時間都在凌晨1-4點。
表/索引數量 | 開始時間 | 結束時間 | 輪次 -------------+--------------------------------+--------------------------------+------ 4391 | 2024-09-23 01:00:09.413901 +08 | 2024-09-23 03:25:39.029702 +08 | 139 4393 | 2024-09-22 01:03:07.365759 +08 | 2024-09-22 03:37:45.227067 +08 | 138 4393 | 2024-09-21 01:03:08.094989 +08 | 2024-09-21 03:45:20.280011 +08 | 137
查看近期每天內執行垃圾回收的表/索引數量,按日期統計,示例如下:
-- 在 postgres 數據庫執行 SELECT start_time::pg_catalog.date AS "時間", count(*) AS "表/索引數量" FROM polar_advisor.advisor_log GROUP BY start_time::pg_catalog.date ORDER BY start_time::pg_catalog.date DESC, count(*) DESC;
結果顯示如下,可以看到最近3天內每一天執行垃圾回收的表數量都在4390左右。
時間 | 表/索引數量 ------------+------------- 2024-09-23 | 4391 2024-09-22 | 4393 2024-09-21 | 4393
查看最近執行垃圾回收的表/索引數量,按日期和數據庫統計,示例如下:
-- 在 postgres 數據庫執行 SELECT start_time::pg_catalog.date AS "時間", count(*) AS "表/索引數量" FROM polar_advisor.advisor_log GROUP BY start_time::pg_catalog.date ORDER BY start_time::pg_catalog.date DESC, count(*) DESC;
結果顯示如下,可以看到最近3天對postgres、db_123、db_12345、db_123456789這些數據庫執行過垃圾回收,每個數據庫執行了幾十個到幾百個表/索引不等。
時間 | DB | 表/索引數量 -------------+----------------+------------- 2024-03-05 | db_123456789 | 697 2024-03-05 | db_123 | 277 2024-03-04 | db_123456789 | 695 2024-03-04 | db_123 | 267 2024-03-04 | db_12345 | 174 2024-03-03 | postgres | 65 (6 rows)
詳細數據
查看近期執行垃圾回收的數據庫的收益信息,示例如下:
-- 在 postgres 數據庫執行 SELECT id, start_time AS "開始時間", end_time AS "結束時間", db_name AS "數據庫", event_type AS "操作類型", total_relation AS "數據庫總表數量", acted_relation AS "操作的表數量", CASE WHEN others->>'cluster_age_before' IS NOT NULL AND others->>'cluster_age_after' IS NOT NULL THEN (others->>'cluster_age_before')::BIGINT - (others->>'cluster_age_after')::BIGINT ELSE NULL END AS "年齡下降", CASE WHEN others->>'db_size_before' IS NOT NULL AND others->>'db_size_after' IS NOT NULL THEN (others->>'db_size_before')::BIGINT - (others->>'db_size_after')::BIGINT ELSE NULL END AS "存儲空間下降" FROM polar_advisor.db_level_advisor_log ORDER BY id DESC;
結果顯示如下,可以看到最近三次執行的操作都是
VACUUM
。id | 開始時間 | 結束時間 | 數據庫 | 操作類型 | 數據庫總表數量 | 操作的表數量 | 年齡下降 | 存儲空間下降 ---------+-------------------------------+-------------------------------+----------------+----------+----------------+--------------+----------+-------------- 1184 | 2024-03-05 00:44:26.776894+08 | 2024-03-05 00:45:56.396519+08 | db_12345 | VACUUM | 174 | 164 | 694 | 0 1183 | 2024-03-05 00:43:30.243505+08 | 2024-03-05 00:44:26.695602+08 | db_123456789 | VACUUM | 100 | 90 | 396 | 0 1182 | 2024-03-05 00:41:47.70952+08 | 2024-03-05 00:43:30.172527+08 | db_12345 | VACUUM | 163 | 153 | 701 | 0 (3 rows)
查看近期執行垃圾回收的表的收益信息,示例如下:
-- 在 postgres 數據庫執行 SELECT start_time AS "開始時間", end_time AS "結束時間", db_name AS "數據庫", schema_name AS "模式", relation_name AS "表/索引", event_type AS "操作類型", tuples_deleted AS "回收死元組數", pages_scanned AS "掃描頁數",pages_truncated AS "回收頁數", idx_tuples_deleted AS "回收索引死元組數", idx_pages_deleted AS "回收索引頁數", age_decreased AS "表年齡下降" FROM polar_advisor.advisor_log ORDER BY id DESC;
結果顯示如下,可以看到最近三次操作回收的死元組數量、回收的頁數、表年齡下降等信息。
開始時間 | 結束時間 | 數據庫 | 模式 | 表/索引 | 操作類型 | 回收死元組數 | 掃描頁數 | 回收頁數 | 回收索引死元組數 | 回收索引頁數 | 表年齡下降 -------------------------------+-------------------------------+----------+--------+--------+---------+------------+---------+---------+---------------+------------+------------ 2024-03-05 00:45:56.204254+08 | 2024-03-05 00:45:56.357263+08 | db_12345 | public | cccc | VACUUM | 0 | 33 | 0 | 0 | 0 | 1345944 2024-03-05 00:45:56.068499+08 | 2024-03-05 00:45:56.200036+08 | db_12345 | public | aaaa | VACUUM | 0 | 28 | 0 | 0 | 0 | 1345946 2024-03-05 00:45:55.945677+08 | 2024-03-05 00:45:56.065316+08 | db_12345 | public | bbbb | VACUUM | 0 | 0 | 0 | 0 | 0 | 1345947 (3 rows)
查看數據庫年齡下降最多的操作記錄。
PolarDB PostgreSQL版共有約21億個可用的事務ID,通過數據庫年齡來衡量已經消耗的事務ID數量,年齡達到21億時將發生事務ID回卷,數據庫將不可用,因此數據庫年齡越小越好。
-- 在 postgres 數據庫執行 -- 獲取數據庫實例年齡下降最大的記錄對應的數據庫和操作類型 SELECT id, exec_id AS "輪次", start_time AS "開始時間", end_time AS "結束時間", db_name AS "數據庫", event_type AS "操作類型", CASE WHEN others->>'cluster_age_before' IS NOT NULL AND others->>'cluster_age_after' IS NOT NULL THEN (others->>'cluster_age_before')::BIGINT - (others->>'cluster_age_after')::BIGINT ELSE NULL END AS "年齡下降" FROM polar_advisor.db_level_advisor_log ORDER BY "年齡下降" DESC NULLS LAST; -- 根據上一步獲取的輪次信息獲取該輪操作中具體導致數據庫年齡下降的詳細記錄 SELECT id, start_time AS "開始時間", end_time AS "結束時間", db_name AS "數據庫", schema_name AS "模式", relation_name AS "表名", sql_cmd AS "命令", event_type AS "操作類型", age_decreased AS "年齡下降" FROM polar_advisor.advisor_log WHERE exec_id = 91 ORDER BY "年齡下降" DESC NULLS LAST; -- 獲取當前數據庫年齡(任何一個數據庫皆可執行)(或者通過 PolarDB 控制臺->性能監控->高級監控->標準試圖->Vacuum->db_age 查看) SELECT MAX(pg_catalog.age(datfrozenxid)) AS "實例年齡" FROM pg_catalog.pg_database;
結果顯示如下:
-- 2024-02-22 這天對 aaaaaaaaaaaaa 這個數據庫執行的 vacuum 操作讓數據庫年齡下降了 9275406,也就是接近一千萬,執行輪次為 91 postgres=# SELECT id, exec_id AS "輪次", start_time AS "開始時間", end_time AS "結束時間", db_name AS "數據庫", event_type AS "操作類型", CASE WHEN others->>'cluster_age_before' IS NOT NULL AND others->>'cluster_age_after' IS NOT NULL THEN (others->>'cluster_age_before')::BIGINT - (others->>'cluster_age_after')::BIGINT ELSE NULL END AS "年齡下降" FROM polar_advisor.db_level_advisor_log ORDER BY "年齡下降" DESC NULLS LAST; id | 輪次 | 開始時間 | 結束時間 | 數據庫 | 操作類型 | 年齡下降 --------+------+-------------------------------+-------------------------------+---------------+----------+---------- 259 | 91 | 2024-02-22 00:00:18.847978+08 | 2024-02-22 00:14:18.785085+08 | aaaaaaaaaaaaa | VACUUM | 9275406 256 | 90 | 2024-02-21 00:00:39.607552+08 | 2024-02-21 00:00:42.054733+08 | bbbbbbbbbbbbb | VACUUM | 7905122 262 | 92 | 2024-02-23 00:00:05.999423+08 | 2024-02-23 00:00:08.411993+08 | postgres | VACUUM | 578308 -- 根據執行輪次 91 獲取詳細的 vacuum 記錄,可以看到主要是一些 pg_catalog 系統表的 vacuum 操作使得數據庫年齡下降 postgres=# SELECT id, start_time AS "開始時間", end_time AS "結束時間", db_name AS "數據庫", schema_name AS "模式", relation_name AS "表名", event_type AS "操作類型", age_decreased AS "年齡下降" FROM polar_advisor.advisor_log WHERE exec_id = 91 ORDER BY "年齡下降" DESC NULLS LAST; id | 開始時間 | 結束時間 | 數據庫 | 模式 | 表名 | 操作類型 | 年齡下降 ----------+-------------------------------+-------------------------------+-------+------------+--------------------+---------+---------- 43933 | 2024-02-22 00:00:19.070493+08 | 2024-02-22 00:00:19.090822+08 | abc | pg_catalog | pg_subscription | VACUUM | 27787409 43935 | 2024-02-22 00:00:19.116292+08 | 2024-02-22 00:00:19.13875+08 | abc | pg_catalog | pg_database | VACUUM | 27787408 43936 | 2024-02-22 00:00:19.140992+08 | 2024-02-22 00:00:19.171938+08 | abc | pg_catalog | pg_db_role_setting | VACUUM | 27787408 -- 當前實例年齡為兩千多萬,距離閾值 21 億還有很遠,非常安全 postgres=> SELECT MAX(pg_catalog.age(datfrozenxid)) AS "實例年齡" FROM pg_catalog.pg_database; 實例年齡 ---------- 20874380 (1 row)
優化效果示例
以下展示部分集群在配置可維護窗口以后的資源使用量和數據庫年齡的優化效果。
鎖表阻塞讀寫、計劃緩存失效等問題的優化效果不太好通過圖表的方式展示,因此不作展示。
并非所有集群都能取得示例中這么好的優化效果,實際效果與具體業務場景有關。導致提升效果不明顯的原因有很多,例如有些集群全天業務都很繁忙且沒有明顯的業務低峰期,有些集群的業務低峰期則配置了一些任務來執行數據分析、數據導入、物化視圖刷新等操作,沒有太多閑置資源可供垃圾回收操作使用。
內存使用量優化效果
如下圖所示,在配置可維護窗口之后,集群的自動清理進程內存使用量峰值從2.06 GB下降到37 MB,降幅達到98%。
所有進程的總內存使用量峰值也隨之從10 GB下降到8 GB,降幅20%。
I/O 使用量優化效果
如下圖所示,在配置可維護窗口之后,集群的自動清理進程PFS IOPS峰值明顯降低,降幅約50%。
所有進程的總PFS IOPS峰值也從35000下降到21000左右,降幅約40%。
自動清理進程PFS I/O吞吐峰值從225 MB下降到173 MB,下降了23%,同時峰的寬度和數量也明顯下降,吞吐平均值從65.5 MB降到42.5 MB,下降了35%。
CPU 使用量優化效果
如下圖所示,在配置可維護窗口之后,集群的自動清理進程的CPU使用率逐漸降低,峰值降幅約50%。
自動清理進程數優化效果
如下圖所示,在配置可維護窗口之后,集群的自動清理進程數量從2降低到1。
數據庫年齡優化效果
如下圖所示,集群在配置可維護窗口之后的兩天內回收了超過10億個事務ID,數據庫年齡從10億多下降到低于1億,事務ID回卷風險大大降低。