clickhouse_schema.sql 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. -- 007_clickhouse.up.sql
  2. -- M7: ClickHouse schema for the deliveries archive.
  3. -- M8: + deliveries_dlq_archive (DLQ) with 2y TTL.
  4. --
  5. -- ClickHouse is columnar and prefers wide tables with
  6. -- per-column compression. The schema mirrors the Postgres
  7. -- `deliveries` table one-to-one for v1; a future M7.5 can
  8. -- strip unused fields (e.g. drop the JSONB payload blob
  9. -- if we never query into it in CH).
  10. --
  11. -- The DDL below is designed to be runnable from the
  12. -- archiverd's first-run setup hook. It's also what the
  13. -- M7 smoke executes by hand via curl http://clickhouse:8123/.
  14. --
  15. -- Note on types:
  16. -- - `id` is BIGINT in Postgres (BIGSERIAL). In CH we
  17. -- keep BIGINT — CH's UInt64 is the closest match,
  18. -- but for an archive that doesn't reference id
  19. -- anywhere else, BIGINT is fine.
  20. -- - `payload` is JSONB in Postgres. We store it as a
  21. -- CH String. The Go side marshals to JSON text
  22. -- before INSERT. CH's JSON type is still experimental
  23. -- in 24.x; String is the safe choice.
  24. -- - `status` is a small enum ('pending','sent','failed','dlq').
  25. -- In CH we use LowCardinality(String) — the column
  26. -- takes ~0.5 bytes per row for the dictionary.
  27. CREATE DATABASE IF NOT EXISTS ba_archive;
  28. -- ── M7: live deliveries archive ─────────────────────────────────
  29. -- 1-year retention. Rows that age out of the Timescale
  30. -- 7d hot window land here; after 1y CH drops the
  31. -- partition. Operators can override via
  32. -- ALTER TABLE ba_archive.deliveries_archive MODIFY TTL ...
  33. CREATE TABLE IF NOT EXISTS ba_archive.deliveries_archive (
  34. id BIGINT,
  35. alert_id String,
  36. company_id String,
  37. individual_id String,
  38. channel LowCardinality(String),
  39. target String,
  40. status LowCardinality(String),
  41. attempts UInt32,
  42. last_error String,
  43. payload String, -- raw JSON
  44. created_at DateTime64(3, 'UTC'),
  45. sent_at Nullable(DateTime64(3, 'UTC')),
  46. next_attempt_at Nullable(DateTime64(3, 'UTC')),
  47. -- The source postgres row's last-update marker. We
  48. -- set this to the wall-clock time at insert. Useful
  49. -- for "show me everything archived in the last 24h".
  50. archived_at DateTime DEFAULT now()
  51. ) ENGINE = MergeTree
  52. PARTITION BY toYYYYMM(created_at)
  53. ORDER BY (company_id, created_at, id)
  54. TTL toDateTime(created_at) + INTERVAL 365 DAY;
  55. -- A MATERIALIZED VIEW that aggregates per-company,
  56. -- per-day delivery counts. Used by M9 dashboards.
  57. CREATE MATERIALIZED VIEW IF NOT EXISTS
  58. ba_archive.deliveries_per_company_daily_mv
  59. ENGINE = SummingMergeTree
  60. PARTITION BY toYYYYMM(day)
  61. ORDER BY (company_id, day, channel, status)
  62. AS
  63. SELECT
  64. company_id,
  65. toDate(created_at) AS day,
  66. channel,
  67. status,
  68. count() AS n
  69. FROM ba_archive.deliveries_archive
  70. GROUP BY company_id, day, channel, status;
  71. -- ── M8: DLQ archive ────────────────────────────────────────────
  72. -- 2-year retention (vs 1y for live). DLQ entries are
  73. -- forensic data — operators want them around longer
  74. -- when triaging a regression. Mirrors the live
  75. -- deliveries_archive column shape minus the retry-
  76. -- scheduling columns (sent_at, next_attempt_at) which
  77. -- the DLQ doesn't carry.
  78. CREATE TABLE IF NOT EXISTS ba_archive.deliveries_dlq_archive (
  79. id BIGINT,
  80. alert_id String,
  81. company_id String,
  82. individual_id String,
  83. channel LowCardinality(String),
  84. target String,
  85. status LowCardinality(String),
  86. attempts UInt32,
  87. last_error String,
  88. payload String, -- raw JSON, the original NATS envelope
  89. created_at DateTime64(3, 'UTC'),
  90. archived_at DateTime DEFAULT now()
  91. ) ENGINE = MergeTree
  92. PARTITION BY toYYYYMM(created_at)
  93. ORDER BY (company_id, created_at, id)
  94. TTL toDateTime(created_at) + INTERVAL 730 DAY;
  95. -- Per-company daily DLQ counts. Used by the M9
  96. -- observability stack to surface DLQ rate per tenant.
  97. CREATE MATERIALIZED VIEW IF NOT EXISTS
  98. ba_archive.deliveries_dlq_per_company_daily_mv
  99. ENGINE = SummingMergeTree
  100. PARTITION BY toYYYYMM(day)
  101. ORDER BY (company_id, day, channel)
  102. AS
  103. SELECT
  104. company_id,
  105. toDate(created_at) AS day,
  106. channel,
  107. count() AS n
  108. FROM ba_archive.deliveries_dlq_archive
  109. GROUP BY company_id, day, channel;