fix(postgres): per-connection timeouts so a wedged index can't stall the store#49
Merged
Merged
Conversation
…the store
A corrupt custom-index access method (e.g. a BM25 index left inconsistent
by a past pg_resetwal) can make an INSERT spin indefinitely on a
buffer-content lock. The wedged backend holds its relation lock the whole
time, so every later statement on that table queues behind it - one stuck
insert silently stalls the entire vector store, saturates the connection
pool, and is only cleared by a Postgres restart (it is uninterruptible, so
pg_terminate_backend does not help). Observed in production: a single
4-day-wedged INSERT with 75 CREATE INDEX statements queued behind it.
The pool opened connections with no timeouts at all, so nothing bounded
the cascade. Set safe defaults on every pooled connection:
- lock_timeout=30s -> queued statements fail fast instead of
piling up for days (the cascade-killer;
never interrupts real work)
- idle_in_transaction_session_timeout=300s -> reap abandoned txns
- statement_timeout -> opt-in (POSTGRES_STATEMENT_TIMEOUT); off by
default because a large DiskANN/HNSW build
can legitimately exceed any fixed limit.
Index builds run via execNoStatementTimeout (SET LOCAL statement_timeout=0
in a tx) so operators can safely enable statement_timeout without risking
long builds. lock_timeout still applies to builds.
All three timeouts are env-overridable; "0"/"off" opts out.
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
mudler
added a commit
to mudler/LocalAI
that referenced
this pull request
Jun 25, 2026
* chore: bump localrecall for postgres per-connection timeouts Pulls mudler/LocalRecall#49: sets lock_timeout / idle_in_transaction (default on) + opt-in statement_timeout on every pooled connection, so a corrupt/wedged index (e.g. a BM25 insert spinning on a buffer-content lock) can no longer hold its relation lock forever and head-of-line block the whole vector store. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code] * docs(agents): document PostgreSQL connection safety timeouts Note the POSTGRES_LOCK_TIMEOUT / POSTGRES_IDLE_IN_TRANSACTION_TIMEOUT / POSTGRES_STATEMENT_TIMEOUT env vars read by the embedded vector store, and that safe defaults are on automatically. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code] --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
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.
Problem
A corrupt custom-index access method (observed: a BM25 index from
pg_textsearchleft inconsistent by a pastpg_resetwal) can make anINSERTspin indefinitely on aBufferContentLWLock. The wedged backend:pg_terminate_backendreturns true but the backend never dies, so only a Postgres restart clears it (and the next insert re-wedges).Observed in production: a single 4-day-wedged
INSERTwith 75CREATE INDEX IF NOT EXISTSstatements queued behind it (LocalRecall re-issues index DDL + inserts on every collection access), connections climbing towardmax_connections.pg_buffercacheconfirmed the only pinned buffer in the instance was in the corrupt bm25 index.The pool opened connections with no timeouts at all, so nothing bounded the cascade.
Fix
applyConnTimeoutssets safe per-connection timeouts on the pool config before the pool is opened:lock_timeout30sidle_in_transaction_session_timeout300sstatement_timeoutIndex builds run via
execNoStatementTimeout(SET LOCAL statement_timeout = 0in a tx), so operators can safely enablestatement_timeoutwithout risking long builds.lock_timeoutstill applies to builds.All three are env-overridable;
"0"/"off"opts out:POSTGRES_LOCK_TIMEOUTPOSTGRES_IDLE_IN_TRANSACTION_TIMEOUTPOSTGRES_STATEMENT_TIMEOUTTests
New
applyConnTimeoutsunit specs (Ginkgo, no live DB): defaults, env overrides,0/offopt-out, nilRuntimeParams.go build ./...andgo vet ./rag/engine/clean.Follow-ups (separate)
pg_textsearchto a tagged release inDockerfile.pgsql(currently an untrackedgit cloneHEAD → the unreproducible1.0.0-dev).