Skip to content

oss_13_http_exporter_retry_after_backoff_cap #18

Closed

Description

@ocelotl

HTTP OTLP exporter: make 429 retryable, honor Retry-After, and cap retry backoff (Linear OSS-13, findings X2/X3).

Branch / worktree: oss_13_http_exporter_retry_after_backoff_cap

Linear issue: https://linear.app/dash0/issue/OSS-13/x2-x3-http-exporter-honor-429retry-after-add-max-backoff-cap

Activity

  1. ocelotl commented on Jul 22, 2026

    @ocelotl
    OwnerAuthor

    🔒 Internal (dash0) — not for upstream.

    Tracking for the OTLP HTTP exporter retry fix.

  2. ocelotl commented on Jul 22, 2026

    @ocelotl
    OwnerAuthor

    📣 Public-facing draft — to be opened upstream in open-telemetry/opentelemetry-python. No internal references; copy verbatim.

    Title: OTLP HTTP exporter ignores Retry-After, does not retry on 429, and has no backoff cap

    Description

    The OTLP HTTP exporters (trace, metric, logs) do not conform to the OTLP exporter spec's retry requirements in three ways.

    1. HTTP 429 Too Many Requests is not retried

    exporter/opentelemetry-exporter-otlp-proto-http/.../_common/__init__.py, _is_retryable() only retries 408 and 5xx:

    def _is_retryable(resp):
        if resp.status_code == 408:
            return True
        if resp.status_code >= 500 and resp.status_code <= 599:
            return True
        return False

    So 429 is treated as permanent and the batch is dropped. The OTLP spec (specification/protocol/exporter.md) requires retrying on 429; the gRPC sibling already honors throttling via the google.rpc.retryinfo-bin trailer.

    2. The Retry-After response header is never read

    The retry loops in trace_exporter/__init__.py, _log_exporter/__init__.py, and metric_exporter/__init__.py compute the delay solely from exponential backoff and never inspect Retry-After, which servers send with 429/503. The spec requires honoring it; Go and Java both parse it (delay-seconds and HTTP-date forms).

    3. The exponential backoff is uncapped

    Each exporter computes backoff = 2**retry_num * random.uniform(0.8, 1.2) with _MAX_RETRYS = 6, reaching ~32s with no clamp; the gRPC exporter has the same uncapped pattern and applies the server retry_delay with no ceiling. Go and Java cap the interval.

    Proposed fix

    • Add 429 to the retryable set in _is_retryable.
    • Add a Retry-After parser (delay-seconds integer and HTTP-date) and use its value as the next-retry delay across the trace/metric/log HTTP exporters.
    • Add a MAX_BACKOFF clamp (min(backoff, MAX_BACKOFF)) to the HTTP exporters and the gRPC exporter.
  3. ocelotl commented on Sep 9, 2026

    @ocelotl
    OwnerAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions