Files
gochat/backend/migrations/000048_fix_reporting_events_rollups_schema.up.sql
T
Rogeeandrogee 6d6d80dd86 HH-441: harden durable storage and recovery (#92)
* HH-441: harden durable storage and recovery

* HH-441: clear recovery review blockers

* HH-441: enforce offsite backup failure domain

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-22 02:16:02 +08:00

108 lines
4.4 KiB
PL/PgSQL

-- Convert the legacy rollup schema without dropping its data. The explicit
-- transaction makes lock/statement timeout failures leave the old table intact.
BEGIN;
LOCK TABLE reporting_events_rollups IN ACCESS EXCLUSIVE MODE;
DO $$
DECLARE
invalid_periods BIGINT;
invalid_dimensions BIGINT;
invalid_counts BIGINT;
oversized_dimensions BIGINT;
collision_groups BIGINT;
BEGIN
SELECT count(*) INTO invalid_periods
FROM reporting_events_rollups
WHERE NOT pg_input_is_valid(period, 'date');
IF invalid_periods > 0 THEN
RAISE EXCEPTION 'migration 000048: % rollup period value(s) cannot convert to date', invalid_periods
USING HINT = 'Repair or remove the reported legacy rows explicitly, then retry the migration.';
END IF;
SELECT count(*) INTO invalid_dimensions
FROM reporting_events_rollups
WHERE NOT pg_input_is_valid(dimension_value, 'bigint');
IF invalid_dimensions > 0 THEN
RAISE EXCEPTION 'migration 000048: % rollup dimension_value(s) cannot convert to bigint', invalid_dimensions
USING HINT = 'Map non-numeric legacy dimensions explicitly, then retry the migration.';
END IF;
SELECT count(*) INTO invalid_counts
FROM reporting_events_rollups
WHERE metric_name LIKE '%count%' AND NOT pg_input_is_valid(value::text, 'bigint');
IF invalid_counts > 0 THEN
RAISE EXCEPTION 'migration 000048: % count value(s) cannot convert losslessly to bigint', invalid_counts
USING HINT = 'Repair fractional, non-finite, or out-of-range count values, then retry the migration.';
END IF;
SELECT count(*) INTO oversized_dimensions
FROM reporting_events_rollups
WHERE length(dimension) > 20;
IF oversized_dimensions > 0 THEN
RAISE EXCEPTION 'migration 000048: % dimension value(s) exceed the new 20-character limit', oversized_dimensions
USING HINT = 'Shorten or explicitly map oversized dimensions, then retry the migration.';
END IF;
SELECT count(*) INTO collision_groups
FROM (
SELECT 1
FROM reporting_events_rollups
GROUP BY account_id, period::date, dimension, dimension_value::BIGINT, metric_name
HAVING count(*) > 1
) collisions;
IF collision_groups > 0 THEN
RAISE EXCEPTION 'migration 000048: % unique-key collision group(s) appear after type conversion', collision_groups
USING HINT = 'Merge or choose one legacy row in each collision group explicitly, then retry the migration.';
END IF;
END $$;
CREATE TABLE reporting_events_rollups_v2 (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL,
date DATE NOT NULL,
dimension_type VARCHAR(20) NOT NULL,
dimension_id BIGINT NOT NULL,
metric VARCHAR(50) NOT NULL,
count BIGINT NOT NULL DEFAULT 0,
sum_value DOUBLE PRECISION NOT NULL DEFAULT 0,
sum_value_business_hours DOUBLE PRECISION NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
UNIQUE(account_id, date, dimension_type, dimension_id, metric)
);
INSERT INTO reporting_events_rollups_v2 (
id, account_id, date, dimension_type, dimension_id, metric, count,
sum_value, sum_value_business_hours, created_at, updated_at, deleted_at
)
SELECT
id,
account_id,
period::date,
dimension,
dimension_value::BIGINT,
metric_name,
CASE WHEN metric_name LIKE '%count%' THEN value::BIGINT ELSE 1 END,
value,
COALESCE(value_in_business_hours, 0),
created_at,
updated_at,
deleted_at
FROM reporting_events_rollups;
SELECT setval(
pg_get_serial_sequence('reporting_events_rollups_v2', 'id'),
COALESCE((SELECT MAX(id) FROM reporting_events_rollups_v2), 1),
EXISTS (SELECT 1 FROM reporting_events_rollups_v2)
);
DROP TABLE reporting_events_rollups;
ALTER TABLE reporting_events_rollups_v2 RENAME TO reporting_events_rollups;
ALTER INDEX reporting_events_rollups_v2_pkey RENAME TO reporting_events_rollups_pkey;
CREATE INDEX idx_reporting_events_rollups_deleted_at ON reporting_events_rollups(deleted_at);
CREATE INDEX idx_reporting_events_rollups_account_id ON reporting_events_rollups(account_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_reporting_events_rollups_date ON reporting_events_rollups(date) WHERE deleted_at IS NULL;
CREATE INDEX idx_reporting_events_rollups_dimension ON reporting_events_rollups(dimension_type, dimension_id) WHERE deleted_at IS NULL;
COMMIT;