Skip to content

fix(receive): resolve 503 errors during ingestor restarts#8720

Merged
GiedriusS merged 2 commits into
thanos-io:mainfrom
pvlltvk:fix/receive-503-during-ingestor-restart
Mar 19, 2026
Merged

fix(receive): resolve 503 errors during ingestor restarts#8720
GiedriusS merged 2 commits into
thanos-io:mainfrom
pvlltvk:fix/receive-503-during-ingestor-restart

Conversation

@pvlltvk

@pvlltvk pvlltvk commented Mar 15, 2026

Copy link
Copy Markdown
Contributor
  • I added CHANGELOG entry for this change.
  • Change is not relevant to the end user.

Changes

  • Make Dialer interface context-aware (DialContext) so TCP connections respect forwarding timeouts
  • Move sendWrites into a goroutine to unblock the response collection loop
  • Return codes.DeadlineExceeded instead of codes.Unavailable for context-originated failures in Cap'n Proto client
  • Replace WorkerPool.Go(Work) with Go(ctx, Work) error to support context cancellation on saturated pools

Closes #8710

Verification

I've tested it in our test environment with separate routers/ingestors, RF=2 and Cap'n Proto - and it looks good. However it certainly needs a thoughtful review.

- Make Dialer interface context-aware (DialContext) so TCP
  connections respect forwarding timeouts
- Move sendWrites into a goroutine to unblock the response
  collection loop
- Return codes.DeadlineExceeded instead of codes.Unavailable
  for context-originated failures in Cap'n Proto client
- Replace WorkerPool.Go(Work) with Go(ctx, Work) error to
  support context cancellation on saturated pools

Signed-off-by: Pavel Litvyak <[email protected]>
@pvlltvk
pvlltvk force-pushed the fix/receive-503-during-ingestor-restart branch from c1804c0 to 9ac9c18 Compare March 15, 2026 17:04
Comment thread pkg/receive/writecapnp/client.go Outdated
}
}

if ctx.Err() != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we use errors.Is(err, context.DeadlineExceeded) instead?

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.

Yep, good point! We certainly should use errors.Is here and seems like we even can handle context.DeadlineExceeded and context.Canceled separately - but I'm not sure if it's really important here.

Comment thread pkg/receive/handler.go Outdated
})
}); err != nil {
responseWriter <- newWriteResponse(
seriesIDs,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we also update attributes on the trace like above?

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.

Sure, I'll take a look how to add the properly in this case

Comment thread pkg/receive/handler.go
h.sendWrites(ctx, &wg, params, localWrites, remoteWrites, responses)

go func() {
h.sendWrites(ctx, &wg, params, localWrites, remoteWrites, responses)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we remove the sync.WaitGroup if we are now calling this in the same goroutine? At least I don't see a purpose for it anymore.

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.

I think that we can't remove it since sendRemoteWrite dispatches work via RemoteWriteAsync, so the actual writes complete asynchronously and we need to track when all those async callbacks finish, so we know when to close(responses)

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.

Actually this line is the source of a backpressure regression. sendWrites runs sendLocalWrite synchronously inline — moving it into a goroutine takes the local TSDB Append off the request's critical path. Once quorum can be satisfied from remote acks alone, the handler returns success while local Append is still pending, gRPC admits the next request, and wrapper goroutines accumulate without admission control. Lock contention on TSDB → slower local Append → more goroutines stacking up — a negative feedback loop. We correlated this against prod heap/CPU profiles.

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.

Hi @jnyi
I think your comment is relevant. I tested this PR in my own environment but mostly did it with a split routers/ingesters topology, seems like it's the reason why I didn't mention this behavior.
Am I right that you noticed this regression on a default mode?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm, I'm trying to think of a good solution here. Maybe we can just convert local writes into a worker architecture as well so that they would all look uniform? That way we wouldn't spawn more go routines if the local appends are lagging up to a certain extent.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Trying to address this in #8866. I feel like this change/refactor made the code also easier to understand since remote/local writes goes through the same infra. Let me know if you have any comments.

- Use errors.Is for context error checking in capnp client, mapping
  DeadlineExceeded and Canceled to appropriate gRPC codes
- Add unit tests for RemoteWrite gRPC error code mapping
- Add tracing span with error attributes when worker pool rejects task

Signed-off-by: Pavel Litvyak <[email protected]>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Mar 19, 2026
@GiedriusS
GiedriusS merged commit 96f8f8a into thanos-io:main Mar 19, 2026
19 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

receive: 503 errors during ingestor restarts

3 participants