From the open engine’s docs (traza 0.28.0). View source Engine routes reach a Traza Cloud project through the gateway.
Dependencies
Traza’s standing budget is stated in CONTRIBUTING: no new dependency without a written justification covering why the standard library cannot reasonably do the job and what the dependency’s own footprint is. This file is where those justifications live, one section per decision, so that the absence of a dependency reads as a decision rather than an accident.
The count today: three direct dependencies in a default build — serde,
serde_json, and lz4_flex (argued in its own section below). The native
wire is JSON in both directions and so are the annotation, eval, tombstone
and manifest files; hand-writing serialization for every API type would be
more code, and more wrong code, than the dependency. Everything else — HTTP,
the OTLP protobuf decoder, the WAL, SHA-256, the digests, the Bloom filters —
is the standard library, on purpose.
The opt-in object-storage feature (preview) is the one deliberate
exception, argued in its own section below: it adds object_store (plus
tokio, futures-util, async-trait) behind a default-off feature, so a
default build compiles none of it and the standalone supply chain stays the
lockfile’s small core.
lz4_flex — segment and blob compression (accepted, ships in v0.24.0)
The job. Format v7 compresses the segment records region in 128 KiB record-aligned blocks and compresses payload blobs whole. The measured motivation is in the spec and in storage-comparison: the records region is where segment bytes go, and nothing in Traza compresses anything today.
Why the standard library cannot reasonably do this job. It contains no
compression — no DEFLATE, no LZ77 machinery, nothing to build on; this is
not a case of “std can do it awkwardly”. The std-only alternative is writing
a compressor and decompressor by hand. That was done for SHA-256, and the
comparison is instructive rather than encouraging: SHA-256 is about 160
lines of src/payload.rs against published FIPS test vectors, with no
performance requirement beyond “off the ingest path”. An LZ4 codec is a
decoder over
length-and-offset-encoded input where a bounds bug is a buffer overrun, in
the hottest read path the store has, competing against a decade of tuning.
Hand-rolling it is more unsafe surface, more maintenance, and more risk than
the dependency it would avoid — which is exactly the test the budget asks.
Footprint.
-
Pure Rust. The intended manifest line, exactly:
lz4_flex = { version = "=0.14.0", default-features = false, features = ["std", "safe-encode", "safe-decode"] }default-features = falseexists to drop the frame format (v7 does not use it), but it dropsstdand the safe implementations with it — which is why all three go back in by name. None of the three is optional in practice: withoutsafe-encode/safe-decodethe crate substitutes its unsafe fast paths, and choosing the safe ones is what keeps the crate compatible in spirit with this project’s#![forbid(unsafe_code)]— the forbid applies to Traza’s own crate, but choosing the safe implementation means not importing an unsafe one either. -
In that configuration it pulls no transitive dependencies — verified against
lz4_flex0.14.0, surveyed 2026-08, where every dependency is optional and none is activated by these features: direct dependencies go 2 → 3, and the lockfile grows by exactly the one package, 12 → 13. (The default feature set would add the frame format and a hash dependency; v7 needs neither.) One more cost, learned from the MSRV gate rather than the survey:lz4_flex0.14.0 requires rustc 1.81, so the crate’s floor moved from 1.75 to 1.81 with it — a two-year-old stable at adoption time, and the gate’s job is exactly to make that a recorded decision instead of a silent drift. -
Version pinned exact (
=inCargo.toml). Compressor output bytes are format bytes under the acceptance tests — see the determinism rule — so a routinecargo updatemust not be able to change what the encoder writes. Upgrading the pin is a deliberate re-baseline commit. The pin constrains the encoder only; any correct LZ4 decoder reads any valid stream. -
The dependency count is written down in more places than the two obvious ones — all correct before this dependency, all stale the day it landed. The v7 implementation settled every one (plus two stray “two dependencies” comments in
src/bin/ingest-bench.rsthe grep below caught):CONTRIBUTING.md— “two direct dependencies” in the PR expectations;README.md— “Two direct dependencies, twelve packages in the whole lockfile”;docs/README.md— “two dependencies” in the opening line;docs/storage-comparison.md— three places: “Two dependencies, nozstd”, “It would be a third dependency”, and “two direct dependencies” in the footprint bullet;docs/operations/durability.md— “carries two dependencies”, inside the macOS-fsync argument;src/wal.rs— the same argument, same phrase, in the module doc comment.
A list like this rots, so the instruction is the grep, not the list: the implementing PR runs
grep -rn 'two dependencies\|two direct\|twelve packages\|third dependency'and settles every hit. This document precedes the code by design.
Rejected.
zstd(the C binding). Better ratio — the projection that motivated compression, published in storage-comparison until94e7f86(the page now records where the savings came from), was computed with zstd, and on those inputsgzip -6measured 7–10% worse thanzstd -3, with LZ4 expected below both on ratio. But the crate chain (zstd→zstd-safe→zstd-sys) plus its build machinery (cc,jobserver, and on some platformspkg-config) adds roughly six lockfile packages at time of writing, puts a C compiler into every build of a project that currently builds withcargo buildand nothing else, and moves the decode path for untrusted disk bytes behind FFI — precisely the unsafe surface the safe-decode choice above exists to avoid. Ratio is the one axis it wins, and v7’s gates are stated in amplification and latency, not in matching zstd.ruzstd(pure-Rust zstd). Removes the C toolchain objection but not the fit objection: it is a decoder-first project whose encoder story is not what v7’s write path should stand on, and its real destiny here is different — see the cold-tier ruling below. Adopting it now would spend a dependency on the wrong tier.- DEFLATE (
miniz_oxide/flate2) and snappy. DEFLATE decodes slower than LZ4 and measured 7–10% worse thanzstd -3on ratio on the projection inputs — dominated on both axes for this workload. Snappy sits in LZ4’s speed class without beating it, and its Rust implementations bring no advantage that would justify choosing the less common format. Neither offers a ratio-speed point that LZ4 and zstd between them do not already cover.
Ruled while deciding this, so the absences are decisions:
- The zstd-with-dictionary cold tier is evidence-gated. The gate is a
real deployment where disk still binds after v7 ships and is measured.
Until that exists, no second codec, no dictionary training, no
ruzstd. v7’s codec-id parameterization is what makes saying yes later cheap: a new codec id and configuration, not a format bump. - Outbound TLS is not being added. No
rustls, no native-TLS binding, nothing. TLS belongs to the future native-S3 replication phase and gets its own entry here when that phase is real; compressing segments requires exactly zero network code. - The HA track neither gates nor is gated by this. Ship/follow/promote work proceeds independently of the v7 format, and nothing in this dependency decision waits on it.
object_store — the opt-in object-storage archive (accepted as a preview, feature-gated, ships in 0.26.0-preview.1)
This is the “gets its own entry when that phase is real” entry the TLS
ruling above promised. The object-storage feature publishes verified pins
to S3-compatible storage and queries them by ranged reads
(src/object_storage, operations guide).
The job. Speak S3: SigV4 request signing, TLS, HTTP with retries and timeouts, multipart upload, conditional create, ranged reads, listing.
Why the standard library cannot reasonably do this job. The same shape
as the LZ4 argument, one tier up: hand-rolling an HTTPS client plus SigV4
against a live cloud API is a security surface, not a codec, and every line
of it would be the least-reviewed TLS/signing code its users run.
object_store is the Rust ecosystem’s maintained, widely-deployed
implementation (Arrow/DataFusion lineage), and it owns exactly the layer
this feature must not: transport, TLS (verifying by default; plain HTTP only
by explicit opt-in), signing, retries.
Footprint, and why it is acceptable here and not in the core.
- Default builds compile none of it. All four direct additions
(
object_store— pinned exact,default-features = false,features = ["aws"]only — plustokiort/sync/time,futures-util, andasync-traitfor the injectable fault backend) areoptional = truebehind the default-offobject-storagefeature. The standalone binary’s dependency story is unchanged; the lockfile grows, the built artifact does not. - The transitive graph is real (an async HTTP/TLS stack), which is why
this ships as a feature, with its own toolchain floor recorded in
package.metadata.traza.object-storage-rust-versionrather than moving the crate’srust-version— the standalone MSRV stays 1.81. - Pinned exact (
=0.14.1): its wire behaviour is part of the archive’s security posture, so upgrading is a deliberate act, not a routine bump. - What is still not taken: no TLS or HTTP in the core engine or server,
no local-filesystem
object_storebackend (the engine’s own file I/O stays std), and no second copy of hashing — the archive’s digests are the crate’s own SHA-256.