Skip to content

fix: offload sync Pydantic serialization to thread in async write path#1178

Open
charles-dyfis-net wants to merge 1 commit intoqdrant:devfrom
charles-dyfis-net:issue-1175
Open

fix: offload sync Pydantic serialization to thread in async write path#1178
charles-dyfis-net wants to merge 1 commit intoqdrant:devfrom
charles-dyfis-net:issue-1175

Conversation

@charles-dyfis-net
Copy link
Copy Markdown

AsyncPointsApi methods for upsert_points, batch_update, and update_vectors call _build_for_* which runs jsonable_encoder (thus Pydantic's model_dump_json) synchronously before the first await. This blocks the event loop for the entire duration of Pydantic serialization, which scales with point count and vector dimensionality.

Move the sync _build_for_* call into run_in_executor for these three methods (skipping some others where body size is bounded and small). The _build_for_* methods return an unawaited coroutine object (from the async api_client.request), so the executor runs the serialization in a thread and we await the resulting coroutine back on the event loop.

Fixes #1175

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 23, 2026

Deploy Preview for poetic-froyo-8baba7 failed.

Name Link
🔨 Latest commit 478165e
🔍 Latest deploy log https://app.netlify.com/projects/poetic-froyo-8baba7/deploys/69c5786f7ba1c60008a3eed1

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9c7b6c8c-248d-4f6f-8bae-f2bf16ce747b

📥 Commits

Reviewing files that changed from the base of the PR and between b76b850 and 478165e.

📒 Files selected for processing (1)
  • qdrant_client/http/api/points_api.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • qdrant_client/http/api/points_api.py

📝 Walkthrough

Walkthrough

Added import asyncio and updated three async methods in AsyncPointsApibatch_update, update_vectors, and upsert_points — to offload synchronous _build_for_* calls to a background thread using asyncio.to_thread(...), capturing the returned coroutine and awaiting it. SyncPointsApi methods were not changed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • tbung
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: offloading synchronous Pydantic serialization to a thread in the async write path.
Description check ✅ Passed The description is clearly related to the changeset, explaining the problem (sync serialization blocking event loop), the solution (moving build_for* calls to executor/thread), and linking to issue #1175.
Linked Issues check ✅ Passed The PR meets the core objective from #1175: offloading CPU-bound Pydantic serialization to a thread for three async write methods (upsert_points, batch_update, update_vectors) to prevent event loop blocking.
Out of Scope Changes check ✅ Passed All changes are scoped to the three target async methods; no unrelated changes were introduced beyond the intended thread offloading implementation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@charles-dyfis-net
Copy link
Copy Markdown
Author

Some contribution process notes:

  • Troubleshooting was AI-assisted: I presented Claude with a stack trace from production performance troubleshooting tools -- quoted from in Pydantic serialization invoked by async client can block main loop #1175 -- and interactively walked/talked through the relevant code with the model.
  • I did not run the pre-commit hooks because they're tied specifically to Python 3.10, and I don't keep an interpreter that old handy. I have run the pytest suite, and the only error was in unrelated code due to a version mismatch (my local qdrant server is somewhat behind).
  • A Netlify error installing qdrant_sphinx_theme in the "deploy preview" does not appear to be relevant to the changes at hand.

@charles-dyfis-net
Copy link
Copy Markdown
Author

Going to revise this slightly to use asyncio.to_thread() in place of run_in_executor.

AsyncPointsApi methods for `upsert_points`, `batch_update`, and `update_vectors` call `_build_for_*` which runs `jsonable_encoder` (-> `model_dump_json`) synchronously before the first `await`. This blocks the event loop for the entire duration of Pydantic serialization, which scales linearly with point count and vector dimensionality.

Wrap the sync `_build_for_*` call with `asyncio.to_thread` for these three methods, whose body size is unbounded. The `_build_for_*` methods return an unawaited coroutine object (from the async api_client.request), so the executor runs the serialization in a thread and we await the resulting coroutine back on the event loop.

Fixes qdrant#1175
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