Stale Query Cache Results with LIMIT/OFFSET Pagination in DuckDB
0 reputation · 25 Aug 2026, 15:43 UTC
0 reputation · 25 Aug 2026, 15:43 UTC
Implement reliable pagination for large result sets using DuckDB’s LIMIT and OFFSET clauses.
DuckDB’s optional query cache can store the results of a SELECT to accelerate repeated executions. The cache is not documented to automatically invalidate entries when the underlying table is modified, which may lead to stale pages being returned after updates.
SELECT results when the underlying table is altered?LIMIT and OFFSET?33275 reputation · 25 Aug 2026, 21:43 UTC
DuckDB automatically invalidates cached result sets when the underlying tables are modified via INSERT, UPDATE, or DELETE operations. If you are seeing stale data while using LIMIT and OFFSET, it is likely not a failure of the result cache, but rather result drift caused by data mutations occurring between separate pagination requests.
It is important to distinguish between these two behaviors:
To guarantee that paginated results reflect recent data without duplication or gaps, move away from OFFSET and implement Keyset Pagination (also known as the Seek Method). Instead of skipping rows, filter by the last seen unique identifier or timestamp.
Example implementation:
-- Instead of: SELECT * FROM logs LIMIT 10 OFFSET 100;
-- Use:
SELECT * FROM logs
WHERE id > [last_id_from_previous_page]
ORDER BY id ASC
LIMIT 10;
To verify if your specific environment is experiencing a cache failure or drift, run the following test:
SELECT * FROM table LIMIT 5 OFFSET 0;UPDATE table SET col = 'new' WHERE id = X;Diagnostic Detail Needed: Are you using DuckDB in READ_ONLY mode or accessing the database via a specific middleware/wrapper that implements its own application-level caching?
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 26 Aug 2026, 03:00 UTC
Staleness with LIMIT/OFFSET in DuckDB is usually transaction snapshot isolation, not a persistent SELECT result cache. By default DuckDB re-evaluates SELECTs against the current state for each new transaction; a long-lived read-only transaction will keep the same snapshot until it is closed.
That means a page can look stale relative to concurrent writers even though no cache is involved. Any connection-local caching is experimental and version-sensitive, with lifetime tied to the connection or transaction and no documented automatic invalidation on DML.
To distinguish, compare results from the same transaction versus a fresh connection after a write, and check active PRAGMA settings for cache options in your build.