Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make garbage collecting wal and recovery files faster #5399

Open
wants to merge 2 commits into
base: 2.1
Choose a base branch
from

Conversation

dlmarion
Copy link
Contributor

Uses the same property and technique used for deleting RFiles to delete wal and recovery files.

Closes #5397

Uses the same property and technique used for deleting
RFiles to delete wal and recovery files.

Closes apache#5397
@dlmarion dlmarion added this to the 2.1.4 milestone Mar 11, 2025
@dlmarion dlmarion self-assigned this Mar 11, 2025
@dlmarion
Copy link
Contributor Author

Kicked off IT build

@dlmarion
Copy link
Contributor Author

Full IT build successful

@dlmarion dlmarion marked this pull request as ready for review March 12, 2025 12:08
Copy link
Contributor

@ddanielr ddanielr left a comment

Choose a reason for hiding this comment

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

Changes look good to me.

The only suggestion I have is using a separate GC_WAL_DELETE_THREADS property.

In the current code, user table activity will actively delay WAL cleanup processing as the wals are processed at the end of a GC run cycle before flushing the metadata and root tables.

If the goal is to speed up GC of the wals and recovery files, then I don't see a reason why the WAL logs have to be handled at the end of the GC run cycle vs just always be running as a separate task thread.

GC_DELETE_THREADS is most likely going to be set higher than needed for the WALs since it's based on file activity in the system while the upper limit of WALs should be based on the amount of tservers * tserver.wal.max.referenced and tserver churn (dead/recovered/etc).

If we move to having the WALs GC'd in a separate thread or just pulled into a different GC process, then using a separate property would make it easier to avoid exceeding the max amount of available threads on the server.

} catch (InterruptedException e1) {
log.error("{}", e1.getMessage(), e1);
}
return counter.get();
Copy link
Contributor

Choose a reason for hiding this comment

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

The code was incrementing this deleted counter, should it still do that?

Suggested change
return counter.get();
status.currentLog.deleted += counter.get();
return counter.get();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, I forgot to increment the counter. I created it though, so I was half way there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On second look, counter is being passed to removeFile, and incremented there.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah counter is being incrementing removeFile, but the old code used to increment status.currentLog.deleted which may be used for display on the monitor and logging. Seems like that is no longer incremented.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 7c54495

fs.deleteRecursively(path);
}
counter.incrementAndGet();
} catch (FileNotFoundException ex) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering if we need to handle any other exception types here as an uncaught exception can bubble up and kill the thead pool. It's pretty common to catch all exceptions as a catch all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The previous code just bubbled the RuntimeExceptions up the call stack. I could call submit instead, test the futures, and return any errors up the stack.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you used futures that might be better, you could wait for them to finish instead of relying on the thread pool to shutdown for completion. Either way could work, I just figured I'd mention it as I'm not too familiar with this code but I noticed we were only catching those specific exceptions so was curious if a random runtime exception would cause issues with the new thread pool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to use futures in 7c54495

@dlmarion
Copy link
Contributor Author

The only suggestion I have is using a separate GC_WAL_DELETE_THREADS property.

Added in 7c54495

}

while (!futures.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The get() method in a future waits until its done or failed, so should be able to loop through all futures once calling get.

futures.forEach((path,future)->{
             try {
            future.get();
          } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException("Uncaught exception deleting recovery log file" + path,
                e);
          }
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking the faster I process the ones that are complete and remove from the map, the faster I'm giving back memory to the VM. I probably need to put a wait at the bottom of the loop though. If the first one the iterator returns is the last one submitted, then I would be waiting until they are all done.

}
}
}
deleteThreadPool.shutdown();
Copy link
Contributor

Choose a reason for hiding this comment

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

If an exception is throw the pool would not be shut down.

}
deleteThreadPool.shutdown();
try {
while (!deleteThreadPool.awaitTermination(1000, TimeUnit.MILLISECONDS)) { // empty
Copy link
Contributor

Choose a reason for hiding this comment

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

Could remove this code now that futures are being waited on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Garbage Collecting Write Ahead Logs can be slow for a large number of logs
4 participants