Skip to content

KAFKA-20979: Ensure indexes resize are fsynced when shutting down - #23258

Open
mimaison wants to merge 3 commits into
apache:trunkfrom
mimaison:kafka-20979
Open

KAFKA-20979: Ensure indexes resize are fsynced when shutting down#23258
mimaison wants to merge 3 commits into
apache:trunkfrom
mimaison:kafka-20979

Conversation

@mimaison

@mimaison mimaison commented Aug 24, 2026

Copy link
Copy Markdown
Member

This avoids reading invalid length for the index files after a clean
shut down

This does not prevent the issue happening on an unclean shutdown. For
validating indexes at startup, we have
https://issues.apache.org/jira/browse/KAFKA-19200

Reviewers: Gaurav Narula gaurav_narula2@apple.com, Luke Chen
showuon@gmail.com

This avoids reading invalid length for the index files after a clean shut down
@github-actions github-actions Bot added storage Pull requests that target the storage module small Small PRs labels Aug 24, 2026
this.maxEntries = mmap.limit() / entrySize();
mmap.position(position);
if (sync) {
raf.getChannel().force(true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the javadoc for FileChannel#force

This method is only guaranteed to force changes that were made to this channel's file via the methods defined in this class, or the methods defined by java.io.FileOutputStream or java.io.RandomAccessFile when the channel was obtained with the getChannel method. It may or may not force changes that were made by modifying the content of a mapped byte buffer obtained by invoking the map method. Invoking the force method of the mapped byte buffer will force changes made to the buffer's content to be written.

I think if the intention is to sync both the existing data and the file metadata we should invoke mmap.force() just before safeForceUnmap() in addition to this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch @gaurav-narula ! Yes, the FileChannel#map change won't be guaranteed to be written into the disk. I think we can directly use the existing flush() here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe my comment fsync the file after resizing to ensure both content and size are durable is confusing. This method only touches metadata (length) and my concern was to ensure metadata are flushed.

The content should already have been flushed from LogSegment.flush().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was that comment that got me looking :)

The content should already have been flushed from LogSegment.flush()

IIUC, the flush is invoked in a thread spawned during shutdown at LogManager at

followed by a UnifiedLog#close.

There is however a conditional append to a time index that may happen after LogSegment#flush() and before TimeIndex#close() at LogSegment#close()

Utils.swallow(LOGGER, Level.WARN, "maybeAppend", () -> timeIndex().maybeAppend(maxTimestampSoFar(), shallowOffsetOfMaxTimestampSoFar(), true));
which is not fsynced.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the content could also not be flushed. I've added a call to flush() in AbstractIndex.close().

@showuon showuon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I think we also need to add a test for it. We already have a AbstractIndexTest#testResizeInvokeUnmap test to verify the Unmap is invoked when resize. We can add the similar test to verify the flush method is invoked.

this.maxEntries = mmap.limit() / entrySize();
mmap.position(position);
if (sync) {
raf.getChannel().force(true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch @gaurav-narula ! Yes, the FileChannel#map change won't be guaranteed to be written into the disk. I think we can directly use the existing flush() here.

@gaurav-narula gaurav-narula left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! A small comment around NPE due to which we've failing tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the tests are failing due to an NPE here as close() is invoked multiple times. Perhaps consider guarding this with a null check?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I pushed a fix, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

small Small PRs storage Pull requests that target the storage module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants