DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
...
| Code Block |
|---|
CLIENT --> BROKER communication (over the Kafka wire protocol):
Share Consumer -----> Share Group Coordinator
ShareFetch, ShareAcknowledge, ShareGroupHeartbeat
TransactionManager --> Transaction Coordinator
InitProducerId, AddPartitionsToTxn, AddShareAcksToTxn(NEW), EndTxn
TransactionManager --> Share Group Coordinator
TxnShareAcknowledgeRequest (NEW)
Producer ------------> Data Partition Leaders
ProduceRequest
BROKER --> BROKER communication (internal, not client-visible):
Transaction Coordinator --> Data Partition Leaders
WriteTxnMarkers (COMMIT/ABORT control record)
Transaction Coordinator --> Group Coordinator (via __consumer_offsets leader)
WriteTxnMarkers (materializes transactional offset commits)
Transaction Coordinator --> Share Group Coordinator (via __share_group_state leader) [NEW]
WriteTxnMarkers (materializes transactional share acks) |
Abort case:
6. Corner Cases
Case 1: Crash BEFORE beginTransaction()
poll() -> records [0..9] ACQUIRED
<CRASH>
What happens:
- No transaction was started, no output produced, no acks sent
- ACQUIRED locks on records 0-9 expire (controlled by
share.record.lock.duration.ms) - Broker re-delivers records 0-9 to another member of the share group
- No data loss, no duplicates
Case 2: Crash AFTER send() but BEFORE commitTransaction()
poll() -> records [0..9] ACQUIRED
beginTransaction()
send(output for 0-9) ← records written to broker, but transactional
<CRASH>
What happens:
- Output records are in the partition log but invisible (
read_committedconsumers skip uncommitted) - No
EndTxnRequestwas sent - TC detects transaction timeout (
transaction.timeout.ms), auto-aborts - TC sends
WriteTxnMarkers(ABORT)-> output records get ABORT marker, permanently invisible - Share group: no acks were sent, ACQUIRED locks expire, records 0-9 re-delivered
- No data loss, no duplicates
Case 3: Crash AFTER sendShareAcksToTransaction() but BEFORE commitTransaction()
poll() -> records [0..9] ACQUIRED
beginTransaction()
send(output)
sendShareAcksToTransaction() ← acks written to SGC as PENDING
<CRASH>
What happens:
- Output records: in log, invisible (uncommitted)
- Share acks: stored in SGC as PENDING (not materialized)
- TC detects timeout, sends
WriteTxnMarkers(ABORT)to both data partitions AND__share_group_state - Output: ABORT marker written, records permanently invisible
- Share acks: SGC discards pending acks, locks expire, records 0-9 re-delivered
- No data loss, no duplicates -- this is the critical case that KIP-1289 solves
Case 4: Crash AFTER EndTxn(COMMIT) sent but BEFORE WriteTxnMarkers completes
poll() -> records [0..9] ACQUIRED
beginTransaction()
send(output)
sendShareAcksToTransaction()
commitTransaction() ← EndTxn sent, PREPARE_COMMIT logged
<TC crashes or broker restart>
What happens:
PREPARE_COMMITis durable in__transaction_statelog- When TC recovers (or new leader elected for
__transaction_statepartition), it replays the log - TC sees PREPARE_COMMIT, resumes sending
WriteTxnMarkers(COMMIT)to all partitions - Output records become visible, share acks materialized
- No data loss, no duplicates -- the two-phase commit guarantees completion
Case 5: ProducerFencedException (zombie detection)
Instance A: beginTransaction(), send(), ...
Instance B: initTransactions() with same transactional.id
← TC bumps epoch, A is now a zombie
Instance A: commitTransaction()
← TC rejects: ProducerFencedException
What happens:
- Instance A's transaction is aborted by the TC (epoch fenced)
- Any pending output and share acks from A are discarded
- Instance B takes over, re-processes the records
- No data loss, no duplicates -- fencing prevents split-brain
Case 6: abortTransaction() called explicitly
poll() -> records [0..9] ACQUIRED
beginTransaction()
send(output for 0-4)
record 5 fails validation
abortTransaction() ← explicit abort
What happens:
- TC writes PREPARE_ABORT, sends
WriteTxnMarkers(ABORT) - Output records 0-4: ABORT marker, permanently invisible
- Share acks (if any sent): discarded
- ACQUIRED locks expire, all records 0-9 re-delivered
- Application gets fresh batch, can retry
- No data loss
Case 7: Partial produce failure (network error to one partition)
beginTransaction()
send("enriched-orders-0", rec1) ← success
send("enriched-orders-1", rec2) ← network error, KafkaException
What happens:
- Application catches
KafkaException, callsabortTransaction() - Same as Case 6: everything rolled back, records re-delivered
- No data loss
The Guarantee Matrix
| Scenario | Output Records | Share Acks | Records Re-delivered? | Data Loss? | Duplicates? |
|---|---|---|---|---|---|
| Happy path (commit) | Visible | Materialized | No | No | No |
| Crash before txn | Never written | Never sent | Yes (lock expiry) | No | No |
| Crash mid-txn | Aborted (invisible) | Discarded | Yes (lock expiry) | No | No |
| Crash after PREPARE_COMMIT | Committed on recovery | Materialized on recovery | No | No | No |
| Zombie fenced | Aborted | Discarded | Yes (new instance) | No | No |
| Explicit abort | Aborted (invisible) | Discarded | Yes (lock expiry) | No | No |
| TC crash after PREPARE | Completed on failover | Completed on failover | No | No | No |
The fundamental property: at no point can output records be visible while share acks are uncommitted, or vice versa. They are in the same transaction and share the same COMMIT/ABORT fate.
7. Compatibility, Deprecation, and Migration Plan
Impact on Existing Users
- If older broker doesn’t support
TxnShareAcknowledgeRequest, fallback to current explicit/implicit ack with warning. - Clients must fail fast on unsupported brokers
- No breaking changes for existing Share Group users
- `transactional` mode is opt-in via `share.acknowledgement.mode` config
- Existing `implicit` and `explicit` modes continue to work unchanged
...
- No deprecation of existing modes planned
- `transactional` mode recommended for exactly-once use cases
8. Test Plan
Unit Tests
- Transaction state machine transitions
- Idempotency of all transaction operations
- Timeout handling and auto-abort
- Record state transitions (ACQUIRED → ACKNOWLEDGED on commit, ACQUIRED → AVAILABLE on abort/timeout)
...
