feat(traces): span export queue with retries - #954
turnipdabeets wants to merge 2 commits into
Conversation
posthog-python Compliance ReportDate: 2026-09-15 22:09:28 UTC ✅ All Tests Passed!111/111 tests passed Capture_V1 Tests✅ 94/94 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
Important Files Changed
Prompt To Fix All With AI### Issue 1
posthog/tracing/_export.py:449-452
`timer.start()` can run the callback before `_flush_timer` points to the new timer. A zero-delay flush can then look stale and return. The old timer is canceled, and the dead timer is stored. Publish the replacement before starting it, and restore the old timer if `start()` fails.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (2): Last reviewed commit: "fix(traces): keep the scheduled flush wh..." | Re-trigger Greptile |
d51f793 to
d817b98
Compare
d817b98 to
57fabd6
Compare
57fabd6 to
2eb751c
Compare
2eb751c to
0a4baaf
Compare
dustinbyrne
left a comment
There was a problem hiding this comment.
Looks good for the export-queue slice, with one non-blocking note: _export.py:182 calls warn_if_due(force=True) after every flush, including automatic flushes. Repeated rejected batches can therefore emit a warning per flush rather than at most once per configured interval. Consider reserving forced reporting for shutdown/exit and updating the fixed-clock test that currently expects two warnings in one interval.
The transport and pipeline feedback belongs to their respective stack PRs; this approval does not cover the complete public tracing release.
AI-assisted review using source, tests and existing CI; no new tests were run.
Adds SpanExporter, the in-memory queue ended spans wait in until they are batched and sent, separate from the events queue. A timer flushes every flush_interval and a full batch flushes at once; only one flush runs at a time. A full queue drops the incoming span, never a queued parent. Failures back off exponentially with jitter, floored by Retry-After (clamped to 30 s, extended by a later deadline but never shortened), and automatic sends pause while backing off. A batch refused across 8 backoff windows is dropped, a 413 halves the batch and ramps back, and other 4xx drop it. flush(timeout) always sends the first batch, so a serverless handler with no budget left still ships spans. Not reachable from the client. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkZAsCciW4PV8ZdcCHmAbA
…to start, and count an unencodable span once The replacement timer now starts before the old one is cancelled, so a thread that cannot be created leaves the earlier flush in place. Records that fail to encode leave the queue before the send is settled, so a failed send no longer counts them a second time.
0a4baaf to
d7aa5d3
Compare
|
Thanks. We warn after every flush on purpose, to match Node. It calls |
dustinbyrne
left a comment
There was a problem hiding this comment.
Thanks, confirmed against Node: the flush path calls _warnAboutDrops(), with an explicit once-per-flush test. I withdraw my warning-cadence suggestion. The spec wording should be reconciled separately, not by changing Python alone. Approval stands.
AI-assisted follow-up source review, including Node parity and the rebased transport change; no new tests run locally.
💡 Motivation and Context
Adds
SpanExporter, the in-memory queue ended spans wait in until they are batched and sent, separate from the events queue.flush_interval; a full batch flushes at once; only one flush runs at a time.Retry-After(clamped to 30 s, extended by a later deadline but never shortened). Automatic sends pause while backing off.flush(timeout)always sends the first batch, so a serverless handler with no budget left still ships spans.Not reachable from the client yet.
Stack (PR 6 of 9, based on
traces/05-pipeline):traces/01-ids-traceparenttraces/02-otlp-encodingtraces/03-span-handlestraces/04-transporttraces/05-pipelinetraces/06-export← this PRtraces/07-span-limitstraces/08-before-span-sendtraces/09-client-wiring💚 How did you test it?
Unit tests in
posthog/test/tracing/test_export.pycover timer and size flushes, backoff andRetry-After, 413 halving, drop rules and the boundedflush(timeout).📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Claude Code (Claude Opus 5) against the traces spec, one commit per slice so each PR reviews on its own. Rebased onto main and opened as a stacked draft in a later Claude Code session (Claude Fable 5.1).
🤖 Generated with Claude Code
https://claude.ai/code/session_012o7CtHLfcypjmXL7g9ZGRC