fix(processor): dead-letter pubsub messages whose blob is persistently missing - #3041
fix(processor): dead-letter pubsub messages whose blob is persistently missing#3041dgellman wants to merge 3 commits into
Conversation
…y missing The ingestor previously retried blob NotFound responses indefinitely: process.Subscribe returned nil without acking, so NATS redelivered the same orphaned message forever, generating hundreds of thousands of GetObject errors per processor over weeks. After maxBlobNotFoundRetries (3) consecutive NotFound responses for a message, ack the pubsub message to stop redelivery. Prefer the broker's native delivery count (NATS JetStream NumDelivered) so the threshold is shared across replicas and survives processor restarts; fall back to an in-process per-key counter for backends that do not expose one. Adds a Prometheus counter guac_processor_blob_notfound_dropped_total (label processor_id) and structured warn logs so operators can alert on orphan accumulation. Other Read errors retain previous redelivery behavior. Signed-off-by: Daniel Gellman <dgellman8@gmail.com>
31d3007 to
248c530
Compare
|
This pull request has been automatically marked as stale because it has not had recent activity (60 days of inactivity). |
|
@dgellman Can you please rebase the branch with main |
|
Will work on it a bit later today - this one appears to have a confict that needs to be resolved now. |
|
rebased but the failing tests don't seem to be me =/ can we try re-running? |
|
The failing test is not blocking, it's just flaky. |
|
Reviewed the blob-NotFound retry/drop logic. Nothing here blocks merge — one item is worth fixing, one is a minor note. 1.
That if err := metricsCollector.AddCounter(ctx, blobNotFoundDroppedCounter, 1, uuidString); err != nil {
childLogger.Debugf("[processor: %s] AddCounter blob-notfound: %v", uuidString, err)
}2. Two distinct pubsub messages referencing the same missing blob share one attempt counter, so the second can hit the cap earlier than For the record, I also checked the ack semantics on the sub-cap NotFound path: returning |
|
Reviewed. See if these comments can be fixed @dgellman |
Summary
The processor previously retried blob
NotFoundresponses indefinitely.process.Subscribereturnednilfrom its handler without acking, so NATS redelivered the same orphaned message forever — generating hundreds of thousands ofGetObjecterrors per processor over weeks for blobs that no longer exist.After
maxBlobNotFoundRetries(3) consecutiveNotFoundresponses for a message, the message is now acked to stop redelivery. Other read errors retain their previous redelivery behavior.How retries are counted
When the underlying transport exposes a delivery count, that is preferred so the threshold is shared across replicas and survives processor restarts:
Metadata().NumDeliveredviagocloud.dev/pubsub.Message.As(&jetstream.Msg).mempubsubin tests): falls back to a per-key in-process counter.A Prometheus counter
guac_processor_blob_notfound_dropped_total(labelprocessor_id) is registered for alerting on orphan accumulation, and a structured warn log is emitted on each drop with the blob key and attempt count.What this does not address
Why orphan events get published in the first place (likely TTL mismatch between blob lifecycle and NATS retention, or out-of-band cleanup). That belongs in a separate investigation.
Test plan
Run on this branch:
go build ./...clean (exit 0)go vet ./...clean (exit 0)go test -race -timeout=60s ./... -count=1— 79 packages OK, 0 fail, 0 data races. Note: at the Makefile's default-timeout=30sunder race-detector parallelism, the unrelatedpkg/ingestor/parser/common/scannerpackage occasionally tips over (it runs ~25–30 s in isolation, with the same behaviour onmain); raising the timeout to 60 s clears it.golangci-lint run ./pkg/handler/processor/process/...— 0 issuesTestNotFoundTracker(per-key increment/clear isolation) andTestDeliveryCountSource(label values). ExistingTest_ProcessSubscribecontinues to pass unchanged.