Skip to content

Input sources

A Drakkar worker reads work from Kafka, from HTTP, or from both. Each input is a source, configured under sources: in the worker config. Both sources are off by default and at least one must be on, so every worker states plainly where its work comes from. Everything downstream of a source — the handler, the executor pool, the sinks, the DLQ, the operator UI — is the same in all three modes.

Field-by-field settings live in Configuration → Input sources. This page explains how the two sources differ and what changes when one of them is off.

The two sources side by side

Kafka source HTTP source
Config block sources.kafka sources.http
What starts A consumer group member, the poll loop, and one processor per assigned partition One POST route on a server thread, bound to host:port
Readiness signal The first poll batch returned The socket is listening
What signal_stop does Stops the poll loop and signals every partition processor Closes the gate: new requests get 503 status=shutdown
Drain Waits for queued and in-flight work on live partitions Waits for in-flight requests
Stop Commits final offsets when the drain was clean, stops processors, closes the consumer Stops the server thread and joins it
Handler hooks required arrange() arrange_http_request() and on_http_request_complete(), plus the 3rd/4th generic types
Partition and offset The real Kafka partition and offset partition = -1 and a per-worker monotone offset, so HTTP work stays out of the partition views
Delivery guarantee At-least-once: offsets commit on a per-partition watermark One HTTP round trip. Nothing is redelivered; the client owns the retry

Sinks behave the same for both. A Kafka sink works on an HTTP-only worker, because the cluster connection in kafka: is separate from the Kafka source.

Choosing a mode

Kafka only

The classic pipeline. Messages arrive on a topic, arrange() turns each one into tasks, and offsets commit as the work completes.

kafka:
  brokers: kafka:9092

sources:
  kafka:
    enabled: true
    topic: search-requests
    consumer_group: search-workers

The handler needs arrange(). The HTTP hooks are never called, so a Kafka-only handler may leave them unimplemented.

HTTP only

A worker with no consumer group. Callers POST a request and wait for the result in the response. The same handler code builds the tasks, and results can still go to sinks.

cluster_name: "http cluster"

kafka:
  brokers: "kafka:9092"          # still needed: the sink below is Kafka

sources:
  kafka:
    enabled: false
  http:
    enabled: true
    host: "0.0.0.0"
    port: 8092
    path: "/process"
    sinks_enabled: true
    request_timeout_seconds: 60.0
    max_concurrent: 16
    clients:
      - name: mirror
        token: "a-long-random-token"
        rpm: 600

sinks:
  kafka:
    mirrored_results:
      topic: "search-results-http"

dlq:
  topic: ""                      # Kafka source off + empty topic = DLQ off

The handler needs the HTTP hooks. It may leave arrange() unimplemented. Read What changes in observability before you deploy one: several Kafka-shaped fields report empty values.

Both

One worker consumes a topic and serves HTTP at the same time. The two sources share the executor pool, the sinks and the cache, so an HTTP request competes with Kafka work for executor slots. Size executor.max_executors for the sum, and use sources.http.max_concurrent to cap what the HTTP side can claim.

sources:
  kafka:
    enabled: true
    topic: search-requests
    consumer_group: search-workers
  http:
    enabled: true
    port: 8090

The handler needs arrange() and the HTTP hooks. This is the mode the Webapp page describes in detail.

Readiness and shutdown

Readiness is composed. The worker reports ready on /readyz when every enabled source is ready and the sinks are connected. One source that is not ready holds the whole worker back. On a Kafka-only worker that means the first poll batch; on an HTTP-only worker it means the socket is listening; on a mixed worker it means both.

A bind failure is fatal. If the HTTP source cannot bind its socket, the worker stops. The HTTP source is an input, and a worker that cannot accept requests has nothing to read — so a port collision fails the start instead of leaving a half-dead worker running.

Shutdown runs against one deadline. On SIGINT or SIGTERM the worker marks itself not ready, calls signal_stop on every source, and then drains all sources concurrently against a single executor.drain_timeout_seconds budget. Whatever remains of that budget is what stop gets. One shared deadline means the total shutdown time does not grow with the number of sources.

The lifecycle logs name each step: sources_starting, source_started, source_start_failed, sources_draining, sources_drained and source_stop_failed. A disabled source block that would fail validation logs one source_config_ignored warning and does not block startup.

The DLQ without the Kafka source

The DLQ topic defaults to the source topic plus _dlq. With no Kafka source there is nothing to derive it from, so the DLQ needs an explicit dlq.topic:

sources.kafka.enabled dlq.topic Result
on empty DLQ built, topic {sources.kafka.topic}_dlq
on set DLQ built with that topic
off set DLQ built with that topic; brokers from dlq.brokers, else kafka.brokers
off empty No DLQ producer. The summary line reports dlq=off.

In the last row a DLQ send is dropped. The worker logs dlq_send_dropped_unconfigured once per run (then at debug level) and counts every drop in drakkar_dlq_unconfigured_drops_total. Alert on that counter if your HTTP-only workers must not lose failed payloads — set dlq.topic and they will not.

What changes in observability

The config-summary line names the enabled sources:

[worker-1/analytics-prod] sources=[kafka:search-requests/drakkar-integration/50poll http:8091] exec=4w/10win retries=3/120s ui=on:8081 storage=/tmp/drakkar fleet=filesystem cache=off metrics=9090 dlq=on sinks=[kf:a,b pg:main] log=INFO

The sources=[...] token lists the enabled sources in a fixed order, Kafka first. The Kafka token is kafka:<topic>/<group>/<max_poll_records>poll; the HTTP token is http:<port>. An HTTP-only worker shows sources=[http:8092] and nothing else.

With the Kafka source off, the Kafka-shaped surfaces report empty rather than a default that would be wrong:

Surface With the Kafka source off
drakkar_worker_info{consumer_group} Empty label. The worker joins no group, so reporting the configured default would put it in every group-scoped dashboard query it has nothing to do with.
Bound log field consumer_group Omitted from every log line.
Recorder worker_config row source_topic and consumer_group are written empty. The schema does not change.
Recorder worker_state row No assigned partitions, count 0, not paused, nothing queued.
Message probe The request must carry a topic. Without one the probe answers 400 topic is required: the Kafka source is disabled on this worker.
Kafka read API aliases source answers 404 with alias 'source' is not available: the Kafka source is disabled. dlq resolves only when dlq.topic is set, and otherwise answers alias 'dlq' is not available: the DLQ is disabled (set dlq.topic). Sink-instance aliases still work.
/readyz Reports not_started until the HTTP source is bound. No partition reasons appear.

Custom sources

The source abstraction is internal in this release: the two built-in sources are the only ones a worker can run, and there is no registration point for your own.

A public API needs an ingestion contract — how a custom source hands work to the pipeline and learns the outcome — that the two built-ins do not share in a general form. Kafka delivers through partition processors with offsets; HTTP delivers through a request runner with a synchronous reply. That contract is a later round of work.