Persistent file handles in VFS write path#5764
Conversation
|
Can you share your benchmark code? How many threads does it use to write files? |
teo-tsirpanis
left a comment
There was a problem hiding this comment.
If we want to fix redundant file opens, we should more comprehensively refactor the VFS write API into something like this, which is much more performant and maintainable:
VFS vfs;
auto writer = vfs.create_writer(uri);
writer.write(buf1, len1);
writer.write(buf2, len2);
writer.write(buf3, len3);
writer.flush();The existence of global1 state in the VFS (and the class design that forces it) is an identified problem of this part of the library, and doubling down on it is not a move in the right direction, and will lead to bugs, performance issues2, and more technical debt down the road.
Footnotes
| void Win::remove_dir(const URI& uri) const { | ||
| auto path = uri.to_path(); | ||
| if (is_dir(uri)) { | ||
| evict_cached_handles(path); |
There was a problem hiding this comment.
This looks like a bad idea, and subverts the operating system's file locking semantics. What would happen if another thread is writing to a file inside the directory at the same time?
| /** State for a file descriptor kept open between write() and flush(). */ | ||
| struct OpenFile { | ||
| int fd; | ||
| uint64_t offset; | ||
| }; |
There was a problem hiding this comment.
The struct should have a destructor (and be un-copiable and immovable).
There was a problem hiding this comment.
The newly added logic should be moved to local.cc as much as currently possible, to reduce duplications across POSIX and Windows.
| for (auto& [path, of] : open_files_) { | ||
| ::close(of.fd); | ||
| } | ||
| open_files_.clear(); |
There was a problem hiding this comment.
| open_files_.clear(); |
No need to clear the map; it's going to be freed very soon either way.
Every
VFS::write()to a local file opened and closed the file descriptor (POSIX) orHANDLE(Windows) on each call. For write patterns with many small sequential writes (e.g., tile-at-a-time ingestion), the syscall overhead adds up significantly.This change introduces persistent file handle caching at the local filesystem layer. The fd/
HANDLEis opened on the firstwrite(), kept alive across subsequent writes to the same path, and onlyfsync()'d and closed onflush(). The destructor cleans up any handles that were not explicitly flushed.Standalone microbenchmark (macOS, Apple Silicon) comparing per-write open/close versus cached file handle:
Measured with 10 trials (3 warmup) writing sequentially to a single file using
pwrite(), comparing per-writeopen()/close()against a single cached fd, withoutfsyncbetween writes (only at finalflush).The performance improvement increases as write size decreases (i.e., when the workload is more syscall-dominated), which matches the typical compressed-tile scenario.
Ref: #5732 (comment)
Closes CORE-497
TYPE: IMPROVEMENT
DESC: Persistent file handles in VFS write path.