Skip to content

Why use a cursor instead of OFFSET for event search?

1 min read · main part only; folded sections are optional

After this you can answer: Why use a cursor instead of OFFSET for event search?

The search query fetches limit plus one rows, returns at most limit, and derives the next cursor from the last returned item. Its timestamp is formatted with microsecond precision because JavaScript Date would otherwise lose precision and could skip or repeat equal-time rows at a boundary.

The cursor includes asOf so moving wall-clock time does not change the upcoming filter between requests. A composite B-tree index supports the starts_at and ID order. Text search has its own trigram index; a cursor alone cannot make every filter fast.

Inserts, deletes and sort-key changes remain visible according to each query’s database view. A new row before the cursor may not appear in that traversal; a moved row can cross its boundary. For browsing events this can be acceptable. An audit export would need snapshot or version semantics and a clear retention policy instead.

Why not order only by starts_at?
Several events can share a start time. The UUID tie-breaker makes the tuple order deterministic and gives the next page an unambiguous boundary.
What if filters change between pages?
Start a new traversal and discard the old cursor. A cursor is meaningful only with the same search semantics; the current shape is not a signed snapshot token binding every filter.
What do you measure before adding an index?
Use realistic filters and deep-page plans, row counts and latency. A matching index helps only when the planner and workload can use it; extra indexes also increase write and storage cost.

Read Search pagination, then inspect the rds-postgres piece →.

apps/api/src/queries/event-search.ts → search

Read this book online: https://book.theversehub.in/