Skip to content

Build1 publisher3 min readPublished

Hardcoded .limit(80) query silently hid 31 published articles

The rows were present and published, but the front end fetched only the first 80 of 111 posts, and every category count agreed with it because the counts were computed from the articles already loaded. The repair took two deploys the same day.

The Engineer · Build desk

Illustration accompanying Hardcoded .limit(80) query silently hid 31 published articles

What happened

  • A developer found 31 published articles missing from their site's series cards, category filters and search, with no error, no log entry and no stack trace, while the database showed every row present and published.
  • The front end's list query carried a hardcoded .limit(80), written years earlier when the site held about sixty articles.
  • The same day brought a paged server RPC, fn_get_content_list_page, taking limit and offset, with the front end loading the first 30 rows and appending more as the reader scrolls.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Because the category count was computed from the array the front end had already loaded, no threshold or alert on that number could ever fire; only a separate database query was capable of disagreeing with the rendered list.
  • decision Bumping the constant is a temporary fix. Whoever ships .limit(200) owns the 201st post, and the logs will look exactly as empty when it arrives.
  • cost Raising the ceiling before stripping article bodies out of the list makes the index page heavier for every visitor, because the old query returned the full content field for each row it fetched.
  • capability A browser test that clicks, scrolls and then compares rendered items against SQL counts can actually fail on silent truncation, which a test reading the same fetch as the page cannot.

Category totals came from `pages.value.filter(...).length`, computed over the array the front end had already loaded [13]. The list and the number describing the list read from the same fetch, so the count always agreed with the list [13].

Nothing fired because the query succeeded. It returned 80 rows, and 80 was the ceiling written into the list page component back when the site held about sixty articles [3][4]. On the day of the fault there were 111 published posts [5], and 111 minus 80 is exactly the 31 that had gone missing [22]. Sort order picked which ones: mostly the 2020 to 2023 coffee posts [6]. Series cards, category bucketing and site search were all correct about the corpus they were handed [7]. "It isn't broken. It is quietly short," the author wrote [21].

The stopgap was `.limit(200)`, and the post reports it took thirty seconds and brought all 31 articles back [8]. It also bought 89 posts of headroom before the same constant binds again [26], which the author describes as the identical bug returning identically [9]. Raising the ceiling raised the bytes, too: the old list query returned the whole `content` jsonb for every article it fetched [16].

The third change stopped that. The list now returns summary, cover image and reading time only, and payload per batch went from 348,954 bytes to 22,114, which the post rounds to about 94% less [16][17]. Dividing gives 6.3%, so the cut is 93.7% [23].

For that number to mean anything to a visitor you need to know how many batches they pull. The front end loads 30 and appends on scroll [11]. Four batches cover 111 posts, so a reader who scrolls the whole archive moves about 88,456 bytes, roughly a quarter of the old single payload; a reader who stops at the top moves 22,114 [24][17]. That holds only if 22,114 is a 30-row batch, which is the shape the post describes [24].

Verification crossed two independent lines: SQL counts per category and per series, and Playwright driving a real browser, clicking the category buttons and scrolling to trigger the next page, counting what actually rendered [18]. Before the fix the buttons summed to 8 + 51 + 6 = 65 against a database total of 111 [19]. After, they summed to 23 + 70 + 13 = 106, plus five more [20]. The pre-fix figures account for 65 of the 80 rows the query did load, and the post does not break out the other 15 [25].

The `countRows` query is the part I would copy first: two narrow columns, asked of the database directly, moving no content [14]. It can disagree with the rendered list. The author wrote that how many are there and which ones do I load are two different questions, and that they should never share a data source [15].

What to watch

  • Whether fn_get_content_list_page enforces its own upper bound on the limit parameter it accepts.
  • Whether the countRows figure and the rendered count are compared on every deploy, or only in the one-off Playwright run the post describes.
  • Whether the .limit(200) call still exists anywhere in the component, or the paged RPC replaced that code path entirely.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories