# AwoooP Partition & Retention Runbook **建立**:2026-05-04(台北) **ADR 依據**:ADR-114(channel_event_dedupe)、ADR-119(run_state) **關聯**:Phase 1 Task 1.4、Phase 4/7(run_state / mcp_gateway_audit 建立時套用) --- ## 概覽 | 表 | Partition 策略 | Retention | 建立 Phase | |---|---|---|---| | `awooop_channel_event_dedupe` | RANGE by `created_at`(日)| 7 天 | Phase 1 ✅ | | `awooop_run_state` | RANGE by `created_at`(月)| 90 天 hot + 1 年 warm | Phase 4 | | `awooop_mcp_gateway_audit` | RANGE by `created_at`(月)| 90 天 hot + 1 年 warm | Phase 5 | | `awooop_agent_audit_log` | RANGE by `created_at`(月)| 90 天 hot + 1 年 warm | Phase 7 | --- ## 1. awooop_channel_event_dedupe(Phase 1,已完成) ### pg_partman 維護(建議) ```sql -- 確認 pg_partman 已安裝 SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_partman'; -- 初始化(若 Phase 1 migration 未自動完成) SELECT partman.create_parent( p_parent_table := 'public.awooop_channel_event_dedupe', p_control := 'created_at', p_type := 'native', p_interval := '1 day', p_premake := 4 ); UPDATE partman.part_config SET retention = '7 days', retention_keep_table = false WHERE parent_table = 'public.awooop_channel_event_dedupe'; ``` ### pg_partman 定期維護(CronJob,每天 00:00) ```bash # K8s CronJob 或 pg_cron 執行 psql $DATABASE_URL -c "SELECT partman.run_maintenance('public.awooop_channel_event_dedupe');" ``` ### 手動維護(無 pg_partman) ```sql -- 查看現有 partition SELECT inhrelid::regclass AS partition_name, pg_get_expr(c.relpartbound, c.oid) AS bounds FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE inhparent = 'awooop_channel_event_dedupe'::regclass ORDER BY bounds; -- 建立下一週 partition(每天執行一次) DO $$ DECLARE d DATE := CURRENT_DATE + 1; BEGIN EXECUTE format( 'CREATE TABLE IF NOT EXISTS awooop_channel_event_dedupe_%s PARTITION OF awooop_channel_event_dedupe FOR VALUES FROM (%L) TO (%L)', to_char(d, 'YYYYMMDD'), d::TIMESTAMPTZ, (d + INTERVAL '1 day')::TIMESTAMPTZ ); END $$; -- 刪除 7 天前的 partition(毫秒級,遠優於 DELETE) DO $$ DECLARE old_date DATE := CURRENT_DATE - 8; partition_name TEXT := 'awooop_channel_event_dedupe_' || to_char(old_date, 'YYYYMMDD'); BEGIN EXECUTE 'DROP TABLE IF EXISTS ' || partition_name; RAISE NOTICE 'Dropped partition: %', partition_name; END $$; ``` --- ## 2. awooop_run_state(Phase 4 建立時套用) > **尚未建立**。Phase 4 建立 `awooop_run_state` 表時使用以下模板。 ### 月份 Partition 建立模板 ```sql CREATE TABLE awooop_run_state ( run_id UUID NOT NULL DEFAULT gen_random_uuid(), project_id VARCHAR(64) NOT NULL, -- ... 其他欄位 ... created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (run_id, created_at) ) PARTITION BY RANGE (created_at); -- pg_partman 初始化 SELECT partman.create_parent( p_parent_table := 'public.awooop_run_state', p_control := 'created_at', p_type := 'native', p_interval := '1 month', p_premake := 3 ); UPDATE partman.part_config SET retention = '90 days', -- hot tier: 90 天自動 DROP retention_keep_table = true, -- warm tier: 保留為 detached table retention_schema = 'warm_archive'-- warm partition 移到此 schema WHERE parent_table = 'public.awooop_run_state'; ``` ### Retention 策略 | Tier | 資料年齡 | 存放位置 | 清理方式 | |------|---------|---------|---------| | Hot | 0~90 天 | `public.awooop_run_state_*` | pg_partman 自動管理 | | Warm | 91 天~1 年 | `warm_archive.awooop_run_state_*` | 保留(detach,不 DROP)| | Cold | > 1 年 | S3 / GCS export(可選)| 手動 COPY TO 後 DROP | --- ## 3. awooop_mcp_gateway_audit + awooop_agent_audit_log(Phase 5/7) 與 `awooop_run_state` 相同策略(月份 partition,90 天 hot + 1 年 warm)。 建立時直接套用同一 pg_partman 模板,替換表名即可。 --- ## 健康檢查 ```sql -- 確認各 partition 的資料分佈 SELECT child.relname AS partition, pg_size_pretty(pg_total_relation_size(child.oid)) AS size, (SELECT count(*) FROM ONLY pg_class WHERE oid = child.oid) AS approx_rows FROM pg_inherits JOIN pg_class parent ON parent.oid = pg_inherits.inhparent JOIN pg_class child ON child.oid = pg_inherits.inhrelid WHERE parent.relname = 'awooop_channel_event_dedupe' ORDER BY child.relname; -- 確認最舊 partition(應 <= 7 天前) SELECT min(created_at), max(created_at) FROM awooop_channel_event_dedupe; ``` --- ## 告警規則(建議加入 Prometheus) ```yaml # partition 數量異常(應維持 7 ± 2 個) - alert: AwoooPDedupPartitionCountAbnormal expr: | (SELECT count(*) FROM pg_inherits WHERE inhparent = 'awooop_channel_event_dedupe'::regclass) NOT BETWEEN 5 AND 10 labels: severity: warning # pg_partman maintenance 未執行(超過 25 小時) - alert: AwoooPPartmanMaintenanceStale expr: time() - awooop_partman_last_run_timestamp > 90000 labels: severity: warning ```