fix(io): attempt every S3 delete and report how many failed - #966
plusplusjiajia wants to merge 5 commits into
Conversation
fee511b to
1f5fb83
Compare
| size_t failed = 0; | ||
| for (const auto& file_location : file_locations) { | ||
| locations_by_io[&FileIOForPath(file_location)].push_back(file_location); | ||
| if (auto status = FileIOForPath(file_location).DeleteFile(file_location); |
There was a problem hiding this comment.
Should we use a thread pool to delete files concurrently? Sequential S3 deletes could be slow for large batches. Java's S3FileIO does that, so maybe we should pursue that too, but not a requirement for this PR.
There was a problem hiding this comment.
@zhjwpku Thanks! Done: deletes now run on up to s3.delete.num-threads threads (default: hardware threads), as in Java. Java also batches keys into DeleteObjects; Arrow has no public batch delete for S3, so each thread deletes one file at a time.
| auto logger = GetCurrentLogger(); | ||
| std::vector<std::future<void>> helpers; | ||
| for (size_t i = 1; i < std::min(delete_threads_, file_locations.size()); ++i) { | ||
| helpers.push_back(std::async(std::launch::async, [&] { |
There was a problem hiding this comment.
We have TaskGroup and Executor abstractions, not sure if they fit it here.
There was a problem hiding this comment.
Maybe not since there is no need to retry here. cc @HuaHuaY
There was a problem hiding this comment.
@zhjwpku Thanks! TaskGroup needs a caller-supplied Executor, which DeleteFiles can't receive, and ExpireSnapshots already retries it. So plain threads here.
ArrowS3FileIO::DeleteFiles grouped locations by credential prefix and returned at the first group that failed, so files in later groups were never attempted. Java's S3FileIO attempts every batch, logs each failed path and reports the failure count. Delete each file through its delegate, log each failure, and return "Failed to delete N of M files" at the end. This sends the same requests as before, since Arrow's S3 DeleteFiles also deletes one file at a time. ResolvingFileIO stays fail-fast across delegates, as it is in Java.
Java's S3FileIO runs its deletes on an executor sized by s3.delete.num-threads, which defaults to the number of processors. DeleteFiles now does the same, with the calling thread taking part. Java also packs keys into DeleteObjects batches. Arrow has no public batch delete for S3, so each thread still deletes one file at a time, as Arrow's own DeleteFiles does.
std::jthread needs -fexperimental-library with libc++ 18 and 19, which the Clang 18+ requirement covers, so use std::async futures instead; they also wait for their thread if an exception unwinds. Helper threads now bind the caller's logger, as logger.h prescribes for thread pools. Without it their warnings went to the global logger while the returned error only carries the failure count.
wait() leaves an exception stored in a helper's future, so a delete that threw was neither counted nor reported, and DeleteFiles could return success. get() rethrows it on the calling thread, as the sequential loop did.
41ff77e to
492c6cb
Compare
Follow-up to #898 (comment).
ArrowS3FileIO::DeleteFilesreturned at the first credential prefix whose delete failed, so files under the remaining prefixes were never attempted. Java'sS3FileIO.deleteFilesattempts every batch on a thread pool, logs each failed path, and throwsBulkDeletionFailureExceptionwith the count.This matches that:
Failed to delete N of M files.s3.delete.num-threadsthreads (default: the number of hardware threads), as in Java.Java also packs keys into
DeleteObjectsbatches. Arrow has no public batch delete for S3, so each thread deletes one file at a time, as Arrow's ownDeleteFilesdoes.ResolvingFileIOstays fail-fast across delegates, as Java's does.DeleteFilesAttemptsEveryFileputs an allowed file between two denied ones; it fails onmainand passes here. This touches the same function as #898, so whichever lands second needs a small rebase.