Add sender queue that expires oldest first - #986
Draft
StephenWakely wants to merge 5 commits into
Draft
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This pull request adds a bounded sender queue that evicts oldest entries, expires stale metrics, and retries transient failures.
Changes:
- Introduces
SenderQueueandPendingPayload. - Integrates expiry, eviction telemetry, and retry handling.
- Adds unit tests and a performance benchmark.
File summaries
| File | Review summary |
|---|---|
tests/unit/dogstatsd/test_statsd.py |
One nit regarding a timing-dependent queue test. |
tests/performance/test_sender_queue_benchmark.py |
Reviewed with no final comments. |
datadog/dogstatsd/sender_queue.py |
One critical producer-notification issue and one moderate shutdown requeue issue. |
datadog/dogstatsd/base.py |
One critical shutdown issue and three moderate expiry, batching, and retry issues. |
Review details
Suppressed comments (2)
datadog/dogstatsd/base.py:1792
- Queue-mode retry only handles
sent is None, but_xmit_packet_attempt()returnsFalseforsocket.timeout;_get_uds_socket()can raise that exception when a UDS connect attempt times out. A connection timeout therefore falls through to the writer-drop path and permanently loses the queued payload instead of letting the sender queue retry it. Distinguish connect timeouts from send timeouts or propagate connect timeouts as transient failures in queue mode.
if sent is None and queue_mode:
# Connection trouble, and the caller is the background sender
# queue: let it requeue the payload and retry once reconnected,
# instead of dropping it here.
return None
datadog/dogstatsd/sender_queue.py:196
Stopis allowed past the capacity limit input(), butrequeue_front()counts that sentinel inlen(self._deque). During shutdown, if the sender has an in-flight payload andstop()appendsStop, a failed send sees the queue as full and drops the payload instead of requeueing it; this loses metrics that shutdown is supposed to drain. Coordinate sentinel insertion with in-flight work or make the requeue path ignore the shutdown sentinel when enforcing capacity.
if self._maxsize > 0 and len(self._deque) >= self._maxsize:
self._on_drop_queue_full(item)
self._finish_task_locked()
return
- Files reviewed: 4/4 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+2180
to
+2184
| if sent is None: | ||
| # Connection trouble: keep the payload for the next attempt | ||
| # instead of losing it. The queue's own expiry check (on a | ||
| # future get()) is what eventually gives up on a payload | ||
| # that's been stuck for too long, unless it's replay-safe. |
Contributor
Author
There was a problem hiding this comment.
This will be addressed in a followup PR that will place a timeout on these functions.
Comment on lines
+135
to
+137
| while self._deque and self._deque[0] is not Stop and self._expired(self._deque[0], now): | ||
| self._on_drop_expired(self._deque.popleft()) | ||
| self._finish_task_locked() |
Comment on lines
+1682
to
+1683
| enqueued_at = None if replay_safe else monotonic() | ||
| self._queue.put(PendingPayload(packet_with_newline, enqueued_at, replay_safe)) |
| # The flushed batch is only as replay-safe as its least safe | ||
| # member: if anything in it needs to be treated as time-sensitive, | ||
| # treat the whole batch that way. | ||
| self._buffer_replay_safe = self._buffer_replay_safe and replay_safe |
Comment on lines
2717
to
+2718
| statsd._send_to_server(metric_name) | ||
| statsd._send_to_server(metric_name + ".second") |
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.
Requirements for Contributing to this repository
What does this PR do?
Changes the sender queue from a simple
queue.Queueto a queue with the following properties:Description of the Change
With the recent addition of a socket connect timeout, metrics can be retried whilst facing network outages or agent downtimes. As it is retrying the sender queue can fill up, and eventually will overflow. When overflowing we want to make sure the most relevant metrics are kept.
Metrics sent without a timestamp have a limited time during which they are relevant. If we queue metrics for 2 minutes, and then send them all in one go, this would lead to 2 minutes worth of metrics being aggregated in a single time window, causing a huge spike. Metric expiry ensures this cannot happen.
Metrics sent with timestamps cannot expire as they are sent through to the Datadog backend as is.
Alternate Designs
Possible Drawbacks
There are performance implications with this change. Benchmarking:
During the restart the client queue grows to 218k. (Each item in the queue is a payload which can consist of more than one metric).

Memory usage is ~10mb more at the peak, settling down to approximately 26mb more afterwards - I presume due to greater memory fragmentation.
I don't get accurate cpu measurements during the agent downtime since the agent is down, but during typical usage CPU seems largely unaffected.
Verification Process
Additional Notes
I have some followup PRs coming:
wait_for_pendingandstopfunctions.Release Notes
Review checklist (to be filled by reviewers)
changelog/label attached. If applicable it should have thebackward-incompatiblelabel attached.do-not-merge/label attached.kind/andseverity/labels attached at least.