WarpSpanExporter
A CRDT-backed span exporter.
Spans are stored in an ORSet> keyed by SpanRecord.spanId: adding the same span twice is a set-union and therefore idempotent — a retry can never double-count. Reconnecting peers reconcile by sharing their ORSet deltas through the kuilt anti-entropy layer rather than replaying a queue, so only missing spans move over the wire.
Key inversion
export returns ExportResult.Success the moment the span is durably written to the DurableStore — not when it is delivered to any backend. Delivery is asynchronous and eventually consistent; the CRDT merge guarantees that any replica which receives the span will incorporate it correctly, even if the span arrives out of order or more than once.
Buffer cap
When the in-memory CRDT exceeds maxSpans, the oldest span (by SpanRecord.startEpochNanos) is evicted according to the bufferPolicy before the new span is inserted. Every eviction is logged with enough detail to correlate against a backend's orphan-span index.
Parameters
The ReplicaId for this device/process. Must be unique and stable across restarts (a UUID is recommended).
The DurableStore to persist CRDT state. Use InMemoryDurableStore in tests; wire a platform WAL (JVM file, IndexedDB, etc.) in production.
Maximum number of spans buffered in memory before eviction. Defaults to DEFAULT_MAX_SPANS.
What to do when maxSpans is exceeded. Defaults to BufferPolicy.DROP_OLDEST.
Samples
val replica = ReplicaId("device-uuid-abc123")
val store = InMemoryDurableStore()
val exporter = WarpSpanExporter(replica = replica, store = store)
// Recover persisted state from a previous session.
exporter.recover()
// Span ids are raw bytes (OTLP wire format): 16 bytes for trace id, 8 for span id.
val span = SpanRecord(
traceId = ByteString(ByteArray(16) { it.toByte() }),
spanId = ByteString(ByteArray(8) { it.toByte() }),
parentSpanId = null,
name = "checkout",
kind = SpanKind.CLIENT,
startEpochNanos = 1_000_000_000L,
endEpochNanos = 2_000_000_000L,
)
// Idempotent: exporting the same span twice is a no-op (ORSet union).
exporter.export(span)
val secondResult = exporter.export(span)
check(secondResult == ExportResult.Success)
check(exporter.snapshot().elements.size == 1) { "duplicate was stored" }Constructors
Functions
Drop every span this exporter holds and persist the emptied set (#2208).
Export one span: insert it into the CRDT and durably flush to store.
Read a snapshot of the current in-memory ORSet for gossip / anti-entropy.