fix(indexjobs): deleting a catalog spec no longer strands index jobs in permanent Degraded#1017
Merged
Merged
Conversation
…in permanent Degraded Deleting an API catalog spec (or a whole catalog) now unwinds its queue residue: pending index jobs are canceled and open failures resolved as part of the delete, so the api_catalog index kind never reports Degraded for a unit that no longer exists. The worker gains a generalized source-gone contract: a LoadItems error wrapping the new indexjobs.ErrSourceGone sentinel resolves the unit (vectors cleared, job completed, prior failures resolved) instead of recording an open failure no later success could supersede. The exhausted-attempts path re-probes the source before pinning a terminal failure, covering a delete that lands mid-attempt, and the delete-side cancel runs under context.WithoutCancel so a client disconnect cannot strand the cleanup. Spec-missing errors from catalog.GetSpec now name the spec and catalog instead of surfacing the misleading bare "catalog: not found" sentinel. The enqueue/cancel queue hooks move from pkg/admin into catalogindex (EnqueueBestEffort / CancelBestEffort / CancelCatalogBestEffort), and admin's duplicate EmbedJobsStore interface is replaced by catalogindex.Store. The shared internal/testdb harness now waits for the mapped port in addition to the ready log line, and both new RealDB integration tests use it. Closes #998
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1017 +/- ##
==========================================
+ Coverage 89.23% 89.25% +0.01%
==========================================
Files 456 457 +1
Lines 50418 50487 +69
==========================================
+ Hits 44993 45061 +68
- Misses 3611 3612 +1
Partials 1814 1814 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Closes #998
Problem
Deleting an API catalog spec while its index job was queued or running orphaned that job:
LoadItemsfailed withcatalogSource: get spec: catalog: not found, the job landed in an open failure, and theapi_catalogindex kind reported Degraded in the portal permanently, even at 100% vectors indexed. The failure could never self-clear (no future job can carry the deleted spec's key), retention never removes open failures, and the only exit was a manual operator dismiss. The surfaced error also named the wrong object: the catalog was present and healthy the whole time; it was the spec that was gone.What this PR does
Deletes unwind their queue residue.
DELETE .../specs/{spec}cancels the spec's pending index jobs and resolves its open failures as part of the delete;DELETE /api-catalogs/{id}does the same for every spec the cascade removes, matched by the encodedsource_idprefix (starts_with, so a catalog id containing a LIKE metacharacter cannot over-match) with no pre-delete spec listing. Both cleanups are best-effort and run undercontext.WithoutCancel: the row delete has already committed, so a client disconnect cannot strand them.The framework gains a generalized source-gone contract. A
Source.LoadItemserror wrapping the newindexjobs.ErrSourceGonesentinel tells the worker the unit was deleted on purpose, not that loading failed: the worker clears the unit's vectors, completes the job, and resolves any open failures for the key, instead of recording a terminal failure nothing can ever supersede.catalogSourcemapscatalog.ErrNotFoundto the sentinel. This closes the same hole for any future source kind, not justapi_catalog; the five existing data consumers (memory, prompts, portal assets/collections/knowledge-pages) already return empty items for missing rows and were verified unaffected.The delete/worker race is closed. If a spec is deleted while its job's final attempt is mid-flight, the delete-side cancel finds nothing to drop (the job is running) and nothing to resolve (no failure exists yet), and the attempt's terminal failure would recreate the permanent residue. The worker now re-probes the source before pinning any exhausted-attempts failure and routes a gone unit to resolution. Earlier attempts need no probe: their retry re-enters
LoadItems, which surfaces the sentinel itself.Spec-missing errors name the spec.
catalog.GetSpec(Postgres and memory stores) returnsspec "A" not found in catalog "c1": ...wrappingErrNotFound, soerrors.Iscallers are unchanged and the message identifies what is actually missing.API surface changes
indexjobs: newErrSourceGonesentinel, newStore.CancelPending(key)(deletes a unit's pending rows; running rows are left to the worker's source-gone path), updatedSource.LoadItemscontract docs.catalogindex:StoregainsCancel(key)andCancelCatalog(catalogID); newEnqueueBestEffort/CancelBestEffort/CancelCatalogBestEfforthooks own the log-and-continue semantics the admin handler previously inlined.pkg/admin: theEmbedJobsStoreinterface is removed; it had become a verbatim duplicate ofcatalogindex.Store, whichDeps.EmbedJobsnow uses directly. The enqueue hook moved tocatalogindexwith the new cancel hooks, keepingpkg/adminwithin its package LOC budget.testdata/allowed_internal_imports.txt: one new edge,catalogindex -> internal/logsan, for sanitizing operator-supplied catalog/spec names in the hooks' log lines (the same sanitization the admin handler applied to these values before the move).Tests
indexjobsstore (TestCatalogSpecDelete_ClearsIndexResidue_RealDB): a pending job is gone after spec delete, an already-failed job's open failure is resolved by spec delete, and a catalog delete clears both residue shapes across its specs. Each subtest asserts zero pending/running rows, zero open failures, andUnresolvedFailures == 0for the kind.TestIndexJobs_SourceGoneSelfHeals_RealDB) runs the fully assembled queue (indexqueue.New: realcatalogSource, sink, worker, Postgres store) against an orphaned job whose spec was deleted without any handler-side cancel: the job completes as source-gone and its completion resolves the unit's earlier open failure.CancelPendingandCancelCatalogSQL, theAdminStorecancel translation, the best-effort hooks, and theErrSourceGonemapping boundary (a store read error must NOT be treated as gone).internal/testdbharness, whose wait strategy now requires the mapped port to be reachable in addition to the second "ready" log line; the log line alone raced under parallel container load and could failConnectionStringwithport "5432/tcp" not found.