fix(api): defer vector index task dispatch until dataset update commits - #40974
Open
zl86790 wants to merge 2 commits into
Open
fix(api): defer vector index task dispatch until dataset update commits#40974zl86790 wants to merge 2 commits into
zl86790 wants to merge 2 commits into
Conversation
Deal/regenerate vector index tasks were dispatched right after DatasetService._update_internal_dataset flushed, before the caller's transaction committed. The worker opens its own session and re-reads the Dataset row, so it could race ahead and read the pre-update embedding model. Defer dispatch to the session's after_commit hook so the worker only runs once the update is durable (langgenius#40961).
zl86790
requested review from
JohnJyong,
QuantumGhost and
laipz8200
as code owners
August 19, 2026 10:32
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.
Important
Fixes #<issue number>.Summary
Fixes #40961.
When you change a high-quality knowledge base's embedding model, the API updates the dataset row and immediately dispatches the Celery re-indexing task (
deal_dataset_vector_index_task.delay(...), andregenerate_summary_index_task.delay(...)for summary vectors). The catch is that the service only callssession.flush()here, notsession.commit()— the actual commit happens later, in the controller'swith_sessiondecorator, after some more DB work runs (permission checks, member list updates, etc).Since the Celery task opens its own independent DB session and re-reads the
Datasetrow from scratch (it's only ever givendataset_id, never the new model), there's a real race: the worker can dequeue and start executing before the web request's transaction actually commits. When that happens, it reads the old embedding model and re-embeds every chunk with it — so the dataset ends up permanently mismatched: metadata says the new model, but the stored vectors are still from the old one. Retrieval quality silently degrades, there's no error, and simply retrying doesn't fix it since the stored vectors already "match" whatever model was current when the task last ran.This is a genuine race condition (Postgres default isolation means the worker's session can't see the flushed-but-uncommitted update), and it got wider recently: a prior fix (#39223) changed this code from
commit()toflush()to solve a different bug (InvalidRequestError: Can't operate on closed transaction, #39191), which pushed the actual commit further away from the.delay()calls than it used to be.The fix
Instead of dispatching the tasks right after
flush(), defer them until the session'safter_commitevent actually fires — same pattern already used insnippet_service.pyfor a similar "don't fire a side effect until the transaction is durable" case. This way the worker can never see a stale row: by the time it starts, the update is guaranteed to be committed. No changes to the transaction handling itself (so #39191 stays fixed), no changes to the task signature — just moving when the dispatch happens.Files touched:
api/services/dataset_service.py— wrap the two.delay()calls in anafter_commitlistener instead of firing them inlineapi/tests/unit_tests/services/test_dataset_service_dataset.py— updated the existing test to assert the tasks are NOT dispatched before commit, and only fire once theafter_commitcallback runsScreenshots
Not applicable — backend-only fix, no UI change.
Checklist
make lint && make type-check(backend) andcd web && pnpm exec vp staged(frontend) to appease the lint gods