From 4e5c45cfb449dc07830f41ca7a79c363a141203e Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Mon, 27 Jul 2026 12:22:04 -0400 Subject: [PATCH] Posix memory map advise cache retention policy. --- .../database/impl/memory/mmap_staging.ipp | 19 ++++++++--- .../database/impl/memory/mmap_storage.ipp | 8 +++++ include/bitcoin/database/memory/mman.hpp | 34 +++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/database/impl/memory/mmap_staging.ipp b/include/bitcoin/database/impl/memory/mmap_staging.ipp index a63d202d1..579807026 100644 --- a/include/bitcoin/database/impl/memory/mmap_staging.ipp +++ b/include/bitcoin/database/impl/memory/mmap_staging.ipp @@ -831,10 +831,21 @@ template bool CLASS::settle_write_(size_t from, size_t to, std::index_sequence) NOEXCEPT { - return (pwrite_all(opened_[Index], - std::next(memory_map_[Index], to_width(from)), - to_width(to) - to_width(from), - to_width(from)) && ...); + const auto settle = [this, from, to]() NOEXCEPT + { + const auto offset = to_width(from); + const auto size = to_width(to) - offset; + const auto success = pwrite_all(opened_[Column], + std::next(memory_map_[Column], offset), size, offset); + + // Settled bytes are read only through the map, drop the write cache. + if (success) + ::evict(opened_[Column], offset, size); + + return success; + }; + + return (settle.template operator()() && ...); } #endif // MANAGE_STAGING diff --git a/include/bitcoin/database/impl/memory/mmap_storage.ipp b/include/bitcoin/database/impl/memory/mmap_storage.ipp index e0f0039f0..e06b0768e 100644 --- a/include/bitcoin/database/impl/memory/mmap_storage.ipp +++ b/include/bitcoin/database/impl/memory/mmap_storage.ipp @@ -26,6 +26,7 @@ #include #include #include +#include namespace libbitcoin { namespace database { @@ -80,6 +81,13 @@ code CLASS::open() NOEXCEPT filenames_.at(index), random_)) return ec; + // Staged bytes are read only through the map once settled, so exclude + // descriptor writes from file system caching (macOS otherwise retains + // settled pages in cache, starving the mapped read set under pressure). + if (staged_) + for (const auto& descriptor: opened_) + ::uncache(descriptor); + // logical_ is the shared row count, derived from column 0's byte size. size_t bytes{}; if (const auto ec = file::size_ex(bytes, opened_.front())) diff --git a/include/bitcoin/database/memory/mman.hpp b/include/bitcoin/database/memory/mman.hpp index ea2b1766c..bd26e312a 100644 --- a/include/bitcoin/database/memory/mman.hpp +++ b/include/bitcoin/database/memory/mman.hpp @@ -6,6 +6,7 @@ #include #if !defined(HAVE_MSC) + #include #include #include #include @@ -58,4 +59,37 @@ int fallocate(int fd, int, off_t offset, off_t len) NOEXCEPT; #endif // HAVE_MSC +/// Exclude descriptor read/write from file system caching (advisory, +/// effective only on macOS). Staged bytes are read only through the map +/// once settled, so cache retention of the written pages can only displace +/// more useful pages. +#if defined(HAVE_APPLE) +inline int uncache(int fd) NOEXCEPT +{ + return ::fcntl(fd, F_NOCACHE, 1); +} +#else +inline int uncache(int) NOEXCEPT +{ + return 0; +} +#endif + +/// Drop cached pages of a written range (advisory, effective only on Linux, +/// where uncache is a no-op). The sync makes the range clean so that the +/// eviction is not skipped, which only hastens settled write durability. +#if defined(HAVE_LINUX) +inline int evict(int fd, size_t offset, size_t size) NOEXCEPT +{ + return (::fdatasync(fd) == -1) ? -1 : ::posix_fadvise(fd, + static_cast(offset), static_cast(size), + POSIX_FADV_DONTNEED); +} +#else +inline int evict(int, size_t, size_t) NOEXCEPT +{ + return 0; +} +#endif + #endif