Skip to content

Conversation

@jsancio
Copy link
Member

@jsancio jsancio commented Mar 30, 2021

Kafka networking layer doesn't close FileRecords and assumes that they are already open when sending them over a channel. To support this pattern this commit changes the ownership model for FileRawSnapshotReader so that they are owned by KafkaMetadataLog. This includes:

  1. Changing KafkaMetadataLog's snapshotIds form a Set[OffsetAndEpoch] to a TreeMap[OffsetAndEpoch, Option[FileRawSnapshotReader]]. This map contains all of the known snapshots. The value will be Some if a snapshot reader has been opened in the past.

  2. Split and change the functionality in KafkaMetadataLog::removeSnapshotFilesBefore so that a) forgetSnapshotsBefore removes any snapshot less that the given snapshot id from snapshots; b) removeSnapshots deletes the enumerated snapshots from forgetSnapshotsBefore.

  3. Change the interface RawSnapshotReader to not extend Closeable since only KafkaMetadataLog is responsible for closing snapshots. FileRawSnapshotReader implements AutoCloseable.

  4. Fixed the implementation of handleFetchSnapshotRequest in KafkaRaftClient so that RawSnapshotReader and the associated FileRecords are not close before sending to the network layer.

More detailed description of your change,
if necessary. The PR title and PR message become
the squashed commit message, so use a separate
comment to ping reviewers.

Summary of testing strategy (including rationale)
for the feature or bug fix. Unit and/or integration
tests are expected for any behaviour change and
system tests should be considered for larger changes.

Committer Checklist (excluded from commit message)

  • Verify design and implementation
  • Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

@jsancio
Copy link
Member Author

jsancio commented Mar 30, 2021

All of the changes are in c7a0a5c. The rest of the changes are included in #10085

@cmccabe cmccabe added the kraft label Mar 30, 2021
@jsancio jsancio force-pushed the kafka-12543-snapshot-ownership branch from c7a0a5c to c7567f2 Compare May 1, 2021 20:17
@jsancio jsancio marked this pull request as ready for review May 3, 2021 17:42
@jsancio
Copy link
Member Author

jsancio commented May 3, 2021

@hachikuji @dengziming @mumrah This PR is ready for review. Thanks!

Copy link
Member

@dengziming dengziming left a comment

Choose a reason for hiding this comment

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

Left some minor questions.

Copy link
Member

@mumrah mumrah left a comment

Choose a reason for hiding this comment

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

Thanks for the patch @jsancio! A few questions and comments inline

latestSnapshotId().asScala match {
case Some(snapshotId) if (snapshotId.epoch > latestEpoch ||
(snapshotId.epoch == latestEpoch && snapshotId.offset > endOffset().offset)) =>
val (truncated, forgottenSnapshots) = latestSnapshotId().asScala match {
Copy link
Member

@mumrah mumrah May 10, 2021

Choose a reason for hiding this comment

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

Should we grab the snapshots lock for this whole match expression like we do in deleteBeforeSnapshot? Is there possible a race between this block and deleteBeforeSnapshot?

Copy link
Member Author

Choose a reason for hiding this comment

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

Synchronizing snapshots is only needed when accessing that object. In deleteBeforeSnapshot it is grabbed because the match expression accesses snapshots in one of the case/branch.

In this method I think it is safe to only grab the log where we currently do.

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@jsancio : Thanks for the PR. Just a few comments below.

// This object needs to be thread-safe because it is used by the snapshotting thread to notify the
// polling thread when snapshots are created.
snapshotIds: ConcurrentSkipListSet[OffsetAndEpoch],
snapshots: mutable.TreeMap[OffsetAndEpoch, Option[FileRawSnapshotReader]],
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the above comment still accurate since snapshots is no longer thread safe?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. I updated the comment. I'll push a commit tomorrow after a few other changes.

try {
Utils.atomicMoveWithFallback(immutablePath, deletedPath, false);
} catch (IOException e) {
log.error("Error renaming snapshot file from {} to {}", immutablePath, deletedPath, e);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we just fail the controller on IOException?

Copy link
Member Author

Choose a reason for hiding this comment

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

@mumrah suggested converting all of the IOException to UncheckedIOException. Kafka doesn't have a precedence of doing that but maybe we should do that going forward. I filed https://issues.apache.org/jira/browse/KAFKA-12773 but I'll change it here to re-throw instead of logging this message.

Copy link
Member Author

Choose a reason for hiding this comment

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

By changing it to UncheckedIOExcpetion this will unwind the stack for the polling thread. Tomorrow, I'll look into how we handle that case but it may already shutdown the broker and controller.

Copy link
Member Author

Choose a reason for hiding this comment

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

Excuse the delay @junrao but I look into this in more detail today. I changed this code to throw an exception instead. This exception will be unhandled by the KafkaRaftClient polling thread in both the broker and controller. This will cause the thread to terminate but I don't think it will cause the JVM process to terminate.

We have the following Jira to revisit our exception handling: https://issues.apache.org/jira/browse/KAFKA-10594. I added a comment there to document the issue you highlighted here. Do you mind if we tackle this problem holistically in that Jira?

try {
fileRecords.close();
} catch (IOException e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we throw KafkaStorageException?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure. I could use some guidance here. I read the documentation for KafkaStorageException: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/errors/KafkaStorageException.java#L19-L30. It looks like Kafka uses KafkaStorageException if the IO is visible to the client.

On the server (broker and controller) this code will be called async by the same scheduler used for deleting log segments. In that case CoreUtils.swallow is used which logs a WARN message. I think we should do the same here.

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@jsancio : Thanks for the updated PR. LGTM. I will wait to see if @mumrah has any other comments.

Copy link
Member

@mumrah mumrah left a comment

Choose a reason for hiding this comment

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

LGTM. Agree that we should revisit the exception handling later on.

@mumrah mumrah merged commit 924c870 into apache:trunk May 18, 2021
@jsancio jsancio deleted the kafka-12543-snapshot-ownership branch May 21, 2021 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants