Skip to content

fix(db): don't stop iteration early when a page has fewer rows than size - #14960

Open
Adityaj0 wants to merge 1 commit into
Kong:masterfrom
Adityaj0:fix/db-off-strategy-iteration-ttl-truncation
Open

fix(db): don't stop iteration early when a page has fewer rows than size#14960
Adityaj0 wants to merge 1 commit into
Kong:masterfrom
Adityaj0:fix/db-off-strategy-iteration-ttl-truncation

Conversation

@Adityaj0

@Adityaj0 Adityaj0 commented Aug 2, 2026

Copy link
Copy Markdown

Summary

Fixes #14959.

kong.db.iteration.page_iterator assumed that a strategy's page always contains exactly size rows whenever it also returns a non-nil offset. The off (DB-less/Hybrid mode) strategy breaks that assumption: it drops rows from a page when the corresponding entity has expired via TTL, while still returning an offset if more raw entries remain. When a page came back short for that reason, the iterator's continuation check (i > size) never triggered, so it silently stopped instead of fetching the next page -- dropping the remainder of the data set from DAO:each() / DAO:each_for_export() (used by Admin API config export, kong config db_export, and cache warmup).

Changes

  • kong/db/iteration.lua: fetch the next page whenever the current page is exhausted (rows[i] is nil) and an offset is present, instead of additionally requiring i > size. This removes the flawed invariant entirely rather than patching around it, and is behavior-preserving for strategies (e.g. postgres) that do always return exactly size rows per non-final page: for those, i still only exceeds #rows once i > size, exactly matching the old check.
  • Added spec/01-unit/01-db/14-iteration_spec.lua, a focused unit test that drives iteration.by_row() with a fake pager returning a short first page (1 row where size=2) with a non-nil offset, and asserts all 3 rows across both pages are yielded.

Test plan

  • New unit test reproduces the bug against the fixed code (verified by hand-tracing: with the old code, i reaches nil at i=2 while size=2, so i > size is false and iteration stops after 1 row instead of continuing to fetch the 2nd page).
  • luac -p syntax-checked the changed files.
  • CI (I was not able to run the full busted/OpenResty integration suite in my local environment — please let me know if anything needs adjusting).

kong.db.iteration's page_iterator assumed that whenever a strategy
returned a non-nil offset, the current page necessarily contained
exactly `size` rows, and only fetched the next page once its internal
counter passed `size`. This assumption doesn't hold for the `off`
(DB-less/Hybrid mode) strategy, which drops expired TTL'd entities from
a page while still reporting an offset when more raw entries remain.
When a page came back short, the iterator stopped before reaching that
offset, silently dropping the remaining entities from DAO:each() /
DAO:each_for_export() -- affecting Admin API config export, `kong
config db_export`, and db_cache_warmup_entities.

Fetch the next page whenever the current page is exhausted and an
offset is present, instead of relying on the row count matching size.
@CLAassistant

CLAassistant commented Aug 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DB-less/Hybrid mode: DAO:each() silently truncates output when a page contains expired TTL'd rows

2 participants