Paweł Rzepiński created NIFI-16518:
--------------------------------------
Summary: ConsumeKafka DEMARCATOR Processing Strategy commits the
wrong Kafka offset
Key: NIFI-16518
URL: https://issues.apache.org/jira/browse/NIFI-16518
Project: Apache NiFi
Issue Type: Bug
Reporter: Paweł Rzepiński
h2. Symptom
With {{Processing Strategy = DEMARCATOR}}, the offset committed to the Kafka
consumer group is one past the *first* offset in the bundle instead of one past
the *last* offset consumed.
A single poll of offsets 0 through 5, bundled into one FlowFile, commits offset
{{1}}. {{FLOW_FILE}} and {{RECORD}} commit {{6}} for the same six records.
The in-memory consumer position is unaffected, because {{KafkaConsumer.poll()}}
already advanced it past offset 5. Only the group offset stored on the broker
is short. That surfaces as:
* *Duplicates after restart.* The new consumer starts from the committed offset
and re-reads offsets 1 through 5.
* *Duplicates after rebalance.* {{commit()}} removes the partition from
{{uncommittedOffsets}} ({{Kafka3ConsumerService.java:161}}), so a later
rebalance has nothing to commit and the broker keeps the short offset. Moving
the partition to another node replays it.
* *Duplicates without any restart.* {{Kafka3ConsumerService.rollback}} seeks to
{{consumer.committed()}} ({{Kafka3ConsumerService.java:182-192}}). Any later
failure in the same running processor (an exception in {{onTrigger}}, a failed
session commit, a failed offset commit) rewinds to the short offset and
re-emits bundles already delivered.
* *Backlog never reports caught up.* {{getBacklog}} subtracts the committed
offset from the end offset via
{{Kafka3ConnectionService.getCommittedOffsetLag}}
({{Kafka3ConnectionService.java:379-396}}). With DEMARCATOR the reported lag
never reaches 0 when the final bundle holds more than one record, so
{{lastCaughtUp}} is never stamped.
h2. Root cause
Three pieces combine:
# {{ByteRecordBundler.toByteRecord}} ({{ByteRecordBundler.java:66-69}}) builds
one {{ByteRecord}} per bundle and passes {{BundleValue.getFirstOffset()}} as
that record's offset. The last offset is written only into a
{{kafka.max.offset}} header, which becomes a FlowFile attribute and is never
read by the commit path.
# {{OffsetTracker.update}} ({{OffsetTracker.java:39-41}}) stores
{{consumerRecord.getOffset()}} for the partition and *replaces* any previous
value.
# {{Kafka3ConsumerService.getOffsets}} ({{Kafka3ConsumerService.java:326}})
commits {{offsetSummary.getOffset() + 1}}.
For a bundle, step 1 feeds the first offset into step 2, and step 3 turns 0
into 1.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)