Eswarar Siva created KAFKA-21363:
------------------------------------

             Summary: Streams EOS: member cut off from the group coordinator 
keeps committing punctuator writes unfenced, and its store diverges from the 
changelog after a clean restart
                 Key: KAFKA-21363
                 URL: https://issues.apache.org/jira/browse/KAFKA-21363
             Project: Kafka
          Issue Type: Bug
          Components: streams
    Affects Versions: 4.4.0
            Reporter: Eswarar Siva


With exactly_once_v2 and the classic group protocol, a Streams instance that 
cannot reach the group coordinator keeps its active task and keeps committing 
what its wall clock punctuator writes, while the group has already given the 
same task to another instance. Both instances write the same changelog 
partition and neither is fenced. If the cut off instance is then closed 
cleanly, it keeps its state directory, and on restart it restores only from its 
own checkpoint offset onwards. The other instance's records below that offset 
are never applied, so the store no longer matches its changelog.

Reproduced twice end to end with 4.4.0 clients against 4.3.0 brokers. Line 
numbers below are at 4.4.0, the same code is on trunk. The streams group 
protocol (KIP-1071) was not tested.

Why the instance keeps the task:
-> ConsumerCoordinator.poll calls pollHeartbeat (525), then returns early while 
the coordinator is unknown (526-528), before the rejoin check at 530. The task 
is never revoked or marked lost.
-> In the heartbeat thread (AbstractCoordinator 1513-1531) the 
coordinatorUnknown() branch only looks up the coordinator again and backs off. 
The session timeout branch at 1523 only marks the coordinator unknown, and the 
poll timeout leave at 1528 is an else-if after 1513, so it is not reached while 
the coordinator is unknown.

Why EOS does not fence it:
-> A punctuator only commit has no input offsets. 
TaskExecutor.commitOffsetsOrTransaction still commits because a transaction is 
in flight (181-186) and passes an empty map to 
StreamsProducer.commitTransaction, which calls sendOffsetsToTransaction (249).
-> KafkaProducer.sendOffsetsToTransaction only contacts the group coordinator 
when the map is not empty (800, from KAFKA-13319). The transaction commits 
against the transaction coordinator, which is still reachable, and group 
membership is never checked.
-> The two instances have different transactional ids, so they do not fence 
each other either. Their changelog records interleave.

Why the store stays wrong:
-> A clean close keeps the task directory. StateManagerUtil only wipes on an 
unclean close (168-169, 183-189; transactional state stores were off).
-> On restart ProcessorStateManager initialises the store offset from the 
store's own committed offset (334-338) and StoreChangelogReader seeks to that 
offset + 1 (1028-1032). Nothing below it is read again.

Setup:
# Three KRaft brokers. __consumer_offsets on broker 2, __transaction_state on 
broker 1, the input topic (1 partition) and the changelog on broker 3.
# Two instances A and B, exactly_once_v2, commit.interval.ms=100, 
session.timeout.ms=6000, heartbeat.interval.ms=2000, one persistent 
KeyValueStore, a 1 s wall clock punctuator that puts a new key each time. No 
input traffic.
# Blackhole the route from the instance that owns 0_0 (A) to broker 2 only. 
Within 30 s B has 0_0 active while A still reports 0_0 active, stays RUNNING 
and keeps punctuating.
# After one to two minutes close A cleanly (end of run in the first run, 
SIGTERM in the second). One "Closed clean", task directory kept.
# Restart A on the same state directory. Restore a third instance from an empty 
state directory as the reference.

Results:
||Run||A records in changelog||B records in changelog||A restarted from 
checkpoint||B keys in reference store||B keys in restarted A||Missing||
|1|208 (offsets 0 to 626)|554 (offsets 204 to 1522)|626|554|448|106, all at 
offsets 204 to 624|
|2|178 (offsets 0 to 522)|85 (offsets 188 to 524)|522|85|1|84, all at offsets 
188 to 520|

Restarted A logs "State store store initialized from checkpoint with offset 
626" (run 1) and restores from 627 on. A's own keys are all there. Nothing 
repairs this later since the checkpoint looks valid to Streams.

Not run, only follows from the same path: a key B deleted below A's checkpoint 
offset keeps A's old value after the restart.

What I think could be done: check group membership on the transactional commit 
even when there are no offsets to send, or treat a coordinator that stays 
unknown past the session timeout as losing the assignment, or let the poll 
timeout leave run outside the coordinatorUnknown() branch. Not sure which one 
fits best.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to