Skip to content

Serialise decoding of large package files#487

Merged
thomashoneyman merged 1 commit into
masterfrom
large-decode-gate
Jul 5, 2026
Merged

Serialise decoding of large package files#487
thomashoneyman merged 1 commit into
masterfrom
large-decode-gate

Conversation

@thomashoneyman

Copy link
Copy Markdown
Member

Problem

v0.9.10 fixed the search index build's memory use, which surfaced the next constraint: rendering any documentation page decodes the entire docs JSON for that package version (transiently ~tens of times the file size in heap). A few generated packages (react-icons 24MB, elmish-html 18MB, deku 13MB × hundreds of versions, ...) have enormous numbers of rarely-cached documentation pages. Crawlers walk them in parallel; concurrent giant decodes stack and exhaust the heap (-M cap), restarting the server — observed 15× in the 24h after the v0.9.10 deploy, with purescript-deku pages alone accounting for 94 backend 502s in one day.

Fix

  • Decodes of files ≥5MB run one at a time through an app-wide FIFO lock (Handler.Database.lookupPackage). Packages under the threshold — nearly all of them — are unaffected.
  • The file is read inside the lock (size checked via stat), so queued requests don't each pin a copy of the file bytes.
  • The queue is bounded (16): excess requests fail fast with a 503 instead of accumulating while clients time out upstream. The hourly index regen uses a wait-forever policy exempt from the bound so the index never silently omits a package.
  • Version pages no longer decode the same file twice when the requested version is the latest (previously findPackageWithLatest did, and held both copies across a synchronous GitHub readme fetch).

Also

  • robots.txt: disallow SEO/bulk crawlers (Semrush, Ahrefs, DotBot, MJ12, Amazonbot, Bytespider, PetalBot)
  • Weekly cache-eviction cron (files unaccessed 90 days) installed by the deploy — the page cache is pure derived data and can no longer fill the disk
  • -M3G-M3400m now that the server has 4GB swap to absorb spikes
  • deploy/SERVER.md documenting server-only and DO-account state

Verification (full production data set, empty cache, no nginx shield)

  • 30-way concurrent burst on react-icons/1.1.6's version page: previous code dies (heap exhausted at -M3400m, 3.7GB RSS); this code serves 16×200 + 14×503 with RSS flat at 2.6GB, search responsive throughout.
  • 8 concurrent workers walking 240 giant-package module pages: 232×200, zero heap exhaustions, peak RSS 2.59GB.
  • --pedantic clean; test suite passes.

Rendering any documentation page decodes the entire docs JSON for that
package version, which transiently needs tens of times the file's size in
heap. A few generated packages (react-icons, elmish-html, deku, ...) have
files of 10MB or more with enormous numbers of rarely-cached documentation
pages, so a handful of concurrent crawler requests for those pages could
exhaust the heap and restart the server (observed 15 times in the 24 hours
after v0.9.10 fixed the search index build's memory use).

Decodes of files over 5MB now run one at a time; smaller packages (nearly
all of them) are unaffected. The file is only read once the lock is held,
so queued requests do not pin file contents, and the queue is bounded:
excess requests fail fast with a 503 rather than accumulating while clients
time out. The hourly index regeneration is exempt from the bound so the
index never silently omits a package. Package pages also no longer decode
the same file twice when the requested version is the latest.

Verified against the full production data set: a 30-way concurrent burst of
requests for a 15MB package's version page kills the previous code (heap
exhausted at -M3400m, 3.7GB RSS) and is served by this code as 16 200s and
14 fast 503s with RSS flat at 2.6GB.

Also:
- robots.txt: disallow SEO/bulk crawlers (Semrush, Ahrefs, DotBot, MJ12,
  Amazonbot, Bytespider, PetalBot), which generate most of the long-tail
  page traffic
- Evict page-cache files not accessed in 90 days via a weekly cron job
  installed by the deploy, so the cache cannot fill the disk
- Raise the heap limit to -M3400m now that the server has swap
- Add deploy/SERVER.md documenting server-only and DO account state
@thomashoneyman thomashoneyman merged commit 732a544 into master Jul 5, 2026
2 checks passed
@thomashoneyman thomashoneyman deleted the large-decode-gate branch July 5, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant