Skip to content

feat: allow configuring any kafka consumer or producer property - #117

Merged
omerlh merged 2 commits into
mainfrom
feat/configurable-kafka-client-properties
Aug 4, 2026
Merged

omerlh merged 2 commits into
mainfrom
feat/configurable-kafka-client-properties

Conversation

@omerlh

@omerlh omerlh commented Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The dead letter producer's Kafka properties are not configurable. KafkaClientFactory.createSenderOptions() and createProducer() set only the bootstrap/auth properties and the two serializers (src/main/java/kafka/KafkaClientFactory.java:76-88 on main), so max.request.size is stuck at the client default of 1,048,576.

The consequence is that a record larger than 1 MiB cannot be dead-lettered at all. When the target fails and DEAD_LETTER_TOPIC is set, HttpTarget calls producer.produce(...), the send is rejected with RecordTooLargeException, Producer.produce's callback fires Monitor.produceError (error log + produce_error counter), and the future completes exceptionally — so doOnSuccess/receiverOffset().acknowledge() never runs and the record sticks on the source topic instead of being parked for replay.

This is not hypothetical: it is the blind spot behind goledge/kafka-message-reviver#538 sizing its dead-letter path for records up to 12 MiB. Every other ceiling on that path is settable; this one is not.

The consumer has the same shape of gap — KAFKA_FETCH_MAX_BYTES and KAFKA_MAX_PARTITION_FETCH_BYTES have dedicated parameters, but nothing else does.

What changed

Two generic passthroughs, mirroring osskit/dafka-producer#38:

Variable Applies to Example
KAFKA_CONSUMER_PROPERTY_* the Kafka consumer KAFKA_CONSUMER_PROPERTY_MAX_PARTITION_FETCH_BYTES=5242880 → max.partition.fetch.bytes=5242880
KAFKA_PRODUCER_PROPERTY_* the dead letter producer KAFKA_PRODUCER_PROPERTY_MAX_REQUEST_SIZE=5242880 → max.request.size=5242880

The name is stripped of its prefix, lowercased with Locale.ROOT, and underscores become dots.

Why two prefixes rather than one

dafka-producer has a single Kafka client, so a single KAFKA_PROPERTY_* prefix is unambiguous there. dafka-consumer has two clients with disjoint property sets — max.request.size is meaningless to the consumer and max.partition.fetch.bytes is meaningless to the producer. A single prefix applied to both would work but would make each client log The configuration '...' was supplied but isn't a known config for every property meant for the other one. Two prefixes keep it unambiguous and make the intent readable in a Helm values file.

Precedence

Applied with putIfAbsent, so a dedicated parameter always wins over the generic passthrough — the same outcome as #38's filterNot, and it also means the passthrough can never clobber the auth or serializer properties.

Source of the variables

Read from System.getenv() rather than from Dotenv, matching #38's use of sys.env. The dedicated parameters keep going through Dotenv, so .env support for them is unchanged.

Chart

extraEnv support is added in osskit/dafka-consumer-helm-chart#111, which also fixes a related rendering bug — see that PR.

Verification

This PR is not verified, and cannot be until the image build is repaired. Being explicit about that rather than implying otherwise:

  • No local compile. No JDK is installed on the machine this was written on (java -version → Unable to locate a Java Runtime), so the Java was not compiled locally.
  • bazel build //src:image.tar is broken on main, independently of this change, which also means yarn pretest and therefore the whole jest suite cannot run. WORKSPACE:56-60 pins the archived io_bazel_rules_docker v0.25.0, whose container_repositories() declares http_file targets against a Google Cloud Storage bucket that no longer serves anonymous reads. Verified today:
https://storage.googleapis.com/rules_docker/aad94363e63d31d574cf701df484b3e8b868a96a/puller-linux-amd64   -> 403
https://storage.googleapis.com/rules_docker/aad94363e63d31d574cf701df484b3e8b868a96a/puller-darwin-amd64  -> 403
https://storage.googleapis.com/rules_docker/aad94363e63d31d574cf701df484b3e8b868a96a/loader-linux-amd64   -> 403

