006_timescale.up.sql 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. -- 006_timescale.up.sql
  2. -- M7: Convert deliveries to a TimescaleDB hypertable with 7d
  3. -- retention. Hot-path writes still go through the same
  4. -- deliveries.*<channel>.<company> NATS subjects; the
  5. -- deliverers INSERT rows just as before. The data-layer
  6. -- change is invisible to the services.
  7. --
  8. -- SPEC §23 calls for both alerts and deliveries as hypertables,
  9. -- but alerts in our codebase never live in Postgres — they
  10. -- flow through NATS JetStream with a 24h stream retention
  11. -- (see cmd/ingestd + cmd/routerd). The "alerts archive" path
  12. -- would be a separate M7.5 if we ever want to long-term-store
  13. -- alert bodies in ClickHouse. For M7, only deliveries is in
  14. -- scope.
  15. --
  16. -- What this migration does:
  17. -- 1. Convert `deliveries` to a hypertable on `created_at`
  18. -- (1d chunks). Timescale will copy data behind the
  19. -- scenes if the table has rows; the existing
  20. -- `idx_deliveries_company_alert` and
  21. -- `idx_deliveries_status` indexes are kept.
  22. -- 2. Add a 7d retention policy. Timescale will drop
  23. -- chunks older than 7 days automatically.
  24. -- 3. Note: PRIMARY KEY (id) is preserved; Timescale allows
  25. -- a non-time column as PK on a hypertable. The
  26. -- `id BIGSERIAL` is sequence-based, not time-based, so
  27. -- it stays valid across chunk drops.
  28. --
  29. -- Why `created_at` not `sent_at`:
  30. -- - `sent_at` is NULL while a delivery is in-flight. We
  31. -- can't hypertable on a NULLable time column.
  32. -- - `created_at` is the enqueue time, which is when the
  33. -- delivery came into existence. For retention purposes
  34. -- (7d after enqueue), this is the right anchor.
  35. -- - The M7 archiver uses `created_at < now() - 7d` as the
  36. -- "old enough to move" predicate.
  37. -- 1. Create the hypertable. The `migrate_data => true` flag
  38. -- tells Timescale to copy existing rows (the dev DB has
  39. -- ~270 rows from the M5–M6.5 smoke runs).
  40. --
  41. -- Timescale requires the partitioning column to be part
  42. -- of any UNIQUE/PRIMARY KEY constraint. The original PK
  43. -- is `id BIGSERIAL` which is a sequence and never reused
  44. -- across chunks, so we extend it to a composite PK
  45. -- (id, created_at). No application code needs to change
  46. -- because the constraint is still keyed on id for
  47. -- uniqueness purposes (BIGSERIAL is monotonically
  48. -- increasing, so (id, created_at) is in practice the
  49. -- same as id alone).
  50. ALTER TABLE deliveries DROP CONSTRAINT IF EXISTS deliveries_pkey CASCADE;
  51. ALTER TABLE deliveries ADD PRIMARY KEY (id, created_at);
  52. SELECT create_hypertable(
  53. 'deliveries',
  54. 'created_at',
  55. chunk_time_interval => INTERVAL '1 day',
  56. migrate_data => true,
  57. if_not_exists => true
  58. );
  59. -- 2. Add the 7-day retention policy. Timescale drops the
  60. -- whole chunk (not row-by-row) when the chunk's max time
  61. -- is older than the threshold. This is dramatically cheaper
  62. -- than a DELETE on a large table.
  63. SELECT add_retention_policy(
  64. 'deliveries',
  65. INTERVAL '7 days',
  66. if_not_exists => true
  67. );
  68. -- 3. Optional: enable compression on older chunks. This
  69. -- is a Timescale 2.x feature. We keep it off in v1 to
  70. -- avoid surprising the operator; can be enabled per-chunk
  71. -- age in M7.5.