Connectors / Streaming & queues
Apache Kafka
sinkApache Kafka producer with QueueFull retry and topic routing
Install
cargo install faucet-cli --features sink-kafka faucet init --sink kafka
# Cargo.toml — use it as a library cargo add faucet-sink-kafka
Starter config
sink:
type: kafka
config:
brokers: "" # REQUIRED — Comma-separated `host:port` bootstrap brokers, passed straight through as librdkafka's `bootstrap.servers` (e.g.
topic:
type: fixed # one of: fixed, from_path
name: "" # REQUIRED — Topic name.
# --- Alternative variants — replace the block above with one of these ---
# type: from_path
# path: "" # REQUIRED
# auth: { type: none } # one of: none, sasl_plain, sasl_scram, ssl, sasl_ssl, tls
# --- Alternative variants — replace the block above with one of these ---
# type: sasl_plain
# config:
# username: "" # REQUIRED
# password: "" # REQUIRED
#
# type: sasl_scram
# config:
# mechanism: null # REQUIRED
# username: "" # REQUIRED
# password: "" # REQUIRED
#
# type: ssl
# config:
# ca_path: "" # REQUIRED
# cert_path: "" # REQUIRED
# key_path: "" # REQUIRED
# key_password: ""
#
# type: sasl_ssl
# config:
# sasl: null # REQUIRED
# ssl: null # REQUIRED
#
# type: tls
# config:
# ca_path: ""
# value_format: { type: json } # one of: json, raw_string, bytes, confluent_avro, confluent_protobuf, confluent_json_schema
# --- Alternative variants — replace the block above with one of these ---
# type: raw_string
#
# type: bytes
#
# type: confluent_avro
# schema_registry:
# url: "" # REQUIRED
# auth: null
# cache_capacity: 1024
# request_timeout: 10
# bytes_encoding: null
#
# type: confluent_protobuf
# schema_registry:
# url: "" # REQUIRED
# auth: null
# cache_capacity: 1024
# request_timeout: 10
#
# type: confluent_json_schema
# schema_registry:
# url: "" # REQUIRED
# auth: null
# cache_capacity: 1024
# request_timeout: 10
# validate: false
# key_format: { type: json } # one of: json, raw_string, bytes, confluent_avro, confluent_protobuf, confluent_json_schema
# --- Alternative variants — replace the block above with one of these ---
# type: raw_string
#
# type: bytes
#
# type: confluent_avro
# schema_registry:
# url: "" # REQUIRED
# auth: null
# cache_capacity: 1024
# request_timeout: 10
# bytes_encoding: null
#
# type: confluent_protobuf
# schema_registry:
# url: "" # REQUIRED
# auth: null
# cache_capacity: 1024
# request_timeout: 10
#
# type: confluent_json_schema
# schema_registry:
# url: "" # REQUIRED
# auth: null
# cache_capacity: 1024
# request_timeout: 10
# validate: false
# value_schema: "" # Schema text (Avro `.avsc` JSON, Protobuf `.proto`, or JSON Schema) for the **value** when `value_format` is a Confluent Schema Registry format (`confluent_avro` / `confluent_protobuf` / `confluent_json_schema`).
# key_schema: "" # Schema text for the **key**, used when `key_format` is a Confluent Schema Registry format.
# key_path: "" # JSONPath to the field that becomes each message's key (the first match wins).
# partition_path: "" # JSONPath to an explicit destination partition for each message.
# headers_path: "" # JSONPath to a flat JSON object whose entries become Kafka message headers (values are stringified).
# on_key_error: null # What the sink does when key/partition extraction fails for a record.
# compression: "" # Producer-side compression for outbound batches. — one of: none, gzip, snappy, lz4, zstd
# acks: null # Broker acknowledgement level required before a send is considered delivered — librdkafka's `acks`.
# idempotent: true # Enable the librdkafka idempotent producer (`enable.idempotence`), which de-duplicates broker-side retries so a retried send cannot append the message twice.
# linger: 0 # How long the producer waits for more messages before sending a batch (librdkafka `linger.ms`).
# batch_size: 1000 # Maximum number of in-flight `FuturesUnordered` send futures during a single `Sink::write_batch` call.
# message_timeout: 30 # Delivery deadline for a single message (librdkafka `message.timeout.ms`), in **whole seconds**.
# max_in_flight: 100 # Hard ceiling on concurrent in-flight sends inside one `Sink::write_batch` call.
# queue_full_backoff: 0 # How long to wait before retrying a send that librdkafka rejected with `QueueFull`.
# queue_full_max_retries: 3 # How many times to retry a `QueueFull` send before failing the batch.
# exactly_once: null # Exactly-once knobs, grouped (#654 M20).
# transactional_id_prefix: "" # **Deprecated** — use `exactly_once.transactional_id_prefix`.
# commit_token_topic: "__faucet_commit_token" # **Deprecated** — use `exactly_once.commit_token_topic`.
# commit_token_topic_partitions: 1 # **Deprecated** — use `exactly_once.commit_token_topic_partitions`.
# commit_token_topic_replication: -1 # **Deprecated** — use `exactly_once.commit_token_topic_replication`.
# extra_client_config: {} # Raw librdkafka producer properties, applied **last** so they override everything this config derives (`acks`, `enable.idempotence`, `compression.type`, `linger.ms`, `message.timeout.ms`, `queue.buffering.max.messages`).
Capabilities
| Delivery | Exactly-once (atomic watermark) |
|---|---|
| Write modes | append |
| Exactly-once writes | Yes |
| Schema evolution | No |
| Scoped cleanup | No |
| Staged bulk load | No |
| Run rollback | No |
| Compression | Yes |
Config fields
| Field | Type | Default | Description |
|---|---|---|---|
brokersrequired | string | — | Comma-separated host:port bootstrap brokers, passed straight through as librdkafka's bootstrap.servers (e.g. "b1:9092,b2:9092"). Required; a blank value is rejected at config load. |
topicrequired | object | — | Destination topic. Either a fixed name ({ type: fixed, name: events }) or a per-record JSONPath lookup ({ type: from_path, path: "$.dest" }) for multi-topic routing. Required. |
auth | object | type: none | Broker authentication — SASL/PLAIN, SASL/SCRAM, SSL client certs, or SASL-over-SSL. Defaults to { type: none } (plaintext brokers only). |
value_format | object | type: json | Encoding applied to each record to produce the message **value**. Defaults to { type: json }. The Confluent Schema Registry formats (confluent_avro / confluent_protobuf / confluent_json_schema) additionally require Self::value_schema and the crate's schema-registry feature. |
key_format | object | — | Encoding applied to the value extracted at Self::key_path to produce the message **key**. When unset, the extracted value is used verbatim as UTF-8 key bytes (numbers and booleans are stringified) — set this only when the key needs a real encoder, e.g. a Schema Registry format. Ignored when key_path is unset (no key is sent). |
value_schema | string | — | Schema text (Avro .avsc JSON, Protobuf .proto, or JSON Schema) for the **value** when value_format is a Confluent Schema Registry format (confluent_avro / confluent_protobuf / confluent_json_schema). Registered under the {topic}-value subject on first use. Required for those formats; ignored otherwise. |
key_schema | string | — | Schema text for the **key**, used when key_format is a Confluent Schema Registry format. Registered under {topic}-key. Required for those key formats; ignored otherwise. |
key_path | string | — | JSONPath to the field that becomes each message's key (the first match wins). Unset — the default — produces keyless messages, so the broker partitions round-robin and log compaction cannot key on them. A path that resolves to nothing is handled per Self::on_key_error. |
partition_path | string | — | JSONPath to an explicit destination partition for each message. The match must be an integer in 0..=i32::MAX; anything else fails the record with FaucetError::Sink. Unset — the default — lets librdkafka pick the partition from the key (or round-robin when there is no key), which is almost always what you want. |
headers_path | string | — | JSONPath to a flat JSON object whose entries become Kafka message headers (values are stringified). Unset by default. |
on_key_error | object | fail | What to do when Self::key_path resolves to nothing for a record: fail (default) aborts the batch with FaucetError::Sink, skip drops the record (counted and warned once per batch), round_robin sends it with no key and lets the broker place it. Only consulted when key_path is set. |
compression | string | none | Producer-side batch compression, set as librdkafka's compression.type: none (default), gzip, snappy, lz4, or zstd. lz4 or zstd usually pays for itself on JSON payloads. |
acks | object | all | Broker acknowledgement level (librdkafka acks): all (default, every in-sync replica), leader (leader only), or none (fire-and-forget). Anything but all can lose data on a broker failure, and Self::idempotent requires all — that combination is rejected at config load. |
idempotent | boolean | true | Enable the librdkafka idempotent producer (enable.idempotence), which de-duplicates broker-side retries so a retried send cannot append the message twice. Defaults to true and requires acks: all. Independent of delivery: exactly_once, which adds a transactional producer plus a commit-token side-topic on top. |
linger | integer | 0 | How long the producer waits for more messages before sending a batch (librdkafka linger.ms). Raising it trades latency for larger, better-compressed batches. Defaults to 5 ms. |
batch_size | integer | 1000 | Maximum number of in-flight FuturesUnordered send futures during a single Sink::write_batch call. Defaults to DEFAULT_BATCH_SIZE. |
message_timeout | integer | 30 | Delivery deadline for a single message (librdkafka message.timeout.ms), in **whole seconds**. Defaults to 30 s. Also used as the timeout for flush(), for init_transactions, and as the floor for the exactly-once transaction.timeout.ms (which is raised to at least 60 s so a long message_timeout cannot make init_transactions reject the producer). |
max_in_flight | integer | 100 | Hard ceiling on concurrent in-flight sends inside one Sink::write_batch call. Defaults to 100 and must be at least 1. When Self::batch_size is non-zero the effective window is min(max_in_flight, batch_size), so this is the cap that applies under the batch_size = 0 sentinel. |
queue_full_backoff | integer | 0 | How long to wait before retrying a send that librdkafka rejected with QueueFull. Defaults to 100 ms. See Self::queue_full_max_retries. |
queue_full_max_retries | integer | 3 | How many times to retry a QueueFull send before failing the batch. Defaults to 3; 0 fails on the first QueueFull. QueueFull means the local producer buffer is saturated — raising queue.buffering.max.messages (via Self::extra_client_config) or lowering Self::batch_size treats the cause rather than the symptom. |
exactly_once | object | — | Exactly-once knobs, grouped (#654 M20). Only consulted under delivery: exactly_once; when present it supersedes the four deprecated flat keys below. |
transactional_id_prefix | string | — | **Deprecated** — use exactly_once.transactional_id_prefix. |
commit_token_topic | string | __faucet_commit_token | **Deprecated** — use exactly_once.commit_token_topic. |
commit_token_topic_partitions | integer | 1 | **Deprecated** — use exactly_once.commit_token_topic_partitions. |
commit_token_topic_replication | integer | -1 | **Deprecated** — use exactly_once.commit_token_topic_replication. |
extra_client_config | object | — | Raw librdkafka producer properties, applied **last** so they override everything this config derives (acks, enable.idempotence, compression.type, linger.ms, message.timeout.ms, queue.buffering.max.messages). Empty by default. |
Get started
Your first pipeline runs in five minutes.
Install the CLI, scaffold a config, and move real data — nothing external to stand up.
curl -LsSf https://github.com/faucet-hq/faucet-stream/releases/latest/download/faucet-cli-installer.sh | sh brew install faucet-hq/faucet-stream/faucet-cli