fix: enforce uniqueness and hour alignment for agent runtime usage events (#27983)

The usage generator writes `hb_agent_runtime_v1` rows with `created_at`
at the UTC hourly bucket start and exactly one row per bucket, but
nothing in the schema enforced either invariant. A duplicate bucket row
under a different id would be double-counted by any consumer summing
`runtime_ms`, and a misaligned `created_at` would skew which usage
period a bucket is attributed to.

This replaces the non-unique partial index
`idx_usage_events_agent_runtime` (from migration 000561) with a unique
index of the same shape and adds an hour-alignment `CHECK` constraint.
Both statements validate existing rows: every supported writer has
always produced conforming data, so a pre-existing violator is anomalous
and failing the migration loudly beats silently rewriting usage rows.
`generateBucket` treats a unique violation on the bucket index as
another replica having won the race, mirroring the existing `ON CONFLICT
(id)` no-op for committed rows.

The `coderd/notifications` sync commit and its revert cancel out (the
drift they addressed was fixed on main by #27979); the PR's net diff is
only the usage-event changes.

Part 1 of a 3-PR stack splitting up #27796 (see there for review
history). Stack: this PR → #27984#27985.
This commit is contained in:
Jaayden Halko
2026-08-17 16:23:45 +07:00
committed by GitHub
parent bf236bb340
commit 20c376a575
8 changed files with 132 additions and 10 deletions
+3 -2
View File
@@ -3552,7 +3552,8 @@ CREATE TABLE usage_events (
publish_started_at timestamp with time zone,
published_at timestamp with time zone,
failure_message text,
CONSTRAINT usage_event_type_check CHECK ((event_type = ANY (ARRAY['dc_managed_agents_v1'::text, 'hb_ai_seats_v1'::text, 'hb_agent_runtime_v1'::text])))
CONSTRAINT usage_event_type_check CHECK ((event_type = ANY (ARRAY['dc_managed_agents_v1'::text, 'hb_ai_seats_v1'::text, 'hb_agent_runtime_v1'::text]))),
CONSTRAINT usage_events_agent_runtime_hour_aligned CHECK (((event_type <> 'hb_agent_runtime_v1'::text) OR (date_trunc('hour'::text, timezone('UTC'::text, created_at)) = timezone('UTC'::text, created_at))))
);
COMMENT ON TABLE usage_events IS 'usage_events contains usage data that is collected from the product and potentially shipped to the usage collector service.';
@@ -4899,7 +4900,7 @@ CREATE INDEX idx_template_versions_has_ai_task ON template_versions USING btree
CREATE UNIQUE INDEX idx_unique_preset_name ON template_version_presets USING btree (name, template_version_id);
CREATE INDEX idx_usage_events_agent_runtime ON usage_events USING btree (event_type, created_at) WHERE (event_type = 'hb_agent_runtime_v1'::text);
CREATE UNIQUE INDEX idx_usage_events_agent_runtime ON usage_events USING btree (event_type, created_at) WHERE (event_type = 'hb_agent_runtime_v1'::text);
CREATE INDEX idx_usage_events_ai_seats ON usage_events USING btree (event_type, created_at) WHERE (event_type = 'hb_ai_seats_v1'::text);