Repository navigation
Store a resent span once instead of rewriting it - #18
Merged
Merged
Conversation
An exporter that times out waiting for the acknowledgement sends the same export again. The store found each span already stored and deleted and rewrote its row, its attribute rows and its operation-index entry. On a full store under traffic one resent 1,000-span export took 16.6 s, so a collector already too slow to answer got slower with every resend. A span whose stored row already holds exactly the exported values is now skipped; a re-export that changes anything still replaces it.
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.
Problem
An exporter that times out waiting for the collector's acknowledgement sends the same export again; OTLP requires it, and Effect's exporter resends up to three more times. The store found each span already stored and deleted and rewrote its row, its attribute rows and its operation-index entry. At its size bound under traffic, the collector answers slowly, so exports time out and are resent, and every resend costs more than the original. On Executor's self-host beta.40, an instrumented copy of this collector stored one resent 1,000-span export in 16.6 s, 12.3 s of it deleting attribute rows. A collector that was already too slow got slower with each resend.
Fix
A span whose stored row already holds exactly the exported values is skipped: one indexed lookup instead of a delete and rewrite. A re-export that changes anything still replaces the row, as before.
Nothing else changes: not eviction, not batch sizes, not admission or routing. File worth reading:
src/services/TelemetryStore.ts,ingestTraces.Notes from review:
insertedSpans: it is stored, and the exporter's acknowledgement should say so.How it was tested
stores an identical resent export without rewriting its rows(telemetry.test.ts): onmainthe second export deletes and reinserts every attribute row; here the row ids are unchanged. The existingreplaces a re-exported span's operation in the search index oncestill passes.bun run test: 220 pass.refuses ingest beyond its queue and size boundsfailed once withECONNRESETon its oversized body and passed on rerun; it fails the same way onmain.Replay benchmark. The collector alone, with beta.40's workerd and bindings, on copies of one full 1 GiB store of product-shaped traces. The exporter behaves like the product's: 1 s interval, 1,000-span batches, 5 s timeout, three resends, 60 s pause after a lost batch. Each run is 4 minutes on a shared Linux host. There is one run per row,
mainthen this change, so neighbouring rows ran under similar load.mainmainRelation to other PRs
Follow-ups: the collector as shipped
Found while investigating Executor issue #2194; none is changed here.
Eviction holds the collector. At the bound under traffic, a batch of the 100 oldest traces took 1.6–4.8 s, and every export waited behind it.
Write volume. The collector writes about 0.26 MiB per stored span at the bound (WSL2 measurement). The causes:
idx_span_attributes_trace_spanduplicates the primary key's prefix;trace_id, so each eviction batch reads the whole index;On a shared disk this volume slows other processes' writes.
Storage amplification. Stored spans take about 5× their export JSON for product spans, and up to about 70× for an export whose resource attributes are shared by many spans (a 76,789-byte export stored about 5.5 MB).
Log orphan sweep. A pass's sweep is capped by attribute rows, not by deleted logs.
Pending exports. Executor's local CLI collector allows 16 pending exports of up to 16 MiB each; self-host allows 4.
Bound with nothing left to evict. FTS segment pages can keep a store over its bound when no rows remain to evict (details below).
A store over its bound with nothing left to evict (probe on
main, workerd, 4 MiB bound)./api/traces/<id>right after each export found nothing.