Repository navigation
fix(backups): GOMEMLIMIT so a two-database backup run fits the 512 MiB cap - #108
Merged
Merged
Conversation
…B cap Each database's age scrypt step (work factor 18) allocates ~256 MiB that is garbage right after; without a soft limit the GC let the second database's allocation stack on the first and the 512 MiB container was OOM-killed (exit 137) after app/ was stored, killing the service with it. GOMEMLIMIT=384MiB makes the GC reclaim first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Rebased onto main (0553a2b). The only conflict was graph.go: main now names the cap |
jayleaton
force-pushed
the
fix/backup-oom
branch
from
October 23, 2026 12:07
cb23318 to
1c2146d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
The backup service (
apps/backup, capped at 512 MiB indeploy/platform/graph.go) is OOM-killed whenever it backs up two databases (app + admin). Each database's age scrypt step (packages/backup, work factor 18, so 128·2¹⁸·8 B = 256 MiB) allocates about 256 MiB that becomes garbage right after. With no soft memory limit, the Go GC lets the second database's allocation stack on the first, and the kernel kills the run (and the long-running service with it) afterapp/is stored. A derived project hit this on its first in-Railway run.Reproduction
Local, same cap, no Railway. The image is built from
main(docker build -f apps/backup/Dockerfile), and a throwawaypostgres:18-alpineholds anappand anadmindatabase:Before (main), 2 runs out of 2:
After (this branch), 3 runs out of 3:
Control: the fixed image with
-e GOMEMLIMIT=offexits 137 afterapp/again, so the limit is what makes the difference.Fix
ENV GOMEMLIMIT=384MiBinapps/backup/Dockerfile's final stage, with the reason in a comment. The 512 MiB line indeploy/platform/graph.gonow notes that the Dockerfile's limit stays below it. There's no code change: the limit is soft, so a largerpg_dumpcan still use the whole cap.Verified
make check-fast: ok.make generate: no diff.make lintandmake deploy-check: 0 issues.make test: on this busy shared host,packages/auth's-racebinary wassignal: killedinside the full run twice (the user cgroup'smemory.eventsshowsoom_kill; other sessions were compiling at the same time). It passes alone (go test -race ./packages/auth/: ok, 3.6 s). This PR changes no Go code, and the identical root-module code passed the fullmake test(-race, Postgres, coverage floors) on the base commit earlier today. CI runs it again.Risk
Low. A backup that genuinely needs more than 384 MiB of live heap still runs; the GC just works harder near the limit.
🤖 Generated with Claude Code