You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
df_engine: all pipeline cores die under real OTLP load (boo panic in metrics record encoder + arrow DictionaryKeyOverflowError), process stays alive and reports healthy #3561
I searched existing issues and didn't find a duplicate
Component(s)
Rust OTAP dataflow (rust/otap-dataflow/)
Bug Description
Under sustained real-world OTLP traffic, df_engine panics on every pipeline core and
the engine never recovers. The process stays alive and keeps accepting connections while
processing nothing, so orchestration sees a healthy pod.
Two distinct panic sites fired in one 30-second window:
crates/pdata/src/encode/record/metrics.rs:266 — panic!("boo {lens:?}") in the
metrics record encoder's column-length check(). This appears to be already known:
draft PR fix: stop metrics otap encode panic #2984 ("fix: stop metrics otap encode panic") root-causes it as a shared
protobuf-cursor misuse producing inconsistent column row counts. That PR is open,
draft and unmerged, so the fix is not in main as of the commit above. Reporting it
here only as an independent real-world reproduction — a data point for fix: stop metrics otap encode panic #2984, not a
new bug.
arrow-data-58.3.0/src/transform/mod.rs:680 — MutableArrayData::new is infallible: DictionaryKeyOverflowError. I could not find any existing issue for this, and it
was the majority failure — 3 of 4 cores. It is not obviously metrics-specific; MutableArrayData::new is the generic array-merge path, so ordinary high-cardinality
span or log attributes look sufficient to trigger it.
The most operationally serious part is neither panic but the aftermath: a core that dies
is never restarted and the failure is invisible to every process-level health signal.
Steps to Reproduce
Build otap-df at 7502e7d (submodules recursive,
upstream Dockerfile unmodified): cargo build --locked --release --target=x86_64-unknown-linux-gnu
with RUSTFLAGS=-C target-cpu=x86-64-v2; runtime gcr.io/distroless/cc-debian13:nonroot.
Run it with 4 pipeline cores and the config in the Configuration field — one shared
chain for all three signals, so a metrics panic takes traces and logs with it.
Point sustained OTLP traffic at it. I used the opentelemetry-demo Helm chart
0.40.10 (appVersion 2.2.0, ~20 services) with every service's OTEL_EXPORTER_OTLP_ENDPOINT set to the engine, plus a service mesh emitting
access-log spans and a second demo application ( Google Micro service demo). Ordinary smoke-test volume was
enough — we never reached our intended load level.
Watch the engine log for panicked at and watch the pod's readiness.
Deterministic: reproduced on two independent pods. Time-to-death shortens with
backlog, because upstream SDKs queue telemetry and retry it as a burst — run 1 survived
3m08s, run 2 survived ~48s. That rules out periodic restarts as a mitigation, since each
restart meets a larger backlog than the last.
Expected Behavior
Encoding a batch of ordinary OTLP telemetry should not panic.
If a pipeline core does panic, pipeline_runtime_failed should be recoverable —
restart the core or start a new generation — rather than terminal.
An engine whose cores have all died should FAIL its health/readiness surface. Reporting Running/Ready while processing nothing is the failure mode that costs downstream
users the most.
On (3) there may be an easy win: the admin server already has the data. GET /api/v1/metrics?format=json&keep_all_zeroes=true returns the internal metric set per
node per core, so a dead core shows as its receiver.otlp / processor.* counters
going flat while sibling cores advance — no log grepping needed.
Actual Behavior
Each panic kills its pipeline core and logs pipeline_runtime_failed. There is no
restart, no new generation, no recovery. Once all four cores are down:
the process is still running and still accepting connections,
Kubernetes reports the pod Running, Ready, restarts=0,
throughput counters keep their last healthy cumulative values, so a dashboard built on
cumulative counters shows no drop.
I nearly banked a two-hour benchmark run on an engine that had been dead for six
minutes, because every liveness signal derived from the process was green. The only
thing that caught it was grepping the log for panic.
Load: opentelemetry-demo chart 0.40.10 (appVersion 2.2.0), ~20 services, traces +
logs + metrics into one pipeline, plus mesh access-log spans and a second application
Backend: an OTLP/HTTP endpoint that returns 400 on cumulative Sum/Histogram/Summary
(relevant only as corroboration that the metrics payload was independently judged
malformed at the same moment the encoder panicked)
thread 'pipeline-default-main-core-3-gen-0' (14) panicked at crates/pdata/src/encode/record/metrics.rs:266:13:
boo [21, 21, 21, 7, 21, 21, 21, 21, 21, 21]
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
2026-07-23T10:53:34.869Z ERROR otap-df-controller::controller.pipeline_runtime_failed: Pipeline terminated with a runtime error [core_id=3]
thread 'pipeline-default-main-core-1-gen-0' (12) panicked at /usr/local/cargo/registry/src/index.crates.io-.../arrow-data-58.3.0/src/transform/mod.rs:680:31:
MutableArrayData::new is infallible: DictionaryKeyOverflowError
2026-07-23T10:53:52.252Z ERROR otap-df-controller::controller.pipeline_runtime_failed: Pipeline terminated with a runtime error [core_id=1] entity/pipeline.attrs: pipeline.id=main pipeline.group.id=default deployment.generation=0 core.id=1 numa.node.id=0
thread 'pipeline-default-main-core-0-gen-0' (11) panicked at /usr/local/cargo/registry/src/index.crates.io-.../arrow-data-58.3.0/src/transform/mod.rs:680:31:
MutableArrayData::new is infallible: DictionaryKeyOverflowError
2026-07-23T10:53:58.263Z ERROR otap-df-controller::controller.pipeline_runtime_failed: Pipeline terminated with a runtime error [core_id=0]
thread 'pipeline-default-main-core-2-gen-0' (13) panicked at /usr/local/cargo/registry/src/index.crates.io-.../arrow-data-58.3.0/src/transform/mod.rs:680:31:
MutableArrayData::new is infallible: DictionaryKeyOverflowError
2026-07-23T10:53:59.582Z ERROR otap-df-controller::controller.pipeline_runtime_failed: Pipeline terminated with a runtime error [core_id=2]
# after this point: process alive, pod Ready, restarts=0, zero records processed
Additional Context
Relationship to existing issues (from the pre-filing search):
Panic site 1 looks like the bug draft PR fix: stop metrics otap encode panic #2984 already fixes. The boo string is a
column-length consistency check() and the reported lengths [21, 21, 21, 7, 21, ...]
show exactly the one-column row-count mismatch that PR describes. Treat this report as
corroboration that it still reproduces on main and does so under ordinary load.
I found nothing for DictionaryKeyOverflowError / MutableArrayData in this engine.
Workaround I adopted, in case it helps others: insert processor:type_router
immediately after the receiver and terminate the metrics port in exporter:noop, so the
metrics encoder is never reached:
With that plus max_batch_duration reduced 3s -> 1s, the same workload ran 41 minutes
with zero panics where it previously died in 30 seconds. ⚠️ I changed two things at
once and cannot attribute the improvement: cutting the batch window also reduces how many
records merge into each array, which plausibly suppresses the dictionary overflow on its
own. So this is not evidence that excluding metrics is sufficient — panic site 2 was
never addressed and we believe it can still fire on high-cardinality traces or logs.
Separately, --validate-and-exit accepts configurations that cannot work at runtime.
Happy to split these out if you'd prefer separate issues:
processor:filter accepts config: {__bogus__: 1} and prints Configuration is valid.
processor:type_router with config: {} and no outputs: declared at all validates
clean, although nothing would be routed by name.
the KQL processor:transform validates predicates it cannot evaluate at runtime — contains, ==, matches regex and replace_regex all fail at runtime against a
config that validates clean.
Pre-filing checklist
Component(s)
Rust OTAP dataflow (rust/otap-dataflow/)
Bug Description
Under sustained real-world OTLP traffic,
df_enginepanics on every pipeline core andthe engine never recovers. The process stays alive and keeps accepting connections while
processing nothing, so orchestration sees a healthy pod.
Two distinct panic sites fired in one 30-second window:
crates/pdata/src/encode/record/metrics.rs:266—panic!("boo {lens:?}")in themetrics record encoder's column-length
check(). This appears to be already known:draft PR fix: stop metrics otap encode panic #2984 ("fix: stop metrics otap encode panic") root-causes it as a shared
protobuf-cursor misuse producing inconsistent column row counts. That PR is open,
draft and unmerged, so the fix is not in main as of the commit above. Reporting it
here only as an independent real-world reproduction — a data point for fix: stop metrics otap encode panic #2984, not a
new bug.
arrow-data-58.3.0/src/transform/mod.rs:680—MutableArrayData::new is infallible: DictionaryKeyOverflowError. I could not find any existing issue for this, and itwas the majority failure — 3 of 4 cores. It is not obviously metrics-specific;
MutableArrayData::newis the generic array-merge path, so ordinary high-cardinalityspan or log attributes look sufficient to trigger it.
The most operationally serious part is neither panic but the aftermath: a core that dies
is never restarted and the failure is invisible to every process-level health signal.
Steps to Reproduce
otap-dfat 7502e7d (submodules recursive,upstream Dockerfile unmodified):
cargo build --locked --release --target=x86_64-unknown-linux-gnuwith
RUSTFLAGS=-C target-cpu=x86-64-v2; runtimegcr.io/distroless/cc-debian13:nonroot.chain for all three signals, so a metrics panic takes traces and logs with it.
opentelemetry-demoHelm chart0.40.10 (appVersion 2.2.0, ~20 services) with every service's
OTEL_EXPORTER_OTLP_ENDPOINTset to the engine, plus a service mesh emittingaccess-log spans and a second demo application ( Google Micro service demo). Ordinary smoke-test volume was
enough — we never reached our intended load level.
panicked atand watch the pod's readiness.Deterministic: reproduced on two independent pods. Time-to-death shortens with
backlog, because upstream SDKs queue telemetry and retry it as a burst — run 1 survived
3m08s, run 2 survived ~48s. That rules out periodic restarts as a mitigation, since each
restart meets a larger backlog than the last.
Expected Behavior
pipeline_runtime_failedshould be recoverable —restart the core or start a new generation — rather than terminal.
Running/Readywhile processing nothing is the failure mode that costs downstreamusers the most.
On (3) there may be an easy win: the admin server already has the data.
GET /api/v1/metrics?format=json&keep_all_zeroes=truereturns the internal metric set pernode per core, so a dead core shows as its
receiver.otlp/processor.*countersgoing flat while sibling cores advance — no log grepping needed.
Actual Behavior
Each panic kills its pipeline core and logs
pipeline_runtime_failed. There is norestart, no new generation, no recovery. Once all four cores are down:
Running,Ready,restarts=0,cumulative counters shows no drop.
I nearly banked a two-hour benchmark run on an engine that had been dead for six
minutes, because every liveness signal derived from the process was green. The only
thing that caught it was grepping the log for
panic.OTel-Arrow Version
7502e7d (main, 2026-07-20)
Environment
gcr.io/distroless/cc-debian13:nonrootcargo build --locked --release --target=x86_64-unknown-linux-gnu,RUSTFLAGS=-C target-cpu=x86-64-v2arrow-data58.3.0opentelemetry-demochart 0.40.10 (appVersion 2.2.0), ~20 services, traces +logs + metrics into one pipeline, plus mesh access-log spans and a second application
(relevant only as corroboration that the metrics payload was independently judged
malformed at the same moment the encoder panicked)
Configuration
Log Output
Additional Context
Relationship to existing issues (from the pre-filing search):
boostring is acolumn-length consistency
check()and the reported lengths[21, 21, 21, 7, 21, ...]show exactly the one-column row-count mismatch that PR describes. Treat this report as
corroboration that it still reproduces on main and does so under ordinary load.
engine in run_forever") is the same theme as our recovery complaint but a different
path — that one is a controller extension, ours is a pipeline core. Whatever policy
comes out of Controller extension panic bypasses fail-fast shutdown and wedges the engine in run_forever #3401 would ideally cover both.
DictionaryKeyOverflowError/MutableArrayDatain this engine.Workaround I adopted, in case it helps others: insert
processor:type_routerimmediately after the receiver and terminate the metrics port in
exporter:noop, so themetrics encoder is never reached:
With that plus⚠️ I changed two things at
max_batch_durationreduced 3s -> 1s, the same workload ran 41 minuteswith zero panics where it previously died in 30 seconds.
once and cannot attribute the improvement: cutting the batch window also reduces how many
records merge into each array, which plausibly suppresses the dictionary overflow on its
own. So this is not evidence that excluding metrics is sufficient — panic site 2 was
never addressed and we believe it can still fire on high-cardinality traces or logs.
Separately,
--validate-and-exitaccepts configurations that cannot work at runtime.Happy to split these out if you'd prefer separate issues:
processor:filteracceptsconfig: {__bogus__: 1}and printsConfiguration is valid.processor:type_routerwithconfig: {}and nooutputs:declared at all validatesclean, although nothing would be routed by name.
processor:transformvalidates predicates it cannot evaluate at runtime —contains,==,matches regexandreplace_regexall fail at runtime against aconfig that validates clean.