feat: 添加强制热表空间的 SQL 构建功能,优化分区创建逻辑
This commit is contained in:
77
bls-onoffline-backend/dist/index.js
vendored
77
bls-onoffline-backend/dist/index.js
vendored
@@ -181,6 +181,69 @@ class PartitionManager {
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return normalizedDate.getTime() >= today.getTime();
|
||||
}
|
||||
escapeSqlLiteral(value) {
|
||||
return String(value).replace(/'/g, "''");
|
||||
}
|
||||
buildForceHotTablespaceSql(schema, partitionName, hotTablespace = "ts_hot") {
|
||||
const schemaLiteral = this.escapeSqlLiteral(schema);
|
||||
const partitionLiteral = this.escapeSqlLiteral(partitionName);
|
||||
const hotLiteral = this.escapeSqlLiteral(hotTablespace);
|
||||
return `
|
||||
DO $$
|
||||
DECLARE
|
||||
v_schema text := '${schemaLiteral}';
|
||||
v_partition text := '${partitionLiteral}';
|
||||
v_hot text := '${hotLiteral}';
|
||||
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=v_schema AND c.relname=v_partition AND c.relkind='r';
|
||||
|
||||
IF v_part_oid IS NULL THEN
|
||||
RAISE EXCEPTION 'partition %.% not found', v_schema, v_partition;
|
||||
END IF;
|
||||
|
||||
EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_schema, v_partition, v_hot);
|
||||
|
||||
FOR r IN
|
||||
SELECT idxn.nspname AS index_schema, i.relname AS index_name
|
||||
FROM pg_index x
|
||||
JOIN pg_class t ON t.oid=x.indrelid
|
||||
JOIN pg_namespace nt ON nt.oid=t.relnamespace
|
||||
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 nt.nspname=v_schema
|
||||
AND t.relname=v_partition
|
||||
AND COALESCE(ts.spcname,'pg_default')<>v_hot
|
||||
LOOP
|
||||
EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, v_hot);
|
||||
END LOOP;
|
||||
|
||||
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, v_hot);
|
||||
|
||||
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')<>v_hot
|
||||
LOOP
|
||||
EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, v_hot);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
EXECUTE format('ANALYZE %I.%I', v_schema, v_partition);
|
||||
END $$;
|
||||
`;
|
||||
}
|
||||
/**
|
||||
* Calculate the start and end timestamps (milliseconds) for a given date.
|
||||
* @param {Date} date - The date to calculate for.
|
||||
@@ -225,13 +288,18 @@ class PartitionManager {
|
||||
if (!checkRes.rows[0].exists) {
|
||||
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||
console.log(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||
const tablespaceClause = this.isCurrentOrFutureDate(targetDate) ? " TABLESPACE ts_hot" : "";
|
||||
const shouldUseHotTablespace = this.isCurrentOrFutureDate(targetDate);
|
||||
const tablespaceClause = shouldUseHotTablespace ? " TABLESPACE ts_hot" : "";
|
||||
const partitionTableName = `${table}_${partitionSuffix}`;
|
||||
const createSql = `
|
||||
CREATE TABLE IF NOT EXISTS ${partitionName}
|
||||
PARTITION OF ${schema}.${table}
|
||||
FOR VALUES FROM (${startMs}) TO (${endMs})${tablespaceClause};
|
||||
`;
|
||||
await client.query(createSql);
|
||||
if (shouldUseHotTablespace) {
|
||||
await client.query(this.buildForceHotTablespaceSql(schema, partitionTableName));
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("Partition check completed.");
|
||||
@@ -271,12 +339,17 @@ class PartitionManager {
|
||||
const checkRes = await client.query(`SELECT to_regclass($1) as exists;`, [partitionName]);
|
||||
if (!checkRes.rows[0].exists) {
|
||||
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||
const tablespaceClause = this.isCurrentOrFutureDate(targetDate) ? " TABLESPACE ts_hot" : "";
|
||||
const shouldUseHotTablespace = this.isCurrentOrFutureDate(targetDate);
|
||||
const tablespaceClause = shouldUseHotTablespace ? " TABLESPACE ts_hot" : "";
|
||||
const partitionTableName = `${table}_${partitionSuffix}`;
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS ${partitionName}
|
||||
PARTITION OF ${schema}.${table}
|
||||
FOR VALUES FROM (${startMs}) TO (${endMs})${tablespaceClause};
|
||||
`);
|
||||
if (shouldUseHotTablespace) {
|
||||
await client.query(this.buildForceHotTablespaceSql(schema, partitionTableName));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -13,6 +13,72 @@ class PartitionManager {
|
||||
return normalizedDate.getTime() >= today.getTime();
|
||||
}
|
||||
|
||||
escapeSqlLiteral(value) {
|
||||
return String(value).replace(/'/g, "''");
|
||||
}
|
||||
|
||||
buildForceHotTablespaceSql(schema, partitionName, hotTablespace = 'ts_hot') {
|
||||
const schemaLiteral = this.escapeSqlLiteral(schema);
|
||||
const partitionLiteral = this.escapeSqlLiteral(partitionName);
|
||||
const hotLiteral = this.escapeSqlLiteral(hotTablespace);
|
||||
|
||||
return `
|
||||
DO $$
|
||||
DECLARE
|
||||
v_schema text := '${schemaLiteral}';
|
||||
v_partition text := '${partitionLiteral}';
|
||||
v_hot text := '${hotLiteral}';
|
||||
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=v_schema AND c.relname=v_partition AND c.relkind='r';
|
||||
|
||||
IF v_part_oid IS NULL THEN
|
||||
RAISE EXCEPTION 'partition %.% not found', v_schema, v_partition;
|
||||
END IF;
|
||||
|
||||
EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_schema, v_partition, v_hot);
|
||||
|
||||
FOR r IN
|
||||
SELECT idxn.nspname AS index_schema, i.relname AS index_name
|
||||
FROM pg_index x
|
||||
JOIN pg_class t ON t.oid=x.indrelid
|
||||
JOIN pg_namespace nt ON nt.oid=t.relnamespace
|
||||
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 nt.nspname=v_schema
|
||||
AND t.relname=v_partition
|
||||
AND COALESCE(ts.spcname,'pg_default')<>v_hot
|
||||
LOOP
|
||||
EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, v_hot);
|
||||
END LOOP;
|
||||
|
||||
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, v_hot);
|
||||
|
||||
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')<>v_hot
|
||||
LOOP
|
||||
EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, v_hot);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
EXECUTE format('ANALYZE %I.%I', v_schema, v_partition);
|
||||
END $$;
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the start and end timestamps (milliseconds) for a given date.
|
||||
* @param {Date} date - The date to calculate for.
|
||||
@@ -66,13 +132,18 @@ class PartitionManager {
|
||||
if (!checkRes.rows[0].exists) {
|
||||
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||
console.log(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||
const tablespaceClause = this.isCurrentOrFutureDate(targetDate) ? ' TABLESPACE ts_hot' : '';
|
||||
const shouldUseHotTablespace = this.isCurrentOrFutureDate(targetDate);
|
||||
const tablespaceClause = shouldUseHotTablespace ? ' TABLESPACE ts_hot' : '';
|
||||
const partitionTableName = `${table}_${partitionSuffix}`;
|
||||
const createSql = `
|
||||
CREATE TABLE IF NOT EXISTS ${partitionName}
|
||||
PARTITION OF ${schema}.${table}
|
||||
FOR VALUES FROM (${startMs}) TO (${endMs})${tablespaceClause};
|
||||
`;
|
||||
await client.query(createSql);
|
||||
if (shouldUseHotTablespace) {
|
||||
await client.query(this.buildForceHotTablespaceSql(schema, partitionTableName));
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info('Partition check completed.');
|
||||
@@ -119,12 +190,17 @@ class PartitionManager {
|
||||
const checkRes = await client.query(`SELECT to_regclass($1) as exists;`, [partitionName]);
|
||||
if (!checkRes.rows[0].exists) {
|
||||
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||
const tablespaceClause = this.isCurrentOrFutureDate(targetDate) ? ' TABLESPACE ts_hot' : '';
|
||||
const shouldUseHotTablespace = this.isCurrentOrFutureDate(targetDate);
|
||||
const tablespaceClause = shouldUseHotTablespace ? ' TABLESPACE ts_hot' : '';
|
||||
const partitionTableName = `${table}_${partitionSuffix}`;
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS ${partitionName}
|
||||
PARTITION OF ${schema}.${table}
|
||||
FOR VALUES FROM (${startMs}) TO (${endMs})${tablespaceClause};
|
||||
`);
|
||||
if (shouldUseHotTablespace) {
|
||||
await client.query(this.buildForceHotTablespaceSql(schema, partitionTableName));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user