Connectors / Streaming & queues

Apache Kafka

sink

Apache 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

FieldTypeDefaultDescription
brokers
required
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.
topic
required
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