Skip to content

Commit d5e624e

Browse files
KAFKA-19693: Added PersisterBatch record in Share Partition which includes updatedState and stateBatch (#20507)
The method rollbackOrProcessStateUpdates in SharePartition received 2 separate lists of updatedStates (InFlightState) and stateBatches (PersisterStateBatch). This PR introduces a new subclass called `PersisterBatch` which encompasses both these objects. Reviewers: Apoorv Mittal <[email protected]>
1 parent 620a01b commit d5e624e

File tree

1 file changed

+78
-82
lines changed

1 file changed

+78
-82
lines changed

core/src/main/java/kafka/server/share/SharePartition.java

Lines changed: 78 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -884,8 +884,7 @@ public CompletableFuture<Void> acknowledge(
884884

885885
CompletableFuture<Void> future = new CompletableFuture<>();
886886
Throwable throwable = null;
887-
List<InFlightState> updatedStates = new ArrayList<>();
888-
List<PersisterStateBatch> stateBatches = new ArrayList<>();
887+
List<PersisterBatch> persisterBatches = new ArrayList<>();
889888
lock.writeLock().lock();
890889
try {
891890
// Avoided using enhanced for loop as need to check if the last batch have offsets
@@ -925,8 +924,7 @@ public CompletableFuture<Void> acknowledge(
925924
batch,
926925
recordStateMap,
927926
subMap,
928-
updatedStates,
929-
stateBatches
927+
persisterBatches
930928
);
931929

932930
if (ackThrowable.isPresent()) {
@@ -939,7 +937,7 @@ public CompletableFuture<Void> acknowledge(
939937
}
940938
// If the acknowledgement is successful then persist state, complete the state transition
941939
// and update the cached state for start offset. Else rollback the state transition.
942-
rollbackOrProcessStateUpdates(future, throwable, updatedStates, stateBatches);
940+
rollbackOrProcessStateUpdates(future, throwable, persisterBatches);
943941
return future;
944942
}
945943

@@ -955,8 +953,7 @@ public CompletableFuture<Void> releaseAcquiredRecords(String memberId) {
955953

956954
CompletableFuture<Void> future = new CompletableFuture<>();
957955
Throwable throwable = null;
958-
List<InFlightState> updatedStates = new ArrayList<>();
959-
List<PersisterStateBatch> stateBatches = new ArrayList<>();
956+
List<PersisterBatch> persisterBatches = new ArrayList<>();
960957

961958
lock.writeLock().lock();
962959
try {
@@ -975,14 +972,14 @@ && checkForStartOffsetWithinBatch(inFlightBatch.firstOffset(), inFlightBatch.las
975972
}
976973

977974
if (inFlightBatch.offsetState() != null) {
978-
Optional<Throwable> releaseAcquiredRecordsThrowable = releaseAcquiredRecordsForPerOffsetBatch(memberId, inFlightBatch, recordState, updatedStates, stateBatches);
975+
Optional<Throwable> releaseAcquiredRecordsThrowable = releaseAcquiredRecordsForPerOffsetBatch(memberId, inFlightBatch, recordState, persisterBatches);
979976
if (releaseAcquiredRecordsThrowable.isPresent()) {
980977
throwable = releaseAcquiredRecordsThrowable.get();
981978
break;
982979
}
983980
continue;
984981
}
985-
Optional<Throwable> releaseAcquiredRecordsThrowable = releaseAcquiredRecordsForCompleteBatch(memberId, inFlightBatch, recordState, updatedStates, stateBatches);
982+
Optional<Throwable> releaseAcquiredRecordsThrowable = releaseAcquiredRecordsForCompleteBatch(memberId, inFlightBatch, recordState, persisterBatches);
986983
if (releaseAcquiredRecordsThrowable.isPresent()) {
987984
throwable = releaseAcquiredRecordsThrowable.get();
988985
break;
@@ -993,7 +990,7 @@ && checkForStartOffsetWithinBatch(inFlightBatch.firstOffset(), inFlightBatch.las
993990
}
994991
// If the release acquired records is successful then persist state, complete the state transition
995992
// and update the cached state for start offset. Else rollback the state transition.
996-
rollbackOrProcessStateUpdates(future, throwable, updatedStates, stateBatches);
993+
rollbackOrProcessStateUpdates(future, throwable, persisterBatches);
997994
return future;
998995
}
999996

@@ -1004,8 +1001,7 @@ long loadStartTimeMs() {
10041001
private Optional<Throwable> releaseAcquiredRecordsForPerOffsetBatch(String memberId,
10051002
InFlightBatch inFlightBatch,
10061003
RecordState recordState,
1007-
List<InFlightState> updatedStates,
1008-
List<PersisterStateBatch> stateBatches) {
1004+
List<PersisterBatch> persisterBatches) {
10091005

10101006
log.trace("Offset tracked batch record found, batch: {} for the share partition: {}-{}", inFlightBatch,
10111007
groupId, topicIdPartition);
@@ -1032,10 +1028,9 @@ private Optional<Throwable> releaseAcquiredRecordsForPerOffsetBatch(String membe
10321028
return Optional.of(new InvalidRecordStateException("Unable to release acquired records for the offset"));
10331029
}
10341030

1035-
// Successfully updated the state of the offset.
1036-
updatedStates.add(updateResult);
1037-
stateBatches.add(new PersisterStateBatch(offsetState.getKey(), offsetState.getKey(),
1038-
updateResult.state().id(), (short) updateResult.deliveryCount()));
1031+
// Successfully updated the state of the offset and created a persister state batch for write to persister.
1032+
persisterBatches.add(new PersisterBatch(updateResult, new PersisterStateBatch(offsetState.getKey(),
1033+
offsetState.getKey(), updateResult.state().id(), (short) updateResult.deliveryCount())));
10391034
// Do not update the next fetch offset as the offset has not completed the transition yet.
10401035
}
10411036
}
@@ -1045,8 +1040,7 @@ private Optional<Throwable> releaseAcquiredRecordsForPerOffsetBatch(String membe
10451040
private Optional<Throwable> releaseAcquiredRecordsForCompleteBatch(String memberId,
10461041
InFlightBatch inFlightBatch,
10471042
RecordState recordState,
1048-
List<InFlightState> updatedStates,
1049-
List<PersisterStateBatch> stateBatches) {
1043+
List<PersisterBatch> persisterBatches) {
10501044

10511045
// Check if member id is the owner of the batch.
10521046
if (!inFlightBatch.batchMemberId().equals(memberId) && !inFlightBatch.batchMemberId().equals(EMPTY_MEMBER_ID)) {
@@ -1072,10 +1066,9 @@ private Optional<Throwable> releaseAcquiredRecordsForCompleteBatch(String member
10721066
return Optional.of(new InvalidRecordStateException("Unable to release acquired records for the batch"));
10731067
}
10741068

1075-
// Successfully updated the state of the batch.
1076-
updatedStates.add(updateResult);
1077-
stateBatches.add(new PersisterStateBatch(inFlightBatch.firstOffset(), inFlightBatch.lastOffset(),
1078-
updateResult.state().id(), (short) updateResult.deliveryCount()));
1069+
// Successfully updated the state of the batch and created a persister state batch for write to persister.
1070+
persisterBatches.add(new PersisterBatch(updateResult, new PersisterStateBatch(inFlightBatch.firstOffset(),
1071+
inFlightBatch.lastOffset(), updateResult.state().id(), (short) updateResult.deliveryCount())));
10791072
// Do not update the next fetch offset as the batch has not completed the transition yet.
10801073
}
10811074
return Optional.empty();
@@ -1826,8 +1819,7 @@ private Optional<Throwable> acknowledgeBatchRecords(
18261819
ShareAcknowledgementBatch batch,
18271820
Map<Long, RecordState> recordStateMap,
18281821
NavigableMap<Long, InFlightBatch> subMap,
1829-
final List<InFlightState> updatedStates,
1830-
List<PersisterStateBatch> stateBatches
1822+
List<PersisterBatch> persisterBatches
18311823
) {
18321824
Optional<Throwable> throwable;
18331825
lock.writeLock().lock();
@@ -1889,11 +1881,11 @@ private Optional<Throwable> acknowledgeBatchRecords(
18891881
}
18901882

18911883
throwable = acknowledgePerOffsetBatchRecords(memberId, batch, inFlightBatch,
1892-
recordStateMap, updatedStates, stateBatches);
1884+
recordStateMap, persisterBatches);
18931885
} else {
18941886
// The in-flight batch is a full match hence change the state of the complete batch.
18951887
throwable = acknowledgeCompleteBatch(batch, inFlightBatch,
1896-
recordStateMap.get(batch.firstOffset()), updatedStates, stateBatches);
1888+
recordStateMap.get(batch.firstOffset()), persisterBatches);
18971889
}
18981890

18991891
if (throwable.isPresent()) {
@@ -1930,8 +1922,7 @@ private Optional<Throwable> acknowledgePerOffsetBatchRecords(
19301922
ShareAcknowledgementBatch batch,
19311923
InFlightBatch inFlightBatch,
19321924
Map<Long, RecordState> recordStateMap,
1933-
List<InFlightState> updatedStates,
1934-
List<PersisterStateBatch> stateBatches
1925+
List<PersisterBatch> persisterBatches
19351926
) {
19361927
lock.writeLock().lock();
19371928
try {
@@ -1995,10 +1986,9 @@ private Optional<Throwable> acknowledgePerOffsetBatchRecords(
19951986
return Optional.of(new InvalidRecordStateException(
19961987
"Unable to acknowledge records for the batch"));
19971988
}
1998-
// Successfully updated the state of the offset.
1999-
updatedStates.add(updateResult);
2000-
stateBatches.add(new PersisterStateBatch(offsetState.getKey(), offsetState.getKey(),
2001-
updateResult.state().id(), (short) updateResult.deliveryCount()));
1989+
// Successfully updated the state of the offset and created a persister state batch for write to persister.
1990+
persisterBatches.add(new PersisterBatch(updateResult, new PersisterStateBatch(offsetState.getKey(),
1991+
offsetState.getKey(), updateResult.state().id(), (short) updateResult.deliveryCount())));
20021992
// Do not update the nextFetchOffset as the offset has not completed the transition yet.
20031993
}
20041994
} finally {
@@ -2011,8 +2001,7 @@ private Optional<Throwable> acknowledgeCompleteBatch(
20112001
ShareAcknowledgementBatch batch,
20122002
InFlightBatch inFlightBatch,
20132003
RecordState recordState,
2014-
List<InFlightState> updatedStates,
2015-
List<PersisterStateBatch> stateBatches
2004+
List<PersisterBatch> persisterBatches
20162005
) {
20172006
lock.writeLock().lock();
20182007
try {
@@ -2044,11 +2033,9 @@ private Optional<Throwable> acknowledgeCompleteBatch(
20442033
new InvalidRecordStateException("Unable to acknowledge records for the batch"));
20452034
}
20462035

2047-
// Successfully updated the state of the batch.
2048-
updatedStates.add(updateResult);
2049-
stateBatches.add(
2050-
new PersisterStateBatch(inFlightBatch.firstOffset(), inFlightBatch.lastOffset(),
2051-
updateResult.state().id(), (short) updateResult.deliveryCount()));
2036+
// Successfully updated the state of the batch and created a persister state batch for write to persister.
2037+
persisterBatches.add(new PersisterBatch(updateResult, new PersisterStateBatch(inFlightBatch.firstOffset(),
2038+
inFlightBatch.lastOffset(), updateResult.state().id(), (short) updateResult.deliveryCount())));
20522039
// Do not update the next fetch offset as the batch has not completed the transition yet.
20532040
} finally {
20542041
lock.writeLock().unlock();
@@ -2090,74 +2077,74 @@ SharePartitionState partitionState() {
20902077
void rollbackOrProcessStateUpdates(
20912078
CompletableFuture<Void> future,
20922079
Throwable throwable,
2093-
List<InFlightState> updatedStates,
2094-
List<PersisterStateBatch> stateBatches
2080+
List<PersisterBatch> persisterBatches
20952081
) {
20962082
lock.writeLock().lock();
20972083
try {
20982084
if (throwable != null) {
20992085
// Log in DEBUG to avoid flooding of logs for a faulty client.
21002086
log.debug("Request failed for updating state, rollback any changed state"
21012087
+ " for the share partition: {}-{}", groupId, topicIdPartition);
2102-
updatedStates.forEach(state -> {
2103-
state.completeStateTransition(false);
2104-
if (state.state() == RecordState.AVAILABLE) {
2088+
persisterBatches.forEach(persisterBatch -> {
2089+
persisterBatch.updatedState.completeStateTransition(false);
2090+
if (persisterBatch.updatedState.state() == RecordState.AVAILABLE) {
21052091
updateFindNextFetchOffset(true);
21062092
}
21072093
});
21082094
future.completeExceptionally(throwable);
21092095
return;
21102096
}
21112097

2112-
if (stateBatches.isEmpty() && updatedStates.isEmpty()) {
2098+
if (persisterBatches.isEmpty()) {
21132099
future.complete(null);
21142100
return;
21152101
}
21162102
} finally {
21172103
lock.writeLock().unlock();
21182104
}
21192105

2120-
writeShareGroupState(stateBatches).whenComplete((result, exception) -> {
2121-
// There can be a pending delayed share fetch requests for the share partition which are waiting
2122-
// on the startOffset to move ahead, hence track if the state is updated in the cache. If
2123-
// yes, then notify the delayed share fetch purgatory to complete the pending requests.
2124-
boolean cacheStateUpdated = false;
2125-
lock.writeLock().lock();
2126-
try {
2127-
if (exception != null) {
2128-
log.debug("Failed to write state to persister for the share partition: {}-{}",
2129-
groupId, topicIdPartition, exception);
2130-
// In case of failure when transition state is rolled back then it should be rolled
2131-
// back to ACQUIRED state, unless acquisition lock for the state has expired.
2132-
updatedStates.forEach(state -> {
2133-
state.completeStateTransition(false);
2134-
if (state.state() == RecordState.AVAILABLE) {
2106+
writeShareGroupState(persisterBatches.stream().map(PersisterBatch::stateBatch).toList())
2107+
.whenComplete((result, exception) -> {
2108+
// There can be a pending delayed share fetch requests for the share partition which are waiting
2109+
// on the startOffset to move ahead, hence track if the state is updated in the cache. If
2110+
// yes, then notify the delayed share fetch purgatory to complete the pending requests.
2111+
boolean cacheStateUpdated = false;
2112+
lock.writeLock().lock();
2113+
try {
2114+
if (exception != null) {
2115+
log.debug("Failed to write state to persister for the share partition: {}-{}",
2116+
groupId, topicIdPartition, exception);
2117+
// In case of failure when transition state is rolled back then it should be rolled
2118+
// back to ACQUIRED state, unless acquisition lock for the state has expired.
2119+
persisterBatches.forEach(persisterBatch -> {
2120+
persisterBatch.updatedState.completeStateTransition(false);
2121+
if (persisterBatch.updatedState.state() == RecordState.AVAILABLE) {
2122+
updateFindNextFetchOffset(true);
2123+
}
2124+
});
2125+
future.completeExceptionally(exception);
2126+
return;
2127+
}
2128+
2129+
log.trace("State change request successful for share partition: {}-{}",
2130+
groupId, topicIdPartition);
2131+
persisterBatches.forEach(persisterBatch -> {
2132+
persisterBatch.updatedState.completeStateTransition(true);
2133+
if (persisterBatch.updatedState.state() == RecordState.AVAILABLE) {
21352134
updateFindNextFetchOffset(true);
21362135
}
21372136
});
2138-
future.completeExceptionally(exception);
2139-
return;
2137+
// Update the cached state and start and end offsets after acknowledging/releasing the acquired records.
2138+
cacheStateUpdated = maybeUpdateCachedStateAndOffsets();
2139+
future.complete(null);
2140+
} finally {
2141+
lock.writeLock().unlock();
2142+
// Maybe complete the delayed share fetch request if the state has been changed in cache
2143+
// which might have moved start offset ahead. Hence, the pending delayed share fetch
2144+
// request can be completed. The call should be made outside the lock to avoid deadlock.
2145+
maybeCompleteDelayedShareFetchRequest(cacheStateUpdated);
21402146
}
2141-
2142-
log.trace("State change request successful for share partition: {}-{}",
2143-
groupId, topicIdPartition);
2144-
updatedStates.forEach(state -> {
2145-
state.completeStateTransition(true);
2146-
if (state.state() == RecordState.AVAILABLE) {
2147-
updateFindNextFetchOffset(true);
2148-
}
2149-
});
2150-
// Update the cached state and start and end offsets after acknowledging/releasing the acquired records.
2151-
cacheStateUpdated = maybeUpdateCachedStateAndOffsets();
2152-
future.complete(null);
2153-
} finally {
2154-
lock.writeLock().unlock();
2155-
// Maybe complete the delayed share fetch request if the state has been changed in cache
2156-
// which might have moved start offset ahead. Hence, the pending delayed share fetch
2157-
// request can be completed. The call should be made outside the lock to avoid deadlock.
2158-
maybeCompleteDelayedShareFetchRequest(cacheStateUpdated);
2159-
}
2160-
});
2147+
});
21612148
}
21622149

21632150
private boolean maybeUpdateCachedStateAndOffsets() {
@@ -2929,6 +2916,15 @@ void updateOffsetMetadata(long offset, LogOffsetMetadata offsetMetadata) {
29292916
}
29302917
}
29312918

2919+
/**
2920+
* PersisterBatch class is used to record the state updates for a batch or an offset.
2921+
* It contains the updated in-flight state and the persister state batch to be sent to persister.
2922+
*/
2923+
private record PersisterBatch(
2924+
InFlightState updatedState,
2925+
PersisterStateBatch stateBatch
2926+
) { }
2927+
29322928
/**
29332929
* LastOffsetAndMaxRecords class is used to track the last offset to acquire and the maximum number
29342930
* of records that can be acquired in a fetch request.

0 commit comments

Comments
 (0)