feat: 实现父表索引策略,确保仅在父表创建索引,避免子表重复索引维护
This commit is contained in:
@@ -38,7 +38,9 @@ ALTER TABLE rcu_action.rcu_action_events
|
|||||||
ALTER TABLE rcu_action.rcu_action_events
|
ALTER TABLE rcu_action.rcu_action_events
|
||||||
ADD COLUMN IF NOT EXISTS loop_name VARCHAR(255);
|
ADD COLUMN IF NOT EXISTS loop_name VARCHAR(255);
|
||||||
|
|
||||||
-- Indexes for performance
|
-- Indexes for performance (ONLY on parent partitioned table)
|
||||||
|
-- PostgreSQL will create/attach corresponding child-partition indexes automatically.
|
||||||
|
-- Do not create duplicated indexes on partition child tables.
|
||||||
CREATE INDEX IF NOT EXISTS idx_rcu_action_hotel_id ON rcu_action.rcu_action_events (hotel_id);
|
CREATE INDEX IF NOT EXISTS idx_rcu_action_hotel_id ON rcu_action.rcu_action_events (hotel_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_rcu_action_room_id ON rcu_action.rcu_action_events (room_id);
|
CREATE INDEX IF NOT EXISTS idx_rcu_action_room_id ON rcu_action.rcu_action_events (room_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_rcu_action_device_id ON rcu_action.rcu_action_events (device_id);
|
CREATE INDEX IF NOT EXISTS idx_rcu_action_device_id ON rcu_action.rcu_action_events (device_id);
|
||||||
|
|||||||
@@ -1,7 +1,24 @@
|
|||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
import dbManager from './databaseManager.js';
|
import dbManager from './databaseManager.js';
|
||||||
|
|
||||||
|
const PARENT_TABLE = 'rcu_action.rcu_action_events';
|
||||||
|
const PARENT_INDEX_STATEMENTS = [
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_hotel_id ON rcu_action.rcu_action_events (hotel_id);',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_room_id ON rcu_action.rcu_action_events (room_id);',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_device_id ON rcu_action.rcu_action_events (device_id);',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_direction ON rcu_action.rcu_action_events (direction);',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_cmd_word ON rcu_action.rcu_action_events (cmd_word);',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_action_type ON rcu_action.rcu_action_events (action_type);',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_query_main ON rcu_action.rcu_action_events (hotel_id, room_id, ts_ms DESC);'
|
||||||
|
];
|
||||||
|
|
||||||
class PartitionManager {
|
class PartitionManager {
|
||||||
|
async ensureParentIndexes(client) {
|
||||||
|
for (const sql of PARENT_INDEX_STATEMENTS) {
|
||||||
|
await client.query(sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the start and end timestamps (milliseconds) for a given date.
|
* Calculate the start and end timestamps (milliseconds) for a given date.
|
||||||
* @param {Date} date - The date to calculate for.
|
* @param {Date} date - The date to calculate for.
|
||||||
@@ -33,6 +50,7 @@ class PartitionManager {
|
|||||||
const client = await dbManager.pool.connect();
|
const client = await dbManager.pool.connect();
|
||||||
try {
|
try {
|
||||||
logger.info(`Starting partition check for the next ${daysAhead} days...`);
|
logger.info(`Starting partition check for the next ${daysAhead} days...`);
|
||||||
|
await this.ensureParentIndexes(client);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
for (let i = 0; i < daysAhead; i++) {
|
for (let i = 0; i < daysAhead; i++) {
|
||||||
@@ -52,7 +70,7 @@ class PartitionManager {
|
|||||||
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
||||||
const createSql = `
|
const createSql = `
|
||||||
CREATE TABLE IF NOT EXISTS ${partitionName}
|
CREATE TABLE IF NOT EXISTS ${partitionName}
|
||||||
PARTITION OF rcu_action.rcu_action_events
|
PARTITION OF ${PARENT_TABLE}
|
||||||
FOR VALUES FROM (${startMs}) TO (${endMs});
|
FOR VALUES FROM (${startMs}) TO (${endMs});
|
||||||
`;
|
`;
|
||||||
await client.query(createSql);
|
await client.query(createSql);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Parent-only Index Strategy for Partition Creation
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
当前 `rcu_action.rcu_action_events` 使用按天 RANGE 分区。
|
||||||
|
本次调整目标是明确并固化索引策略:
|
||||||
|
- 仅在父表创建索引
|
||||||
|
- 分区子表不重复显式创建索引
|
||||||
|
|
||||||
|
这样可以避免子表重复索引带来的维护成本和潜在冲突,并保持分区创建逻辑简洁。
|
||||||
|
|
||||||
|
## 2. 策略定义
|
||||||
|
1. 父表 `rcu_action.rcu_action_events` 作为索引定义唯一来源。
|
||||||
|
2. 分区创建只执行 `CREATE TABLE ... PARTITION OF ...`。
|
||||||
|
3. 禁止在分区创建流程中追加 `CREATE INDEX` 到子分区。
|
||||||
|
4. 索引初始化保持 `IF NOT EXISTS` 幂等语义。
|
||||||
|
|
||||||
|
## 3. 实施范围
|
||||||
|
- `src/db/partitionManager.js`
|
||||||
|
- 在分区检查流程中显式确保父表索引存在(幂等)。
|
||||||
|
- 创建子分区时仅建分区,不做子表索引创建。
|
||||||
|
- `scripts/init_db.sql`
|
||||||
|
- 明确注释:索引仅建父表,子分区不重复建索引。
|
||||||
|
|
||||||
|
## 4. 验收标准
|
||||||
|
- 分区创建 SQL 不包含任何对子分区的 `CREATE INDEX`。
|
||||||
|
- 父表索引可重复执行而不报错(`IF NOT EXISTS`)。
|
||||||
|
- 运行初始化和分区预创建后,日志中仅出现父表索引保障与分区建表行为。
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Parent-only index definition for partitioned table
|
||||||
|
`rcu_action.rcu_action_events` MUST define and maintain query indexes only on the parent partitioned table, and partition child tables MUST NOT run explicit `CREATE INDEX` statements during partition creation.
|
||||||
|
|
||||||
|
#### Scenario: Initialize parent indexes before partition creation
|
||||||
|
- **WHEN** the service runs database initialization or scheduled partition pre-creation
|
||||||
|
- **THEN** index creation statements are executed only against `rcu_action.rcu_action_events`
|
||||||
|
- **AND** statements use `IF NOT EXISTS` to remain idempotent
|
||||||
|
|
||||||
|
#### Scenario: Create a new daily child partition
|
||||||
|
- **WHEN** a missing partition `rcu_action.rcu_action_events_YYYYMMDD` is created
|
||||||
|
- **THEN** the SQL only performs `CREATE TABLE ... PARTITION OF ... FOR VALUES ...`
|
||||||
|
- **AND** no child-specific `CREATE INDEX` statement is executed
|
||||||
|
|
||||||
|
#### Scenario: Prevent duplicate child index maintenance
|
||||||
|
- **WHEN** partition creation runs repeatedly across deployments
|
||||||
|
- **THEN** no duplicate child index maintenance logic is introduced in application code
|
||||||
|
- **AND** parent-table index strategy remains the single source of truth
|
||||||
Reference in New Issue
Block a user