mirror of https://github.com/apache/kafka.git
KAFKA-19163: Avoid deleting groups with pending transactional offsets (#19496)
When a group has pending transactional offsets but no committed offsets, we can accidentally delete it while cleaning up expired offsets. Add a check to avoid this case. Reviewers: David Jacot <djacot@confluent.io>
This commit is contained in:
parent
9aad1849e2
commit
48f75616d7
|
@ -1001,13 +1001,14 @@ public class OffsetMetadataManager {
|
||||||
* @param groupId The group id.
|
* @param groupId The group id.
|
||||||
* @param records The list of records to populate with offset commit tombstone records.
|
* @param records The list of records to populate with offset commit tombstone records.
|
||||||
*
|
*
|
||||||
* @return True if no offsets exist or if all offsets expired, false otherwise.
|
* @return True if no offsets exist after expiry and no pending transactional offsets exist,
|
||||||
|
* false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean cleanupExpiredOffsets(String groupId, List<CoordinatorRecord> records) {
|
public boolean cleanupExpiredOffsets(String groupId, List<CoordinatorRecord> records) {
|
||||||
TimelineHashMap<String, TimelineHashMap<Integer, OffsetAndMetadata>> offsetsByTopic =
|
TimelineHashMap<String, TimelineHashMap<Integer, OffsetAndMetadata>> offsetsByTopic =
|
||||||
offsets.offsetsByGroup.get(groupId);
|
offsets.offsetsByGroup.get(groupId);
|
||||||
if (offsetsByTopic == null) {
|
if (offsetsByTopic == null) {
|
||||||
return true;
|
return !openTransactions.contains(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect the group to exist.
|
// We expect the group to exist.
|
||||||
|
|
|
@ -2591,6 +2591,42 @@ public class OffsetMetadataManagerTest {
|
||||||
assertEquals(Collections.emptyList(), records);
|
assertEquals(Collections.emptyList(), records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCleanupExpiredOffsetsWithPendingTransactionalOffsetsOnly() {
|
||||||
|
GroupMetadataManager groupMetadataManager = mock(GroupMetadataManager.class);
|
||||||
|
Group group = mock(Group.class);
|
||||||
|
|
||||||
|
OffsetMetadataManagerTestContext context = new OffsetMetadataManagerTestContext.Builder()
|
||||||
|
.withGroupMetadataManager(groupMetadataManager)
|
||||||
|
.withOffsetsRetentionMinutes(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
long commitTimestamp = context.time.milliseconds();
|
||||||
|
|
||||||
|
context.commitOffset("group-id", "foo", 0, 100L, 0, commitTimestamp);
|
||||||
|
context.commitOffset(10L, "group-id", "foo", 1, 101L, 0, commitTimestamp + 500);
|
||||||
|
|
||||||
|
context.time.sleep(Duration.ofMinutes(1).toMillis());
|
||||||
|
|
||||||
|
when(groupMetadataManager.group("group-id")).thenReturn(group);
|
||||||
|
when(group.offsetExpirationCondition()).thenReturn(Optional.of(
|
||||||
|
new OffsetExpirationConditionImpl(offsetAndMetadata -> offsetAndMetadata.commitTimestampMs)));
|
||||||
|
when(group.isSubscribedToTopic("foo")).thenReturn(false);
|
||||||
|
|
||||||
|
// foo-0 is expired, but the group is not deleted beacuse it has pending transactional offset commits.
|
||||||
|
List<CoordinatorRecord> expectedRecords = List.of(
|
||||||
|
GroupCoordinatorRecordHelpers.newOffsetCommitTombstoneRecord("group-id", "foo", 0)
|
||||||
|
);
|
||||||
|
List<CoordinatorRecord> records = new ArrayList<>();
|
||||||
|
assertFalse(context.cleanupExpiredOffsets("group-id", records));
|
||||||
|
assertEquals(expectedRecords, records);
|
||||||
|
|
||||||
|
// No offsets are expired, and the group is still not deleted because it has pending transactional offset commits.
|
||||||
|
records = new ArrayList<>();
|
||||||
|
assertFalse(context.cleanupExpiredOffsets("group-id", records));
|
||||||
|
assertEquals(List.of(), records);
|
||||||
|
}
|
||||||
|
|
||||||
private static OffsetFetchResponseData.OffsetFetchResponsePartitions mkOffsetPartitionResponse(
|
private static OffsetFetchResponseData.OffsetFetchResponsePartitions mkOffsetPartitionResponse(
|
||||||
int partition,
|
int partition,
|
||||||
long offset,
|
long offset,
|
||||||
|
|
Loading…
Reference in New Issue