The rules_docker-v0.25.0.tar.gz release asset itself still returns 200 — only the puller/loader binaries are gone, and they were only ever published to that bucket. This is the same breakage osskit/dafka-producer#40 is repairing there (rules_docker → rules_oci); dafka-producer needed a base-image change too, but this repo already pulls eclipse-temurin:18, which still resolves.

The last green run here was v13.3.1 on 2026-02-08, so the bucket was withdrawn some time after that.

  • The added spec tests/specs/extraKafkaProperties.spec.ts has therefore never been executed. It is produceToDeadLetterTopic.spec.ts with KAFKA_PRODUCER_PROPERTY_MAX_REQUEST_SIZE: '1024' added and a 4 KB payload: where that spec ends with the source offset committed at 1 after a successful dead-letter produce, this one asserts the offset is never committed, because the clamped max.request.size makes the produce fail. If the passthrough did not reach the producer, the 4 KB record would dead-letter fine and the offset would be 1.

  • The chart-side rendering is verified (helm lint + helm template) in the chart PR.

I have deliberately not taken on the rules_docker → rules_oci migration here — it is a separate concern from a config passthrough and belongs in its own PR, as it did for dafka-producer. Happy to open it if you'd like, but flagging rather than assuming.

@omerlh

omerlh commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

CI confirms the build blocker described above — the test job failed exactly as predicted, before reaching any of this PR's code:

WARNING: Download from https://storage.googleapis.com/rules_docker/aad94363e63d31d574cf701df484b3e8b868a96a/puller-darwin-amd64 failed: class java.io.IOException GET returned 403 Forbidden
ERROR: /src/BUILD:45:16: no such package '@java_base_image//image': no such package '@go_puller_darwin//file'
ERROR: Analysis of target '//src:image.tar' failed; build aborted
FAILED: Build did NOT complete successfully (155 packages loaded, 9793 targets configured)

Run 30901027620. This is yarn pretest (bazel build //src:image.tar), so the whole jest suite is gated behind it — the failure is pre-existing on main and reproduces on any PR.

Repairing that build is a separate change (the rules_docker → rules_oci migration, as in osskit/dafka-producer#40) and I have deliberately kept it out of this PR. Let me know if you'd like it opened.

The dead letter producer has no way to set max.request.size, so a record
above the 1 MiB default cannot be dead-lettered at all - the produce fails,
Monitor.produceError fires, and the offset is never acknowledged.

Adds KAFKA_CONSUMER_PROPERTY_* and KAFKA_PRODUCER_PROPERTY_* passthroughs,
mirroring osskit/dafka-producer#38. Two prefixes rather than one because the
consumer and the dead letter producer are separate clients with disjoint
property sets. Dedicated parameters still win over the passthrough.

Co-authored-by: Cursor <cursoragent@cursor.com>
@omerlh
omerlh force-pushed the feat/configurable-kafka-client-properties branch from efd4bf7 to 42ca9f0 Compare August 4, 2026 15:21
… clamps

HttpTarget acknowledges the source offset before the DLQ produce future
settles, so foo's consumer offset cannot observe a failed produce. Check
the dead topic high watermark instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@omerlh

omerlh commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

CI failure was a bad assertion in the new property test, not a passthrough bug.

HttpTarget.call uses handleAsync without flattening the nested DLQ produce future, so the source topic offset is acknowledged before we know whether the dead-letter send succeeded. Asserting getOffset(foo) === -1 can never pass.

Updated the test to assert the dead topic high watermark stays 0 when KAFKA_PRODUCER_PROPERTY_MAX_REQUEST_SIZE=1024 rejects the ~4 KiB payload — that is what actually proves the property was applied. (Same scenario without the clamp ends with watermark 1, per produceToDeadLetterTopic.spec.ts.)

@omerlh
omerlh merged commit 3131adf into main Aug 4, 2026
1 check passed
@omerlh
omerlh deleted the feat/configurable-kafka-client-properties branch August 4, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants