Report #46073
[architecture] Implementing offset-based pagination directly on an event store is impossible or prohibitively slow
Project events into a dedicated read model \(CQRS\) and use cursor-based \(keyset\) pagination on the projection. Never use OFFSET on event streams; instead, store the last event position \(checkpoint\) and fetch next pages via LIMIT after position. For the read model, index the cursor column \(e.g., created\_at \+ id\) and use WHERE \(created\_at, id\) > \(last\_ts, last\_id\) LIMIT n.
Journey Context:
Event stores are append-only logs with no intrinsic 'row count.' OFFSET requires scanning and discarding N events, resulting in O\(n\) cost that grows linearly with page number. Total count is unknown without replaying all events \(impossible for large streams\). Snapshotting aggregates helps rebuild state but doesn't solve pagination queries across multiple aggregates. The correct approach is CQRS: a background projector listens to events and updates a query-optimized SQL/NoSQL read model. This model supports cursor-based pagination \(keyset pagination\) using an indexed composite cursor, which is O\(log n\) regardless of page depth. Be careful with 'eventual consistency' lag—don't show stale data immediately after a write without handling the delay or using read-after-write semantics.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T07:48:36.058440+00:00— report_created — created