Skip to content

metrics: do not count postponed tasks as failed - #280

Open
den-dw wants to merge 2 commits into
clyso:mainfrom
den-dw:fix/metrics-postponed-tasks
Open

den-dw wants to merge 2 commits into
clyso:mainfrom
den-dw:fix/metrics-postponed-tasks

Conversation

@den-dw

@den-dw den-dw commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Problem

A task that hits a storage rate limit or can't get the object lock returns dom.ErrRateLimitExceeded and is retried after a short delay. The worker error handler treats this as a postponed task and logs it at debug level. WorkerMiddleware still counts it in worker_failed_tasks_total.

The most common case is lock contention between tasks of the same object. CompleteMultipartUpload (and PutObject with ACL or tagging headers) enqueues object:sync, object:sync:acl and object:sync:tags for the same object at once. All three take the same object lock, so some of them wait.

We replicate pgBackRest repositories, which are mostly multipart uploads. While a backup runs, worker_failed_tasks_total grows by up to ~490 per 15 minutes for object:sync:acl and object:sync:tags (ACL sync is disabled in our config), and the worker logs show no errors at all. An alert on this metric can't tell these delays from real failures.

Fix

WorkerMiddleware doesn't count dom.ErrRateLimitExceeded as a failed task. worker_processed_tasks_total is unchanged.

Testing

New pkg/metrics/worker_middleware_test.go: success, a regular error, and a wrapped ErrRateLimitExceeded ("lock not obtained"). On main the test fails: the postponed task is counted (expected: 1, actual: 2). With the change it passes.

if err != nil && !errors.As(err, &rlErr) {
failedCounter.WithLabelValues(queue, t.Type()).Inc()
}
processedCounter.WithLabelValues(queue, t.Type()).Inc()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should then ratelimited task not counted as processed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — a postponed task goes back to the queue and is processed later, so it shouldn't count as processed either. The middleware now skips both counters for dom.ErrRateLimitExceeded, and the test and the README metric descriptions cover it. I also rebased onto main; the CI failure was make proto-gen changing go.mod on the old base.

A task that hits a storage rate limit or cannot obtain the object lock
returns dom.ErrRateLimitExceeded and is retried after a short delay; the
worker error handler logs it at debug level, not as a failure. The worker
middleware still counted it in worker_failed_tasks_total.

CompleteMultipartUpload (and PutObject with ACL or tagging headers) enqueues
object sync, object ACL sync and object tags sync tasks of the same object
at once, and all of them take the same object lock, so some of them wait and
are counted as failed. A burst of uploads (e.g. a database backup) produces
hundreds of "failed" tasks per 15 minutes with no errors in the logs, which
makes the metric useless for alerting.

Signed-off-by: Denis Ivanov <dendw.work@gmail.com>
A postponed task returns to the queue and is processed later, so counting each postponed attempt inflated worker_processed_tasks_total as well. The middleware now skips both counters for dom.ErrRateLimitExceeded; the metric descriptions in the chart README say so.

Signed-off-by: Denis Ivanov <dendw.work@gmail.com>
@den-dw
den-dw force-pushed the fix/metrics-postponed-tasks branch from eeb6dc1 to fecf67d Compare October 27, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants