Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions drizzle/migrations/0025_audit_logs_cursor_index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Migration: 0025_audit_logs_cursor_index
-- Replaces the single-column audit_logs_created_at_idx with a composite
-- (created_at DESC, id DESC) index so that keyset-cursor pagination on
-- GET /api/admin/audit is stable under concurrent inserts.
--
-- When two rows share the same created_at timestamp (common under high write
-- concurrency) the previous single-column index left the tie-breaking order
-- non-deterministic, meaning a page boundary could skip or repeat rows.
-- The composite index makes (created_at, id) the canonical sort key, matching
-- the ORDER BY and cursor predicate already used in auditLogRepo.ts.

-- Drop the old single-column index (no longer needed).
DROP INDEX IF EXISTS audit_logs_created_at_idx;

-- Create the composite covering index used by the keyset cursor predicate:
-- WHERE (created_at < $cursor_ts)
-- OR (created_at = $cursor_ts AND id < $cursor_id)
-- ORDER BY created_at DESC, id DESC
CREATE INDEX IF NOT EXISTS audit_logs_created_at_id_idx
ON audit_logs (created_at DESC, id DESC);
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,15 @@ export const auditLogs = pgTable(
auditLogsCorrelationIdx: index("audit_logs_correlation_idx").on(
t.correlationId,
),
auditLogsCreatedAtIdx: index("audit_logs_created_at_idx").on(t.createdAt),
// Composite index for stable cursor pagination: ORDER BY created_at DESC, id DESC.
// The (created_at, id) compound key is unique and monotone, so a keyset cursor
// over it is stable even when rows with the same timestamp are inserted
// concurrently — the id tie-breaker ensures no row is skipped or duplicated
// across page boundaries.
auditLogsCreatedAtIdIdx: index("audit_logs_created_at_id_idx").on(
t.createdAt,
t.id,
),
}),
);

Expand Down
Loading
Loading