From 70e717ea5d7254dbaa88add688b3e887ae3b1bb9 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Tue, 3 Mar 2026 21:00:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20relocate=5Fpartiti?= =?UTF-8?q?on=5Fto=5Ftablespace=20=E5=87=BD=E6=95=B0=E4=BB=A5=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E5=88=86=E5=8C=BA=E5=8F=8A=E5=85=B6=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E5=88=86=E9=85=8D=E5=88=B0=E6=8C=87=E5=AE=9A=E8=A1=A8=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=EF=BC=8C=E5=B9=B6=E5=9C=A8=E5=88=9B=E5=BB=BA=E6=97=A5?= =?UTF-8?q?=E5=88=86=E5=8C=BA=E6=97=B6=E8=B0=83=E7=94=A8=E8=AF=A5=E5=87=BD?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/db/020_partitioning_auto_daily.sql | 66 ++++++++++++++++++++++ src/db/databaseManager.js | 66 ++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/scripts/db/020_partitioning_auto_daily.sql b/scripts/db/020_partitioning_auto_daily.sql index 5f321a3..f849aca 100644 --- a/scripts/db/020_partitioning_auto_daily.sql +++ b/scripts/db/020_partitioning_auto_daily.sql @@ -33,6 +33,70 @@ AS $$ SELECT format('heartbeat_events_%s', to_char(p_day, 'YYYYMMDD')); $$; +-- 强制将分区及其索引分配到指定表空间(幂等) +CREATE OR REPLACE FUNCTION heartbeat.relocate_partition_to_tablespace( + p_schema text, + p_partition text, + p_tablespace text DEFAULT 'ts_hot' +) +RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE + v_part_oid oid; + v_toast_oid oid; + r record; +BEGIN + SELECT c.oid INTO v_part_oid + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = p_schema + AND c.relname = p_partition + AND c.relkind = 'r'; + + IF v_part_oid IS NULL THEN + RAISE EXCEPTION 'partition %.% not found', p_schema, p_partition; + END IF; + + -- 1) 分区表对象 -> 指定 tablespace + EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', p_schema, p_partition, p_tablespace); + + -- 2) 分区全部索引 -> 指定 tablespace + FOR r IN + SELECT idxn.nspname AS index_schema, i.relname AS index_name + FROM pg_index x + JOIN pg_class i ON i.oid = x.indexrelid + JOIN pg_namespace idxn ON idxn.oid = i.relnamespace + LEFT JOIN pg_tablespace ts ON ts.oid = i.reltablespace + WHERE x.indrelid = v_part_oid + AND COALESCE(ts.spcname, 'pg_default') <> p_tablespace + LOOP + EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, p_tablespace); + END LOOP; + + -- 3) TOAST 表 + TOAST 索引 -> 指定 tablespace(若存在) + SELECT reltoastrelid INTO v_toast_oid FROM pg_class WHERE oid = v_part_oid; + IF v_toast_oid IS NOT NULL AND v_toast_oid <> 0 THEN + EXECUTE format('ALTER TABLE %s SET TABLESPACE %I', v_toast_oid::regclass, p_tablespace); + + FOR r IN + SELECT idxn.nspname AS index_schema, i.relname AS index_name + FROM pg_index x + JOIN pg_class i ON i.oid = x.indexrelid + JOIN pg_namespace idxn ON idxn.oid = i.relnamespace + LEFT JOIN pg_tablespace ts ON ts.oid = i.reltablespace + WHERE x.indrelid = v_toast_oid + AND COALESCE(ts.spcname, 'pg_default') <> p_tablespace + LOOP + EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, p_tablespace); + END LOOP; + END IF; + + -- 4) 统计信息 + EXECUTE format('ANALYZE %I.%I', p_schema, p_partition); +END; +$$; + -- 创建单日分区(幂等);父表索引自动继承到子表,无需手动建索引 CREATE OR REPLACE FUNCTION heartbeat.create_daily_partition(p_day date) RETURNS void @@ -51,6 +115,8 @@ BEGIN 'CREATE TABLE IF NOT EXISTS heartbeat.%I PARTITION OF heartbeat.heartbeat_events FOR VALUES FROM (%s) TO (%s) TABLESPACE ts_hot', part_name, start_ms, end_ms ); + + PERFORM heartbeat.relocate_partition_to_tablespace('heartbeat', part_name, 'ts_hot'); END; $$; diff --git a/src/db/databaseManager.js b/src/db/databaseManager.js index c0bd8d5..98db4a5 100644 --- a/src/db/databaseManager.js +++ b/src/db/databaseManager.js @@ -198,6 +198,70 @@ class DatabaseManager { SELECT format('heartbeat_events_%s', to_char(p_day, 'YYYYMMDD')); $$; + -- 强制将分区及其索引分配到指定表空间(幂等) + CREATE OR REPLACE FUNCTION heartbeat.relocate_partition_to_tablespace( + p_schema text, + p_partition text, + p_tablespace text DEFAULT 'ts_hot' + ) + RETURNS void + LANGUAGE plpgsql + AS $$ + DECLARE + v_part_oid oid; + v_toast_oid oid; + r record; + BEGIN + SELECT c.oid INTO v_part_oid + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = p_schema + AND c.relname = p_partition + AND c.relkind = 'r'; + + IF v_part_oid IS NULL THEN + RAISE EXCEPTION 'partition %.% not found', p_schema, p_partition; + END IF; + + -- 1) 分区表对象 -> 指定 tablespace + EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', p_schema, p_partition, p_tablespace); + + -- 2) 分区全部索引 -> 指定 tablespace + FOR r IN + SELECT idxn.nspname AS index_schema, i.relname AS index_name + FROM pg_index x + JOIN pg_class i ON i.oid = x.indexrelid + JOIN pg_namespace idxn ON idxn.oid = i.relnamespace + LEFT JOIN pg_tablespace ts ON ts.oid = i.reltablespace + WHERE x.indrelid = v_part_oid + AND COALESCE(ts.spcname, 'pg_default') <> p_tablespace + LOOP + EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, p_tablespace); + END LOOP; + + -- 3) TOAST 表 + TOAST 索引 -> 指定 tablespace(若存在) + SELECT reltoastrelid INTO v_toast_oid FROM pg_class WHERE oid = v_part_oid; + IF v_toast_oid IS NOT NULL AND v_toast_oid <> 0 THEN + EXECUTE format('ALTER TABLE %s SET TABLESPACE %I', v_toast_oid::regclass, p_tablespace); + + FOR r IN + SELECT idxn.nspname AS index_schema, i.relname AS index_name + FROM pg_index x + JOIN pg_class i ON i.oid = x.indexrelid + JOIN pg_namespace idxn ON idxn.oid = i.relnamespace + LEFT JOIN pg_tablespace ts ON ts.oid = i.reltablespace + WHERE x.indrelid = v_toast_oid + AND COALESCE(ts.spcname, 'pg_default') <> p_tablespace + LOOP + EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, p_tablespace); + END LOOP; + END IF; + + -- 4) 统计信息 + EXECUTE format('ANALYZE %I.%I', p_schema, p_partition); + END; + $$; + -- 创建单日分区(幂等);父表索引自动继承到子表,无需手动建索引 CREATE OR REPLACE FUNCTION heartbeat.create_daily_partition(p_day date) RETURNS void @@ -216,6 +280,8 @@ class DatabaseManager { 'CREATE TABLE IF NOT EXISTS heartbeat.%I PARTITION OF heartbeat.heartbeat_events FOR VALUES FROM (%s) TO (%s) TABLESPACE ts_hot', part_name, start_ms, end_ms ); + + PERFORM heartbeat.relocate_partition_to_tablespace('heartbeat', part_name, 'ts_hot'); END; $$;