Avro format
The Avro deserializer (etl-avro, feature avro on the etl facade)
decodes bare Avro datums as carried by Kafka messages into records — either
your own serde types or dynamically-typed values. Three framings are
supported; the framework handles the framing bytes so your schemas and
structs only ever describe the datum itself.
Construct it from the pipeline's opaque section, handing it the builder's I/O runtime handle (the registry fetcher lives there):
let deserializer =
AvroDeserializerBuilder::from_component(deser_section, &pipeline.io_handle())?
.build_serde::<Order>()?;
Configuration
The deserializer: { avro: ... } section deserializes into AvroSettings
(crates/etl-avro/src/config.rs); unknown fields are rejected.
deserializer:
avro:
mode: confluent # confluent | raw | single_object
registry: # required for confluent
url: ${SCHEMA_REGISTRY_URL}
username: ${SR_USER} # optional basic auth
password: ${SR_PASSWORD}
prewarm_subjects: [orders-value]
negative_cache_ttl: 30s
reader_schema: # optional: pin the resolved shape
path: /etc/etl/orders.avsc # (or `inline: '{"type": ...}'`)
# schema: { inline | path } # required for raw / single_object
| Key | Type | Default | Meaning |
|---|---|---|---|
mode | string | confluent | Payload framing: confluent, raw, or single_object. |
registry.url | string | — | Base URL of a Confluent-compatible schema registry. Required in confluent mode; rejected in the others. |
registry.username / registry.password | string | none | Optional basic auth (interpolate secrets via ${VAR}). |
prewarm_subjects | string list | [] | Subjects whose latest schemas are fetched at startup (confluent mode only). |
negative_cache_ttl | duration | 30s | How long a schema id the registry could not serve stays negatively cached before a refetch is allowed. |
reader_schema | { inline } or { path } | none | Optional reader schema pinning the resolved record shape (confluent and raw modes; not single_object). |
schema | { inline } or { path } | — | The writer schema. Required in raw and single_object modes; rejected in confluent mode (writer schemas come from the registry there). |
Mode requirements are validated at construction with explicit messages —
confluent without a registry, raw without a schema, prewarm_subjects
outside confluent, and so on, all fail fast at startup.
The three modes
confluent— Confluent wire format: each payload starts with a 5-byte header (magic byte0plus a 4-byte schema id) followed by the datum. The framework parses the header, resolves the id against the registry, and decodes; producers using Confluent-ecosystem serializers just work.raw— bare datums with a fixed schema you configure (inlineor a mounted.avscfile).single_object— Avro single-object encoding (marker plus Rabin schema fingerprint), verified against the configured schema's fingerprint.
Typed or dynamic records
The builder hands out two deserializer flavors (plus two more behind the opt-in fast backend):
build_serde::<T>()— decodes each datum into your ownT: serde::de::DeserializeOwnedstruct (field names match the writer schema). This is the flagship path — seecrates/etl/examples/kafka_avro_to_clickhouse.rs, where the same struct derivesDeserializefor Avro in andSerializefor RowBinary out.build_value()— decodes into dynamically-typedAvroValuerecords, for pipelines that inspect or route on structure not known at compile time.build_serde_fast::<T>()/build_fast::<F>()(featureavro-fast) — single-pass typed decode, several times faster, including borrowed (zero-copy) records; different evolution semantics. See the fast backend.
Schema fetching never blocks a pipeline thread
In confluent mode, registry schemas are fetched by an asynchronous task
on the I/O runtime and cached per process. When a payload references a
schema that is not cached yet, the deserializer does not wait: it reports
"not ready", the operator chain holds the batch (backpressure pauses the
source if needed), and the batch retries once the fetch lands. Records
are never dropped or duplicated by this mechanism, and the CPU-pinned
pipeline threads never perform I/O. Two knobs shape the edge cases:
prewarm_subjectsfetches your subjects' latest schemas at startup, so the first batch does not pay the miss.- Ids the registry cannot serve are negatively cached for
negative_cache_ttland then handled by the deserializer'sErrorPolicy(Skip or Fail) like any other poison payload — a permanent bad id cannot wedge the pipeline into an infinite retry.
Schema evolution
Writer schemas come from the payload (registry id or fingerprint) or
configuration; an optional reader_schema pins the shape records resolve
into, using Avro's standard schema-resolution rules — field reordering,
defaults for missing fields, type promotions, aliases. Pin a reader schema
when your struct must stay stable while producers evolve. Registry schemas
using schema references are not supported yet and surface as unavailable.
Backends
Decoding uses the apache-avro crate by default. An opt-in
serde_avro_fast backend — single-pass typed decoding, several times
faster, and the only one able to emit borrowed (zero-copy) records — ships
behind the avro-fast feature; the default build contains no trace of it.
Backends are chosen per pipeline by builder method, so both coexist in
one binary: keep the apache backend where you need real Avro reader-schema
resolution, use the fast one where decode throughput matters. Details,
trade-offs, and the license note: the fast backend.
Related
- The fast backend — opt-in single-pass decoding and zero-copy records.
- Kafka source — the payloads this format decodes.
- Error handling — Skip vs Fail for undecodable payloads.
- Backpressure — how a held batch interacts with flow control.