Add dedicated bulk embeddings pool for ingest - #2112
Conversation
Ingest embedding tasks and search queries currently share one embeddings microservice URL, so a scale-to-zero / cold-starting pool sized for batch ingest also serves latency-sensitive search queries (and vice versa). Add EMBEDDINGS_MICROSERVICE_URL_BULK (config/settings/base.py, defaults to EMBEDDINGS_MICROSERVICE_URL) and route only the ingest Celery tasks in opencontractserver/tasks/embeddings_task.py through it, via a new optional service_url_override parameter threaded into the four ingest leaves (_create_text_embedding, _create_embedding_for_annotation, _batch_embed_text_annotations, _apply_dual_embedding_strategy) plus _embed_relationship. The override reuses the embedder's existing call-time 'embeddings_microservice_url' kwarg, so the embedder client, base class, and all search resolvers are untouched. When the setting is absent the override is None and no kwarg is passed, leaving default behavior unchanged. Query call sites keep reading EMBEDDINGS_MICROSERVICE_URL (the always-warm pod), fully isolating search latency from batch ingest load. The multimodal image pool intentionally stays on its own URL (text-only bulk setting). Adds regression tests covering the override threading at both the helper and task-entry-point level.
ReviewWent through the diff (
One thing worth double-checking (not blocking)
EMBEDDINGS_MICROSERVICE_URL_BULK = env(
"EMBEDDINGS_MICROSERVICE_URL_BULK", default=EMBEDDINGS_MICROSERVICE_URL
)So A related, more speculative point: because the override is unconditionally forwarded to both the default-embedder pass and the corpus-specific-embedder pass in Minor nits
Changelog fragment, settings comment, and sample env files all look correct and follow the repo's conventions ( |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
CI on the initial commit failed two checks: - linter (mypy): the new _EmbedFunc Protocol required embed_func to accept a service_url_override keyword, which the pre-existing 3-arg embed_func closures in test_embeddings_task.py do not, producing 5 arg-type errors. Replace the Protocol with Callable[..., bool], which accepts both the legacy 3-arg callables and the optional 4th-kwarg call the dual strategy makes when a bulk URL is set. Also removes the ellipsis-default the code-quality bot flagged. - codecov/patch: the document, note, and relationship ingest entry points had no direct test exercising the override, leaving added lines uncovered. Add TestBulkEmbeddingsPoolRouting asserting the bulk URL threads through to the embedder for calculate_embedding_for_doc_text, calculate_embedding_for_note_text, and calculate_embeddings_for_relationship_batch, plus a no-bulk negative case. Also make EMBEDDINGS_MICROSERVICE_URL_BULK opt-in (default None instead of EMBEDDINGS_MICROSERVICE_URL): when unset, ingest stays on the embedder's configured URL and no override kwarg is threaded at all, so the 'byte-identical default behavior' claim is literally exercised and a corpus's differently-pooled embedder is never redirected unless bulk is explicitly configured. Documents the forwarding scope near _apply_dual_embedding_strategy. Docs, sample env files, and changelog updated to match. Moves an inline override_settings import to module scope.
The pytest CI check failed on one pre-existing test: TestCalculateEmbeddingsForRelationshipBatch::test_explicit_embedder_counts_outcomes. It patches _embed_relationship with a strict 3-arg side_effect (rel, embedder, embedder_path); threading service_url_override=... into that call raised TypeError, which the loop caught, miscounting every relationship as failed. Fix: in the explicit relationship path, forward service_url_override only when a bulk URL is configured (the same conditional-forwarding pattern already used for embed_extra in _apply_dual_embedding_strategy). When unset, _embed_relationship is called with its original three positional args, so the mocked side_effect matches and the test passes untouched. Also add direct unit tests for _service_url_override_kwargs (both branches) and _bulk_embeddings_service_url (set / unset) so the pure helpers have reliable coverage attribution independent of the task-level mocks.
|
Review: Dedicated bulk embeddings pool for ingest Overall this is a clean, well-scoped change. The mechanism (reusing the embedder's existing call-time What I checked
Minor notes (non-blocking)
Nice test discipline overall — particularly appreciated that the regression in |
|
This introduces separate configuration pathway besides our singletons... take another pass |
Summary
Ingest embedding tasks and search queries currently share a single embeddings
microservice URL (
EMBEDDINGS_MICROSERVICE_URL). That forces a compromise: apool sized for latency-sensitive search queries (needs to stay warm) also
absorbs the load of batch ingest (thousands of embeddings, happy to hit an
autoscaled / scale-to-zero pool), and vice versa.
This PR lets operators point ingest at a separate bulk pool while query call
sites stay on the always-warm pod, fully isolating search latency from ingest
load — with no change to the embedder client, base class, or any search
resolver.
Changes
New setting —
EMBEDDINGS_MICROSERVICE_URL_BULKinconfig/settings/base.py(next toEMBEDDINGS_MICROSERVICE_URL), defaultingto
EMBEDDINGS_MICROSERVICE_URLso single-pool deployments need no config.Query call sites are untouched and stay warm automatically.
Ingest routing — the Celery tasks in
opencontractserver/tasks/embeddings_task.pyresolve the bulk URL via a new_bulk_embeddings_service_url()helper and thread it through an optionalservice_url_overrideparameter added to the four ingest leaves —_create_text_embedding,_create_embedding_for_annotation,_batch_embed_text_annotations,_apply_dual_embedding_strategy— plus_embed_relationship. The override is translated into the embedder'sexisting call-time
embeddings_microservice_urlkwarg (read byMicroserviceEmbedder._get_service_config), so no embedder/base-class changeis required. Embedders that don't read that kwarg (hosted providers, the
multimodal image pool) simply ignore it.
Backwards compatible by construction — when the override is
None(the default, and any direct/legacy caller), no URL kwarg is passed and
behavior is byte-identical to before. The dual-embedding strategy only
forwards the keyword to
embed_funcwhen a bulk URL is set, so the originalthree-argument
embed_funccontract keeps working.Multimodal image pool left alone — the bulk setting is text-only; image
embedding keeps its own
CLIP_EMBEDDER_URL/QWEN_EMBEDDER_URLpool.Deployment note
Rebuild/redeploy the Django image so Celery workers pick up the new code and
setting. Set
EMBEDDINGS_MICROSERVICE_URL_BULKin the environment to pointingest at the bulk pool; leaving it unset keeps the current single-pool
behavior.
Tests
opencontractserver/tests/test_batch_embedding.pygains regression testsverifying:
_batch_embed_text_annotationsforwardsservice_url_overrideasembeddings_microservice_url, and passes no URL kwarg when unset.calculate_embeddings_for_annotation_batchtask readsEMBEDDINGS_MICROSERVICE_URL_BULK(viaoverride_settings) and routes ingestto it end-to-end.
Docs / changelog
docs/deployment/performance_tuning.md— new "Separate bulk embeddings poolfor ingest" section.
changelog.d/bulk-embeddings-pool.added.md.Generated by Claude Code