You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
litestream replicate does not start with 10 000 databases in its config. It dies during boot with the Go runtime's hard thread ceiling:
runtime: program exceeds 10000-thread limit
fatal error: thread exhaustion
5 000 databases run fine on the same machine, same config, same binary. The wall is somewhere between the two, and it is a crash at startup, not a degradation.
Reproduction
litestream v0.5.14, linux/arm64, 4 vCPU / 32 GB
N empty WAL-mode SQLite databases, one replica: each
30 019 goroutines at the point of the crash. Memory was not a factor — 31 GB were free.
Confirmed on two independent runs.
What it is not
Not file descriptors. That was my first hypothesis and it is wrong: the second run set ulimit -n 200000 and the outcome was identical. File descriptors scale at a steady 6 per database (3 006 / 6 006 / 15 006 / 30 006), so 10 000 needs ~60 000 — comfortably under that limit.
Not memory. RSS is close to linear at ~0.36 MB per database and the crash happens with tens of GB available.
Why it seems worth reporting
The failure mode is abrupt. There is no slow degradation to notice: replication for every database stops at once, at startup. For a backup tool that is an unusually bad shape — the discovery moment tends to be a restore.
An operator cannot raise the limit. It is not a ulimit or a sysctl — debug.SetMaxThreads defaults to 10 000 inside the process, so the ceiling is only reachable from litestream's own code.
v0.5's directory-watching mode invites this scale. It makes many-database deployments the natural configuration, and nothing warns that a ceiling exists.
debug.SetMaxThreads is a one-line raise, though it only moves the wall. The more durable fix is presumably fewer OS threads held per database — the goroutines that block in syscalls are what consume them (the crash dump is full of modernc.org/sqlite.interruptOnDone select frames).
Even documenting a supported maximum would help: right now the only way to find it is to hit it.
Environment
litestream v0.5.14 (fatal error reproduced twice)
linux/arm64, Amazon Linux 2023
Deployment shape: many databases in one process, the large majority write-idle
Summary
litestream replicatedoes not start with 10 000 databases in its config. It dies during boot with the Go runtime's hard thread ceiling:5 000 databases run fine on the same machine, same config, same binary. The wall is somewhere between the two, and it is a crash at startup, not a degradation.
Reproduction
replica:eachfile://replicas (no network backend involved)l0-retention: 3h,l0-retention-check-interval: 30m,levels: 30m/2h/6h,snapshot: 6h/72h,sync-interval: 1s30 019 goroutines at the point of the crash. Memory was not a factor — 31 GB were free.
Confirmed on two independent runs.
What it is not
Not file descriptors. That was my first hypothesis and it is wrong: the second run set
ulimit -n 200000and the outcome was identical. File descriptors scale at a steady 6 per database (3 006 / 6 006 / 15 006 / 30 006), so 10 000 needs ~60 000 — comfortably under that limit.Not memory. RSS is close to linear at ~0.36 MB per database and the crash happens with tens of GB available.
Why it seems worth reporting
ulimitor asysctl—debug.SetMaxThreadsdefaults to 10 000 inside the process, so the ceiling is only reachable from litestream's own code.Possible directions
debug.SetMaxThreadsis a one-line raise, though it only moves the wall. The more durable fix is presumably fewer OS threads held per database — the goroutines that block in syscalls are what consume them (the crash dump is full ofmodernc.org/sqlite.interruptOnDoneselect frames).Even documenting a supported maximum would help: right now the only way to find it is to hit it.
Environment
fatal errorreproduced twice)