Skip to content

feat(tracing): make span queue size configurable via max_queue_size#1752

Open
vismaytiwari wants to merge 1 commit into
langfuse:mainfrom
vismaytiwari:feat/configurable-max-queue-size
Open

feat(tracing): make span queue size configurable via max_queue_size#1752
vismaytiwari wants to merge 1 commit into
langfuse:mainfrom
vismaytiwari:feat/configurable-max-queue-size

Conversation

@vismaytiwari

@vismaytiwari vismaytiwari commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Refs langfuse/langfuse#12974

Under high concurrency the OpenTelemetry BatchSpanProcessor drops spans once its in-memory queue fills up. When the dropped span happens to be a parent, the trace ends up broken/incomplete — which is what #12974 describes ("the SDK's flush queue gets overwhelmed … some spans don't get flushed before the trace completes").

flush_at and flush_interval are already exposed as first-class Langfuse settings, but max_queue_size — the knob that actually governs how many spans can be buffered before drops start — wasn't. That left the queue pinned to OTEL's default of 2048 with no Langfuse-native way to raise it (you'd have to know about the underlying OTEL_BSP_MAX_QUEUE_SIZE env var).

This adds a max_queue_size client argument and a matching LANGFUSE_MAX_QUEUE_SIZE env var, threaded through the resource manager and span processor exactly like the existing flush settings. It defaults to None, so behavior is unchanged for existing users (OTEL still falls back to OTEL_BSP_MAX_QUEUE_SIZE / 2048). Users hitting drops under load can now raise the buffer without reaching into OTEL internals.

Note: this doesn't eliminate the bounded-queue drop behavior itself — it gives users a documented, first-class way to size the buffer for their concurrency, consistent with how flush_at/flush_interval already work.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactor
  • Documentation update
  • Tooling, CI, or repo maintenance

Verification

List the main commands you ran:

uv run --frozen pytest tests/unit/test_span_processor.py   # 5 passed (3 new: constructor arg, env var, unchanged 2048 default)
uv run --frozen pytest -n auto --dist worksteal tests/unit  # 636 passed (the test_prompt.py errors are pre-existing and credential-related; they reproduce on a clean tree)
uv run --frozen ruff check .                                # passed
uv run --frozen mypy langfuse --no-error-summary            # passed

Checklist

  • I self-reviewed the diff using code_review.md.
  • I added or updated tests for behavior changes.
  • I updated docs, examples, or .env.template if needed. (Added the client docstring + LANGFUSE_MAX_QUEUE_SIZE env-var reference; no .env.template change needed.)
  • I did not hand-edit generated files; if generated files changed, I used the upstream regeneration path.
  • I did not commit secrets or credentials.

Greptile Summary

This PR exposes max_queue_size as a first-class Langfuse setting (constructor argument + env var LANGFUSE_MAX_QUEUE_SIZE), giving users a documented way to raise the OTEL BatchSpanProcessor in-memory buffer beyond its 2 048-span default without reaching into OTEL internals. The change defaults to None so existing behavior is fully preserved.

  • Threads max_queue_size consistently through client.pyresource_manager.pyspan_processor.pyBatchSpanProcessor.__init__, mirroring the existing flush_at/flush_interval pattern.
  • Adds three unit tests covering the constructor argument, env-var override, and OTEL fallback default; all pass cleanly.

Confidence Score: 4/5

Safe to merge; all existing behavior is unchanged when max_queue_size is not set, and the new parameter is correctly propagated through every layer.

The change is mechanical and well-tested, following the existing flush_at/flush_interval pattern precisely. Two small gaps in span_processor.py mean a misconfigured value surfaces as a raw OTEL exception rather than a clear Langfuse message, but neither gap causes silent data loss.

langfuse/_client/span_processor.py — env-var parsing and the constraint check before the BatchSpanProcessor call.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Langfuse as Langfuse(client.py)
    participant RM as LangfuseResourceManager
    participant SP as LangfuseSpanProcessor
    participant BSP as BatchSpanProcessor(OTEL)
    participant Env as Environment Variables

    User->>Langfuse: "Langfuse(max_queue_size=N)"
    Langfuse->>RM: "LangfuseResourceManager(max_queue_size=N)"
    RM->>SP: "LangfuseSpanProcessor(max_queue_size=N)"
    SP->>Env: os.environ.get(LANGFUSE_MAX_QUEUE_SIZE)
    Env-->>SP: env value (if set, overrides None only)
    SP->>BSP: "super().__init__(max_queue_size=N or None)"
    BSP->>Env: os.environ.get(OTEL_BSP_MAX_QUEUE_SIZE)
    Env-->>BSP: fallback to 2048 if all None
    BSP-->>SP: initialized with effective queue size
    SP-->>RM: span processor ready
    RM-->>Langfuse: resource manager ready
    Langfuse-->>User: client initialized
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant Langfuse as Langfuse(client.py)
    participant RM as LangfuseResourceManager
    participant SP as LangfuseSpanProcessor
    participant BSP as BatchSpanProcessor(OTEL)
    participant Env as Environment Variables

    User->>Langfuse: "Langfuse(max_queue_size=N)"
    Langfuse->>RM: "LangfuseResourceManager(max_queue_size=N)"
    RM->>SP: "LangfuseSpanProcessor(max_queue_size=N)"
    SP->>Env: os.environ.get(LANGFUSE_MAX_QUEUE_SIZE)
    Env-->>SP: env value (if set, overrides None only)
    SP->>BSP: "super().__init__(max_queue_size=N or None)"
    BSP->>Env: os.environ.get(OTEL_BSP_MAX_QUEUE_SIZE)
    Env-->>BSP: fallback to 2048 if all None
    BSP-->>SP: initialized with effective queue size
    SP-->>RM: span processor ready
    RM-->>Langfuse: resource manager ready
    Langfuse-->>User: client initialized
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
langfuse/_client/span_processor.py:99-101
**No validation guard for invalid env-var value**

`int(env_max_queue_size)` raises an unhandled `ValueError` if `LANGFUSE_MAX_QUEUE_SIZE` is set to a non-integer (e.g. `"auto"` or `"4096.5"`). The same pattern exists for `flush_at` on line 93, so this is a pre-existing gap — but this PR is a good opportunity to add a friendlier parse-or-warn pattern. A failed `int()` here will surface as a raw Python exception deep in the constructor, with no hint that `LANGFUSE_MAX_QUEUE_SIZE` is the culprit.

### Issue 2 of 2
langfuse/_client/span_processor.py:140-148
**Undocumented constraint not validated before OTEL call**

The docstring states `max_queue_size` must be `>= flush_at`, but no check is performed before passing both values to `BatchSpanProcessor.__init__`. When the constraint is violated (e.g., `max_queue_size=100` with the default `flush_at=512`), OTEL raises a `ValueError` citing `max_export_batch_size` and `max_queue_size` — names internal to OTEL and not exposed in the Langfuse API, making the error hard to diagnose. An explicit guard here would produce a clear, Langfuse-native message before reaching the OTEL constructor.

Reviews (1): Last reviewed commit: "feat(tracing): make span queue size conf..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

The OpenTelemetry BatchSpanProcessor drops spans once its in-memory queue is
full, which under high concurrency can drop a parent span and leave incomplete
traces. flush_at and flush_interval were already exposed as first-class
settings, but the queue size — the knob that actually governs drops under load
— was not, leaving OTEL's default of 2048 with no Langfuse-native way to raise
it.

Add a max_queue_size client argument and LANGFUSE_MAX_QUEUE_SIZE env var,
threaded through the resource manager and span processor exactly like the
existing flush settings. Defaults to None, preserving current behavior (OTEL
falls back to OTEL_BSP_MAX_QUEUE_SIZE / 2048).

Refs langfuse/langfuse#12974

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Comment on lines +99 to +101
env_max_queue_size = os.environ.get(LANGFUSE_MAX_QUEUE_SIZE, None)
if max_queue_size is None and env_max_queue_size is not None:
max_queue_size = int(env_max_queue_size)

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.

P2 No validation guard for invalid env-var value

int(env_max_queue_size) raises an unhandled ValueError if LANGFUSE_MAX_QUEUE_SIZE is set to a non-integer (e.g. "auto" or "4096.5"). The same pattern exists for flush_at on line 93, so this is a pre-existing gap — but this PR is a good opportunity to add a friendlier parse-or-warn pattern. A failed int() here will surface as a raw Python exception deep in the constructor, with no hint that LANGFUSE_MAX_QUEUE_SIZE is the culprit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/_client/span_processor.py
Line: 99-101

Comment:
**No validation guard for invalid env-var value**

`int(env_max_queue_size)` raises an unhandled `ValueError` if `LANGFUSE_MAX_QUEUE_SIZE` is set to a non-integer (e.g. `"auto"` or `"4096.5"`). The same pattern exists for `flush_at` on line 93, so this is a pre-existing gap — but this PR is a good opportunity to add a friendlier parse-or-warn pattern. A failed `int()` here will surface as a raw Python exception deep in the constructor, with no hint that `LANGFUSE_MAX_QUEUE_SIZE` is the culprit.

How can I resolve this? If you propose a fix, please make it concise.

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 catch. I kept this consistent with the flush_at / flush_interval parsing right above it — same behavior — so the new option isn't a one-off special case. Hardening the env-var parsing (parse-or-warn with a clear message) is worth doing, but I'd rather do all three together in a small follow-up than change only this one here. Happy to open that if you'd like it.

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.

1 participant