Skip to content

Build the search index one package at a time - #486

Merged
thomashoneyman merged 2 commits into
masterfrom
incremental-search-index
Jul 4, 2026
Merged

Build the search index one package at a time#486
thomashoneyman merged 2 commits into
masterfrom
incremental-search-index

Conversation

@thomashoneyman

@thomashoneyman thomashoneyman commented Jul 3, 2026

Copy link
Copy Markdown
Member

Pursuit has been getting OOM-killed several times an hour (over 1,600 times in the past 30 days per the systemd restart counter), which is why the site regularly goes down. The root cause is the search index regeneration in startRegenThread, which runs at startup and hourly: getAllPackages decoded the latest version of every package in the database into memory at once (~1,400 packages, 1.7GB of JSON) before building the index. The decoded packages now require more memory than the 4GB droplet has, so the kernel kills the process mid-build — currently within ~2 minutes of startup, meaning the server never finishes building its index and crash-loops indefinitely (nginx's static page cache is the only reason the site mostly still works).

Fix

Decode packages one at a time and retain only each package's (comparatively small, fully forced) index entries, filling in reverse dependency counts once all packages have been seen. The resulting index is identical.

Verified against a full clone of the production data set (pursuit-backups): the server now peaks around 1.1GB resident with a fully built index, versus 3.6GB+ and an OOM kill before.

Also in this PR:

  • pursuit.service: use both vCPUs and cap the heap below available RAM (-N2 -A64m -M3G), so the RTS runs out of heap before the kernel OOM killer steps in.
  • CI: update deprecated actions (actions/cache@v2 and upload-artifact@v2 have been disabled by GitHub and no longer run at all) and pin the runner to ubuntu-24.04 to match the server OS (being upgraded separately).

The hourly search index regeneration (which also runs at startup) decoded
every package in the database into memory at once before building the index.
The decoded packages now need more memory than the server has, so the server
was being OOM-killed several times an hour (over 1,600 times in the last
30 days), taking the site down for a couple of minutes each time.

Instead, decode packages one at a time and retain only each package's index
entries, filling in reverse dependency counts once all packages have been
seen. Serving the full production data set now peaks around 1.1GB resident
instead of 3.6GB+.

Also:
- Use both vCPUs and cap the heap below available RAM (-N2 -A64m -M3G)
- Update deprecated GitHub Actions (cache/upload-artifact v2 no longer run)
  and pin the runner to ubuntu-24.04 to match the server OS
- A package file which cannot be read or decoded is now skipped with a
  logged error instead of silently aborting the entire index build (which
  left the search index empty until the next successful hourly run).
  Verified by corrupting a package file in a local run against production
  data: the package is skipped and the rest of the index builds normally.
- Fully evaluate the new index on the regeneration thread before publishing
  it, so the first search request after a regen no longer pays for building
  the trie.
- Pin stack to 3.9.3 in CI (what "latest" resolves to today) so release
  builds stay reproducible.

Memory, measured over 21 consecutive index rebuilds against the full
production data set with +RTS -s -N2 -A64m -M3G: 715MB maximum residency,
2.2GB peak RTS memory in use, no growth across cycles.
@thomashoneyman
thomashoneyman merged commit 6bc1edc into master Jul 4, 2026
2 checks passed
@thomashoneyman
thomashoneyman deleted the incremental-search-index branch July 4, 2026 13:44
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