nextcloud / nextcloud/context_chat_backend

A batch waits for its slowest worker, so one slow source stalls indexing and blocks the next fetch

Open
#350 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement priority: normal
Dominant language
Python
Stars
27
Forks
24
Avg merge
3d 4h
Merged PRs (30d)
6

Description

Describe the Bug

A batch makes no progress until its slowest worker returns, so one slow source stalls the whole batch and no new queue items are fetched meanwhile.

files_indexing_thread submits the chunks and then walks the futures in submission order:

file_futures = [executor.submit(_load_sources, chunk) for chunk in file_chunks]
...
for i, future in enumerate(file_futures):
    LOGGER.debug('Waiting for file chunk %d/%d future to complete', i + 1, len(file_futures))
    files_result.update(future.result())

Results are reported and the queue rows deleted only after that loop, so nothing a fast worker produced is committed until the slowest one is done.

That is fine when workers finish in comparable times. It is expensive when they do not, and they often do not: in our runs the workers of a single batch finished anywhere between 1.4 s and 300 s. The 300 s ones were an embedding request that never got a response, retried up to three times against request_timeout.

How visible this is

Doubling doc_indexing_batch_size from 32 to 64 halved our throughput, from ~37 to ~15–20 documents/min. Each worker then carries twice as many sources, so the chance that a batch contains a slow one goes up and the tail it imposes doubles. We reverted it.

The default request_timeout of 1800 s compounds it: before we lowered it, a single unresponsive embedding request held a batch for ~35 minutes (3 attempts plus back-offs), during which all four of our backend instances sat idle. Lowering it to 30 s — healthy requests answer in about 6 s — was the single largest improvement we made, from ~5 to ~60–100 documents/min.

Suggested direction

Two things would help independently:

  • Consume the futures as they complete (concurrent.futures.as_completed) and report results incrementally, so a slow worker delays only its own sources rather than the batch and the next fetch.
  • Reconsider the default request_timeout. 1800 s is a long time to hold a worker for a live indexing pipeline; something in the tens of seconds, with the retry budget in mind, matches how the embedding services actually behave.

I have not sent a patch for the first one because changing the batch bookkeeping touches the result and deletion paths, and I would rather not guess at the invariants you want there. Happy to write it if you tell me which shape you prefer.

Setup Details

Nextcloud version: 34.0.3
context_chat / context_chat_backend: 5.4.0 / 5.4.1
Deployment: manual-install deploy daemon, four backend instances, external embedding service, PostgreSQL 18 with pgvector, ~1.5 M eligible files

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at files_indexing_thread and trace how _load_sources futures feed the result and queue-row deletion paths. Compare the current submission-order loop with completion-order handling, and review how request_timeout and retries affect the batch. Done means fast results are reported and committed without waiting for the slowest worker, while bookkeeping remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.