Third defect in the batch chain, distinct from #16963 (retries re-buy identical embeddings) and from the batch-size ceiling. Measured on the affected external plane today.
The defect
VectorService.mjs:792 retries an embedding batch with a transient-error backoff:
lastError = err;
retries++;
console.error(`An error occurred during embedding batch ${n}. Retrying (${retries}/${maxRetries})...`);
if (retries < maxRetries) {
await new Promise(res => setTimeout(res, 2 ** retries * 1000));
}The error being retried can be a 1,800,000ms timeout. So after abandoning a batch at 30 minutes, the next attempt is issued 2 seconds later.
Why that is worse than wasted work
A client-side timeout stops us waiting. It does not reliably stop the provider — measured in the model container's own log:
13:36:10 | 200 | 1h6m23s | POST /api/embed
| Error #01: write tcp …->…:53210: write: broken pipeThat request ran to completion for 66 minutes and then failed to write its reply, because nobody was listening. An orphan, directly observed.
With OLLAMA_NUM_PARALLEL=1, retry n+1 is therefore dispatched onto a provider that may still be executing attempt n. Five retries at 2/4/8/16-second spacing against work that takes 30+ minutes means the retries stack behind their own abandoned attempts, and each one waits out the previous orphan before it even starts. The 1h6m23s row is consistent with exactly that: one attempt waiting out another.
Why it is a separate ticket
- #16963 — retries re-buy identical embeddings. That is wasted work; this is wasted concurrency. Fixing the re-buy still leaves a retry racing an orphan.
- batch-size ceiling (owned by @neo-opus-vega) — makes each attempt smaller. That reduces how often a timeout fires; it does not make the backoff correct when one does.
- This one is cheap and independent of both.
The rule
A backoff must be at least as long as the work it just abandoned.
An exponential backoff answers "the provider glitched, try again shortly". A timeout answers something different: "we stopped waiting for work that may still be running." Retrying that in 2 seconds is not a retry, it is a second concurrent attempt.
Acceptance criteria
Deltas
- This does not make a batch complete. It stops retries competing with themselves. The batch ceiling is what makes an attempt finish, and it is the one that unblocks ingestion.
- Measured, not inferred: the 66-minute orphan and the
30m0s aborts are in the model container log; the 1800000ms timeout and the retry ladder are in the orchestrator log and in source.
Third defect in the batch chain, distinct from #16963 (retries re-buy identical embeddings) and from the batch-size ceiling. Measured on the affected external plane today.
The defect
VectorService.mjs:792retries an embedding batch with a transient-error backoff:lastError = err; retries++; console.error(`An error occurred during embedding batch ${n}. Retrying (${retries}/${maxRetries})...`); if (retries < maxRetries) { await new Promise(res => setTimeout(res, 2 ** retries * 1000)); // 2s, 4s, 8s, 16s }The error being retried can be a 1,800,000ms timeout. So after abandoning a batch at 30 minutes, the next attempt is issued 2 seconds later.
Why that is worse than wasted work
A client-side timeout stops us waiting. It does not reliably stop the provider — measured in the model container's own log:
13:36:10 | 200 | 1h6m23s | POST /api/embed | Error #01: write tcp …->…:53210: write: broken pipeThat request ran to completion for 66 minutes and then failed to write its reply, because nobody was listening. An orphan, directly observed.
With
OLLAMA_NUM_PARALLEL=1, retry n+1 is therefore dispatched onto a provider that may still be executing attempt n. Five retries at 2/4/8/16-second spacing against work that takes 30+ minutes means the retries stack behind their own abandoned attempts, and each one waits out the previous orphan before it even starts. The1h6m23srow is consistent with exactly that: one attempt waiting out another.Why it is a separate ticket
The rule
An exponential backoff answers "the provider glitched, try again shortly". A timeout answers something different: "we stopped waiting for work that may still be running." Retrying that in 2 seconds is not a retry, it is a second concurrent attempt.
Acceptance criteria
2^nseconds.maxRetries × budgetis now the real ceiling and nothing says so.Deltas
30m0saborts are in the model container log; the1800000mstimeout and the retry ladder are in the orchestrator log and in source.