106 lines
4.4 KiB
SQL
106 lines
4.4 KiB
SQL
-- +goose Up
|
|
-- records holds one durable notification record per accepted intent. The
|
|
-- (producer, idempotency_key) UNIQUE constraint replaces the previous Redis
|
|
-- idempotency keyspace: the durable row IS the idempotency reservation.
|
|
CREATE TABLE records (
|
|
notification_id text PRIMARY KEY,
|
|
notification_type text NOT NULL,
|
|
producer text NOT NULL,
|
|
audience_kind text NOT NULL,
|
|
recipient_user_ids jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
payload_json text NOT NULL,
|
|
idempotency_key text NOT NULL,
|
|
request_fingerprint text NOT NULL,
|
|
request_id text NOT NULL DEFAULT '',
|
|
trace_id text NOT NULL DEFAULT '',
|
|
occurred_at timestamptz NOT NULL,
|
|
accepted_at timestamptz NOT NULL,
|
|
updated_at timestamptz NOT NULL,
|
|
idempotency_expires_at timestamptz NOT NULL,
|
|
CONSTRAINT records_idempotency_unique UNIQUE (producer, idempotency_key)
|
|
);
|
|
|
|
-- Newest-first listing index used by operator/audit reads.
|
|
CREATE INDEX records_listing_idx
|
|
ON records (accepted_at DESC, notification_id DESC);
|
|
|
|
-- routes stores one row per (notification_id, route_id). next_attempt_at is
|
|
-- non-NULL only while the row is a scheduling candidate (status pending or
|
|
-- failed); the partial index keeps the scheduler scan tight.
|
|
CREATE TABLE routes (
|
|
notification_id text NOT NULL
|
|
REFERENCES records(notification_id) ON DELETE CASCADE,
|
|
route_id text NOT NULL,
|
|
channel text NOT NULL,
|
|
recipient_ref text NOT NULL,
|
|
status text NOT NULL,
|
|
attempt_count integer NOT NULL DEFAULT 0,
|
|
max_attempts integer NOT NULL,
|
|
next_attempt_at timestamptz,
|
|
resolved_email text NOT NULL DEFAULT '',
|
|
resolved_locale text NOT NULL DEFAULT '',
|
|
last_error_classification text NOT NULL DEFAULT '',
|
|
last_error_message text NOT NULL DEFAULT '',
|
|
last_error_at timestamptz,
|
|
created_at timestamptz NOT NULL,
|
|
updated_at timestamptz NOT NULL,
|
|
published_at timestamptz,
|
|
dead_lettered_at timestamptz,
|
|
skipped_at timestamptz,
|
|
PRIMARY KEY (notification_id, route_id)
|
|
);
|
|
|
|
-- Drives the publishers' due-route pull. Partial predicate keeps the index
|
|
-- narrow: terminal rows (published / dead_letter / skipped) never appear.
|
|
CREATE INDEX routes_due_idx
|
|
ON routes (next_attempt_at)
|
|
WHERE next_attempt_at IS NOT NULL;
|
|
|
|
-- Coarse status / channel filters used by operator views.
|
|
CREATE INDEX routes_status_idx ON routes (status);
|
|
CREATE INDEX routes_channel_idx ON routes (channel);
|
|
|
|
-- dead_letters carries the operator-visible record for one route that
|
|
-- exhausted automated handling. Cascade tied to the parent route row so a
|
|
-- record-level retention DELETE clears dependent dead-letter rows naturally.
|
|
CREATE TABLE dead_letters (
|
|
notification_id text NOT NULL,
|
|
route_id text NOT NULL,
|
|
channel text NOT NULL,
|
|
recipient_ref text NOT NULL,
|
|
final_attempt_count integer NOT NULL,
|
|
max_attempts integer NOT NULL,
|
|
failure_classification text NOT NULL,
|
|
failure_message text NOT NULL,
|
|
recovery_hint text NOT NULL DEFAULT '',
|
|
created_at timestamptz NOT NULL,
|
|
PRIMARY KEY (notification_id, route_id),
|
|
FOREIGN KEY (notification_id, route_id)
|
|
REFERENCES routes(notification_id, route_id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX dead_letters_listing_idx
|
|
ON dead_letters (created_at DESC, notification_id DESC, route_id DESC);
|
|
|
|
-- malformed_intents stores operator-visible records for stream entries the
|
|
-- intent validator could not accept. Independent retention pass.
|
|
CREATE TABLE malformed_intents (
|
|
stream_entry_id text PRIMARY KEY,
|
|
notification_type text NOT NULL DEFAULT '',
|
|
producer text NOT NULL DEFAULT '',
|
|
idempotency_key text NOT NULL DEFAULT '',
|
|
failure_code text NOT NULL,
|
|
failure_message text NOT NULL,
|
|
raw_fields jsonb NOT NULL,
|
|
recorded_at timestamptz NOT NULL
|
|
);
|
|
|
|
CREATE INDEX malformed_intents_listing_idx
|
|
ON malformed_intents (recorded_at DESC, stream_entry_id DESC);
|
|
|
|
-- +goose Down
|
|
DROP TABLE IF EXISTS malformed_intents;
|
|
DROP TABLE IF EXISTS dead_letters;
|
|
DROP TABLE IF EXISTS routes;
|
|
DROP TABLE IF EXISTS records;
|