Debugging OSD cache thrashing
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to
Hi all, I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right. I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right. I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space). I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned? Here is a perf call trace of the thrashing: the same place ]
- 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
- Hector
The default OSD memory cache size is 4 GB, it’s not recommended to reduce it to such low values, especially if there’s real load on the cluster. I am not a developer, so I can’t really comment on the code. Zitat von Hector Martin <marcan@marcan.st>:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
On 2025/06/22 22:37, Eugen Block wrote:
The default OSD memory cache size is 4 GB, it’s not recommended to reduce it to such low values, especially if there’s real load on the cluster. I am not a developer, so I can’t really comment on the code.
I don't have enough RAM for 4GB per OSD. Nonetheless, this behavior is clearly broken. I also confirmed that `ceph tell osd.X cache drop` also fixes the CPU usage (until the cache fills up again). If something is assuming the cache is at least 4GB when sizing partitions or something like that, then I'd like to know how to tune it (and the default should probably be improved to remove that assumption).
Zitat von Hector Martin <marcan@marcan.st>:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
_______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
- Hector
On 2025/06/22 23:19, Anthony D'Atri wrote:
I don't have enough RAM for 4GB per OSD.
DIMMs are cheap.
No DIMMs on Apple Macs.
You might experiment with the values described here:
docs.ceph.com <https://docs.ceph.com/en/latest/rados/configuration/ bluestore-config-ref/> <https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
<https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
Eg bluestore_cache_size_*
Nonetheless, this behavior is clearly broken.
You’re welcome to submit a PR. Make no mistake, you’re actively disregarding recommendations with unrealistic recommendations.
To submit a PR I first need to figure out what's going on, hence my email. "This is not recommended and performance may suffer" is one thing, "the cache will thrash to death" is another. I don't expect this setup to have ideal performance, but I do expect it not to have completely broken cache behavior, which is what is happening.
I also confirmed that `ceph tell osd.X cache drop` also fixes the CPU usage (until the cache fills up again). If something is assuming the cache is at least 4GB when sizing partitions or something like that, then I'd like to know how to tune it (and the default should probably be improved to remove that assumption).
See above. Please submit a PR that behaves the way you want, with empirical evidence that it does not adversely impact the wide variety of deployments that follow recommendations.
Given how averse you seem to be to even consider improving this use case and dismissive of my report, I'm not particularly inclined to submit a PR at this point. If one of the tunables fixes this for me, I'll just keep the fix to myself and the next person to run into it will have to figure it out for themselves again. *shrug* - Hector
DIMMs are cheap.
No DIMMs on Apple Macs.
You’re running virtualized in VMs or containers, with OSDs, mons, mgr, and the constellation of other daemons with resources dramatically below recommendations. I’ll speculate that at least the HDDs are USB-attached, or perhaps you’re on an old cheese-grater?
You might experiment with the values described here:
docs.ceph.com <https://docs.ceph.com/en/latest/rados/configuration/ bluestore-config-ref/> <https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
<https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
Eg bluestore_cache_size_*
Nonetheless, this behavior is clearly broken.
You’re welcome to submit a PR. Make no mistake, you’re actively disregarding recommendations with unrealistic recommendations.
To submit a PR I first need to figure out what's going on, hence my email.
"This is not recommended and performance may suffer" is one thing, "the cache will thrash to death" is another.
There are countless unsupported configurations. Nobody can predict at that level of precision how an unspecified but decidedly underresourced deployment will behave, especially if presented with snapshots.
I don't expect this setup to have ideal performance, but I do expect it not to have completely broken cache behavior, which is what is happening.
You’re running a deployment whose parameters are mostly undisclosed but clearly not even close to supportable recommendations. Graceful degradation cannot be expected. It’s sorta like filling a diesel truck’s tank with cough syrup and expecting it to only be “a little sluggish”.
Given how averse you seem to be to even consider improving this use case
I’m not averse to anything, but bear in mind that this is free software that thousands of installations use quite successfully. Be the change you want to see in the world. Not everyone here is a developer, and nobody owes anyone anything. "Considering improving" is not up to me. A virtualized sandbox — which this must be because native macOS is not a supported platform — is not a use-case, it’s a sandbox and no expectations whatsoever should be made with respect to performance. I would not be surprised if your systems are swapping, which is doing to exhibit poor performance for any software. I pointed you at tunables to try. On my own time on Sunday morning. You’re welcome.
and dismissive of my report, I'm not particularly inclined to submit a PR at this point. If one of the tunables fixes this for me, I'll just keep the fix to myself and the next person to run into it will have to figure it out for themselves again. *shrug*
If you find a way to transcend math, more power to you. Angrily writing that a complex, mature, FREE system is “broken” because it doesn’t perform miracles when abused is folly, like expecting coffee to not be hot.
On 2025/06/23 0:21, Anthony D'Atri wrote:
DIMMs are cheap.
No DIMMs on Apple Macs.
You’re running virtualized in VMs or containers, with OSDs, mons, mgr, and the constellation of other daemons with resources dramatically below recommendations. I’ll speculate that at least the HDDs are USB-attached, or perhaps you’re on an old cheese-grater?
No, I'm running on bare metal. It's kind of the project I started a few years ago and everything: https://asahilinux.org/ Yes, the HDDs are USB-attached, and me running this Ceph workload has led directly to finding and fixing many years-old Linux kernel USB driver bugs (affecting all platforms, not just funny ones like this one), and even discovering others that haven't been tracked down yet but we're currently debugging. If I hadn't run this "strange" workload, those bugs would have not been found and fixed. I've also helped track down and fix broken Ceph stuff in Fedora as part of all this, but I'm sure you'll say Fedora is also an unsupported distribution. In fact, I even found a *GCC 13 regression* that miscompiled Ceph with this whole experiment: https://tracker.ceph.com/issues/63867 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113359 Should I give up running my "unsupported configuration" and stop finding fixing all these bugs that affect lots of other configurations and deployments of Ceph and non-Ceph things?
You might experiment with the values described here:
docs.ceph.com <https://docs.ceph.com/en/latest/rados/configuration/ bluestore-config-ref/> <https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
<https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
Eg bluestore_cache_size_*
Nonetheless, this behavior is clearly broken.
You’re welcome to submit a PR. Make no mistake, you’re actively disregarding recommendations with unrealistic recommendations.
To submit a PR I first need to figure out what's going on, hence my email.
"This is not recommended and performance may suffer" is one thing, "the cache will thrash to death" is another.
There are countless unsupported configurations. Nobody can predict at that level of precision how an unspecified but decidedly underresourced deployment will behave, especially if presented with snapshots.
In my experience, these kinds of corner case bugs that reproduce on "unsupported" configurations end up hitting supported configurations too, just less often. As I said, "it's slower" and "it thrashes to death after the cache fills up" are two very different things. In fact, I have a production deployment on x86_64 servers with enough RAM currently suffering from heavy perf degradation on snaptrim too. It's on an older Ceph version, which I will upgrade before anything else, but wouldn't it be funny if it turns out it's the same bug? Never mind that sometimes "unsupported" configurations are a fact of life for whatever reason (e.g. during DR). Just because a configuration is "unsupported" doesn't mean problems it uncovers aren't worth fixing or looking at.
I don't expect this setup to have ideal performance, but I do expect it not to have completely broken cache behavior, which is what is happening.
You’re running a deployment whose parameters are mostly undisclosed but clearly not even close to supportable recommendations. Graceful degradation cannot be expected. It’s sorta like filling a diesel truck’s tank with cough syrup and expecting it to only be “a little sluggish”.
Gee, I didn't know tuning a configuration parameter to 60% of its default setting is like running a diesel truck on cough syrup. Heck, the docs say below 2GB is not recommended, and between 2 and 4GB may result in "degraded performance" (I was at 2.4GB): https://docs.ceph.com/en/latest/start/hardware-recommendations/ If anything strictly below 4GB is completely unsupported and expected to go into a thrashing tailspin, perhaps that doc should be updated to state that.
Given how averse you seem to be to even consider improving this use case
I’m not averse to anything, but bear in mind that this is free software that thousands of installations use quite successfully.
Many of which I'm sure are also "unsupported", especially with people running homelabs like this. Again, just because it's outside production parameters doesn't mean stuff should be outright broken.
Be the change you want to see in the world.
That's kind of the point here.
Not everyone here is a developer, and nobody owes anyone anything.
Nobody owes anyone fixing anything, but as a developer I generally do not dismiss performance reports that point to something being pathologically wrong just because the specs don't match recommended values. I'm not asking you to fix it, I'm asking not to be brushed away and told to buy more RAM while I'm trying to figure out what the underlying bug is.
A virtualized sandbox — which this must be because native macOS is not a supported platform — is not a use-case, it’s a sandbox and no expectations whatsoever should be made with respect to performance.
No macOS involved here, no virtualization, no sandboxing, no containers. This is a bare metal ARM64 machine running Fedora 41. It just happens to be made by Apple.
I would not be surprised if your systems are swapping, which is doing to exhibit poor performance for any software.
Two out of three of the systems involved don't even have physical swap configured (they do have the default 8GB zram Fedora provisions, but that's obviously not the problem here because there is plenty of free RAM on those). And as I said I checked systemwide I/O load and saw nothing significant, so if it were swap thrashing it would have been evident.
I pointed you at tunables to try. On my own time on Sunday morning. You’re welcome.
And that is welcome, and it would have been great if it didn't also come with all the dismissiveness.
and dismissive of my report, I'm not particularly inclined to submit a PR at this point. If one of the tunables fixes this for me, I'll just keep the fix to myself and the next person to run into it will have to figure it out for themselves again. *shrug*
If you find a way to transcend math, more power to you.
Angrily writing that a complex, mature, FREE system is “broken” because it doesn’t perform miracles when abused is folly, like expecting coffee to not be hot.
Is there any reason to believe it's not broken besides "I set osd_memory_target to 2.4G"? If this repros with the 4GB setting on a node with enough RAM (one of the three does have as much), then is it broken? If I do track this down to a demonstrable code bug that might affect any configuration, then is it broken? - Hector
Maybe you should ask this additionally on the devs mailing list. Zitat von Hector Martin <marcan@marcan.st>:
On 2025/06/23 0:21, Anthony D'Atri wrote:
DIMMs are cheap.
No DIMMs on Apple Macs.
You’re running virtualized in VMs or containers, with OSDs, mons, mgr, and the constellation of other daemons with resources dramatically below recommendations. I’ll speculate that at least the HDDs are USB-attached, or perhaps you’re on an old cheese-grater?
No, I'm running on bare metal. It's kind of the project I started a few years ago and everything: https://asahilinux.org/
Yes, the HDDs are USB-attached, and me running this Ceph workload has led directly to finding and fixing many years-old Linux kernel USB driver bugs (affecting all platforms, not just funny ones like this one), and even discovering others that haven't been tracked down yet but we're currently debugging. If I hadn't run this "strange" workload, those bugs would have not been found and fixed.
I've also helped track down and fix broken Ceph stuff in Fedora as part of all this, but I'm sure you'll say Fedora is also an unsupported distribution.
In fact, I even found a *GCC 13 regression* that miscompiled Ceph with this whole experiment: https://tracker.ceph.com/issues/63867 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113359
Should I give up running my "unsupported configuration" and stop finding fixing all these bugs that affect lots of other configurations and deployments of Ceph and non-Ceph things?
You might experiment with the values described here:
docs.ceph.com <https://docs.ceph.com/en/latest/rados/configuration/ bluestore-config-ref/> <https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
<https://docs.ceph.com/en/latest/rados/configuration/bluestore-config-ref/>
Eg bluestore_cache_size_*
Nonetheless, this behavior is clearly broken.
You’re welcome to submit a PR. Make no mistake, you’re actively disregarding recommendations with unrealistic recommendations.
To submit a PR I first need to figure out what's going on, hence my email.
"This is not recommended and performance may suffer" is one thing, "the cache will thrash to death" is another.
There are countless unsupported configurations. Nobody can predict at that level of precision how an unspecified but decidedly underresourced deployment will behave, especially if presented with snapshots.
In my experience, these kinds of corner case bugs that reproduce on "unsupported" configurations end up hitting supported configurations too, just less often. As I said, "it's slower" and "it thrashes to death after the cache fills up" are two very different things.
In fact, I have a production deployment on x86_64 servers with enough RAM currently suffering from heavy perf degradation on snaptrim too. It's on an older Ceph version, which I will upgrade before anything else, but wouldn't it be funny if it turns out it's the same bug?
Never mind that sometimes "unsupported" configurations are a fact of life for whatever reason (e.g. during DR). Just because a configuration is "unsupported" doesn't mean problems it uncovers aren't worth fixing or looking at.
I don't expect this setup to have ideal performance, but I do expect it not to have completely broken cache behavior, which is what is happening.
You’re running a deployment whose parameters are mostly undisclosed but clearly not even close to supportable recommendations. Graceful degradation cannot be expected. It’s sorta like filling a diesel truck’s tank with cough syrup and expecting it to only be “a little sluggish”.
Gee, I didn't know tuning a configuration parameter to 60% of its default setting is like running a diesel truck on cough syrup.
Heck, the docs say below 2GB is not recommended, and between 2 and 4GB may result in "degraded performance" (I was at 2.4GB):
https://docs.ceph.com/en/latest/start/hardware-recommendations/
If anything strictly below 4GB is completely unsupported and expected to go into a thrashing tailspin, perhaps that doc should be updated to state that.
Given how averse you seem to be to even consider improving this use case
I’m not averse to anything, but bear in mind that this is free software that thousands of installations use quite successfully.
Many of which I'm sure are also "unsupported", especially with people running homelabs like this. Again, just because it's outside production parameters doesn't mean stuff should be outright broken.
Be the change you want to see in the world.
That's kind of the point here.
Not everyone here is a developer, and nobody owes anyone anything.
Nobody owes anyone fixing anything, but as a developer I generally do not dismiss performance reports that point to something being pathologically wrong just because the specs don't match recommended values. I'm not asking you to fix it, I'm asking not to be brushed away and told to buy more RAM while I'm trying to figure out what the underlying bug is.
A virtualized sandbox — which this must be because native macOS is not a supported platform — is not a use-case, it’s a sandbox and no expectations whatsoever should be made with respect to performance.
No macOS involved here, no virtualization, no sandboxing, no containers. This is a bare metal ARM64 machine running Fedora 41. It just happens to be made by Apple.
I would not be surprised if your systems are swapping, which is doing to exhibit poor performance for any software.
Two out of three of the systems involved don't even have physical swap configured (they do have the default 8GB zram Fedora provisions, but that's obviously not the problem here because there is plenty of free RAM on those). And as I said I checked systemwide I/O load and saw nothing significant, so if it were swap thrashing it would have been evident.
I pointed you at tunables to try. On my own time on Sunday morning. You’re welcome.
And that is welcome, and it would have been great if it didn't also come with all the dismissiveness.
and dismissive of my report, I'm not particularly inclined to submit a PR at this point. If one of the tunables fixes this for me, I'll just keep the fix to myself and the next person to run into it will have to figure it out for themselves again. *shrug*
If you find a way to transcend math, more power to you.
Angrily writing that a complex, mature, FREE system is “broken” because it doesn’t perform miracles when abused is folly, like expecting coffee to not be hot.
Is there any reason to believe it's not broken besides "I set osd_memory_target to 2.4G"?
If this repros with the 4GB setting on a node with enough RAM (one of the three does have as much), then is it broken?
If I do track this down to a demonstrable code bug that might affect any configuration, then is it broken?
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
If anything strictly below 4GB is completely unsupported and expected to go into a thrashing tailspin, perhaps that doc should be updated to state that.
Angrily writing that a complex, mature, FREE system is “broken” because it doesn’t perform miracles when abused is folly, like expecting coffee to not be hot.
Is there any reason to believe it's not broken besides "I set osd_memory_target to 2.4G"?
If this repros with the 4GB setting on a node with enough RAM (one of the three does have as much), then is it broken?
We did not get a clear description of your system, but if we make a parallel to say "fsck on unixes", a common recommendation for those is to have 1G of ram for each TB of disk (or 1M of ram for each G in the old days) in order to be able to build linked lists of files and dirs and whatever fsck does to validate a possibly broken file system. If you - in such a case - have a limited ram system and a huge drive, you can expect a very poor progress where it either needs to swap or might not actually work at all. There is basically no guarantee that fsck will finish if you have too few resources. It is super bad if it doesn't finish when you need to boot, but sometimes the solution is to have enough ram for a certain size of disk (or certain number of files on the disk). So while I'm not certain if your tests did end up at the same place, with a too-large disk for the ram you gave the process, it sounds possible. Though one can run ceph on a single rpi with a usb-disk, its still not recommended to do so, and if developer time is tight I would (like Eugen might have been trying to say) not like for them to spend time fixing rpi-size cluster issues at the expense of other bugs. It might well be actual real bugs, but I'm more happy if they code on 65+ PB cluster bugs than 1-2G ram OSDs (to take a recent example of working at the other end of size limits) because we are running several multi-PB clusters and zero raspberry pi clusters. Over the years, people have tried to run "test" clusters on 1G disks, 5G disks and so on, and they run in to problems and bugs that literally no one with a "real" cluster ever sees. Same there, there exists real bugs in the code based on the OSD assuming it can preallocate several gigs for RocksDB and all that and that is totally fine for everyone that has gone into a store and bought a disk for the last 10+ years, all those disks have "a few G to spare". The machines that do not have this are basically not fit for running clusters, either being rpis trying to run on smartcards or VMs given too few resources. There are other options for file serving on such small machines, but being a part of ceph clusters might not be in that list. -- May the most significant bit of your life be positive.
On 6/22/25 18:25, Hector Martin wrote:
On 2025/06/23 0:21, Anthony D'Atri wrote:
DIMMs are cheap.
No DIMMs on Apple Macs.
You’re running virtualized in VMs or containers, with OSDs, mons, mgr, and the constellation of other daemons with resources dramatically below recommendations. I’ll speculate that at least the HDDs are USB-attached, or perhaps you’re on an old cheese-grater?
No, I'm running on bare metal. It's kind of the project I started a few years ago and everything: https://asahilinux.org/
Yes, the HDDs are USB-attached, and me running this Ceph workload has led directly to finding and fixing many years-old Linux kernel USB driver bugs (affecting all platforms, not just funny ones like this one), and even discovering others that haven't been tracked down yet but we're currently debugging. If I hadn't run this "strange" workload, those bugs would have not been found and fixed.
I've also helped track down and fix broken Ceph stuff in Fedora as part of all this, but I'm sure you'll say Fedora is also an unsupported distribution.
In fact, I even found a *GCC 13 regression* that miscompiled Ceph with this whole experiment: https://tracker.ceph.com/issues/63867 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113359
Should I give up running my "unsupported configuration" and stop finding fixing all these bugs that affect lots of other configurations and deployments of Ceph and non-Ceph things?
Please not, if you ask me. If we want to learn from other communities, the OpenBSD project supports many different architectures / platforms which has helped them to uncover machine-independent bugs that might affect other systems under the "right" conditions. Fixing those helps the entire ecosystem. Ceph is considered the "Swiss army knife of storage". To try to advance beyond what is currently deemed possible with Ceph, we need to push its boundaries. For some users / use cases this might be to have Ceph scale to ever-larger clusters. But for others this might be to try make Ceph work on ever-smaller hardware. And both might be useful, and benefit each other. Work is done to make Ceph more CPU efficient (CRIMSON project). This work would allow lower end systems to make use of Ceph where they might currently not. Not sure if memory efficiency is also part of their goals, but I'm sure it will be taken into consideration (to allow for drop-in replacement). It also helps to run bigger and faster on large systems (as it is their main goal). Expanding the range of applications for Ceph storage systems will contribute to the growth of the community, which benefits everyone involved. My 2 cents, Gr. Stefan
On Sun, Jun 22, 2025, 8:52 AM Hector Martin <marcan@marcan.st> wrote:
I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
For maybe a quick fix or lead, I'd start by disabling bluefs_buffered_io. This tunable has a strange history of being turned in and off againin Ceph's past with what I believe you've been describing as the reason. If anything, it may provide credence to what you're seeing. If terms of getting stats of individual memory pools, you can tell the OSD to dump of stats and from that you discern memory allocations of individual pools/use cases which might help for the situation you're describing given there are so many components and which make up an OSD (rockdbs, etc.) and subsystems that tend to hold on to memory for their respective use cases (i.e. they hold onto the memory in local free list slabs and don't return it to tcmalloc necessarily). I've personally also had problems with the kernel swapping out to disk when I had plenty of free memory, only to realize it was certain NUMA zones that were causing the effect. I have no clue if Apple Silicon has NUMA zones, but also a variable you can control for by pinning OSDs to specific NUMA zones for a bit if it does. Cheers, Tyler
Hi Hector, Sorry I'm a bit late to the party on this one. I wrote the OSD memory autotuning code and am probably one of the most recent people to really dig in and refactor bluestore's caches. I'll respond inline below. On 6/22/25 05:51, Hector Martin wrote:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right. I suspect that what you are seeing here are RocksDB reads, since we allow those to come from page cache. We have a long standing issue where RocksDB was repeatedly re-reading the same blocks for readahead when data isn't in the block cache and buffered IO is the crutch basically allowing it to perform well. Tyler Stachecki mentioned the bluefs_buffered_io setting and that indeed is directly related to this issue and why we''ve flip-flopped on whether to leave it enabled. This is a bit of a distraction imho though, see below.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
In bluestore itself we have two primary caches: The onode cache and the buffer cache. Onode cache stores the metadata about objects while buffer cache stores object data. A cache miss in either of these will result in data being read, but onode cache misses are far more expensive because it requires extra work to go fetch the data from RocksDB (even if the data is stored in RocksDB's block cache!) It may even result in extra reads of the SST files if the data can't easily be found in early RocksDB levels. To me, what you are seeing sounds like severe onode cache misses, potentially combined with additional read throughput overhead due to cache thrashing during compaction if snaptrimming is resulting in a lot of writes or deletes (combined with listings).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
Just to start, the onode cache hit and miss rates in the OSD performance counter is a good place to start. As Anthony mentioned, things tend not to work very well once you drop below ~1.5-2GB of memory. There are some tricks you can play that may help (reducing various buffer sizes, pglog lengths, RocksDB WAL buffer sizes and counts, etc). The honest truth though is that Ceph is incredibly sensitive to onode cache missses, especially if the backend storage is not very fast.
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
Note how much of the perf call trace is spent in RocksDB fetching blocks as a result of trying to load BlueStore shared_blobs via onodes. Presumably your onode hit ratio is very low, and bluestore is trying to fetch a significant ratio of the onodes from RocksDB, which itself likely has very little block cache, so each onode read is coming from RocksDB, which is likely trying to fetch a block from disk, putting it in the block cache, but then may be immediately discarding it if there is very little memory for the next block. One thing that's missing here is a description of the workload that is happening between snapshots. Are you doing a lot of IO that could result in severe fragmentation between snapshots? Thanks, Mark
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
-- Best Regards, Mark Nelson Head of Research and Development Clyso GmbH p: +49 89 21552391 12 | a: Minnesota, USA w: https://clyso.com | e: mark.nelson@clyso.com We are hiring: https://www.clyso.com/jobs/
Hi Hector, Just as a follow-up, Here are the comments I mentioned in the ceph slack channel from the PR where we re-enabled bluestore_buffered_io. I recorded these notes back when aclamk and I were digging into the RocksDB code to see if there was anything we could do to improve the situation. They may be valuable if you want to dig into the RocksDB side of what is going on. https://github.com/ceph/ceph/pull/38044#issuecomment-789192720 https://github.com/ceph/ceph/pull/38044#issuecomment-790157415 In the end we never solved the problem afaik. We still rely on bluestore_buffered_io and the linux page cache to hide excessive disk reads (especially the repeated prefetch reads mentioned in the PR!) when the block cache misses. In your case, with very limited memory and potentially limited page cache, we are likely hitting RocksDB exceptionally hard for metadata lookups and there's no guarantee that the page cache itself isn't thrashing as well when the block cache misses. If that's the case, the bluestore_buffered_io "fix" would not be able to mitigate the repeated prefetch reads we observed. That may be related to what you are observing in your setup as well. Mark On 6/24/25 08:06, Mark Nelson wrote:
Hi Hector,
Sorry I'm a bit late to the party on this one. I wrote the OSD memory autotuning code and am probably one of the most recent people to really dig in and refactor bluestore's caches. I'll respond inline below.
On 6/22/25 05:51, Hector Martin wrote:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right. I suspect that what you are seeing here are RocksDB reads, since we allow those to come from page cache. We have a long standing issue where RocksDB was repeatedly re-reading the same blocks for readahead when data isn't in the block cache and buffered IO is the crutch basically allowing it to perform well. Tyler Stachecki mentioned the bluefs_buffered_io setting and that indeed is directly related to this issue and why we''ve flip-flopped on whether to leave it enabled. This is a bit of a distraction imho though, see below.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
In bluestore itself we have two primary caches: The onode cache and the buffer cache. Onode cache stores the metadata about objects while buffer cache stores object data. A cache miss in either of these will result in data being read, but onode cache misses are far more expensive because it requires extra work to go fetch the data from RocksDB (even if the data is stored in RocksDB's block cache!) It may even result in extra reads of the SST files if the data can't easily be found in early RocksDB levels.
To me, what you are seeing sounds like severe onode cache misses, potentially combined with additional read throughput overhead due to cache thrashing during compaction if snaptrimming is resulting in a lot of writes or deletes (combined with listings).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
Just to start, the onode cache hit and miss rates in the OSD performance counter is a good place to start. As Anthony mentioned, things tend not to work very well once you drop below ~1.5-2GB of memory. There are some tricks you can play that may help (reducing various buffer sizes, pglog lengths, RocksDB WAL buffer sizes and counts, etc). The honest truth though is that Ceph is incredibly sensitive to onode cache missses, especially if the backend storage is not very fast.
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
Note how much of the perf call trace is spent in RocksDB fetching blocks as a result of trying to load BlueStore shared_blobs via onodes. Presumably your onode hit ratio is very low, and bluestore is trying to fetch a significant ratio of the onodes from RocksDB, which itself likely has very little block cache, so each onode read is coming from RocksDB, which is likely trying to fetch a block from disk, putting it in the block cache, but then may be immediately discarding it if there is very little memory for the next block.
One thing that's missing here is a description of the workload that is happening between snapshots. Are you doing a lot of IO that could result in severe fragmentation between snapshots?
Thanks,
Mark
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
-- Best Regards, Mark Nelson Head of Research and Development Clyso GmbH p: +49 89 21552391 12 | a: Minnesota, USA w: https://clyso.com | e: mark.nelson@clyso.com We are hiring: https://www.clyso.com/jobs/
Hi Mark, Thanks a lot for the pointers and info, it's really helpful. Since the issue is happening in a live cluster (which is a homelab I can screw around with to an extent, but not take down for very long periods of time or lose data in), and since I don't have a lot of spare hours in the coming weeks, I think this will probably be an investigation on the longer side of things. So don't expect fast follow-ups, but rest assured this is now on my radar and I'll be poking at it and seeing what I can figure out. Re the workload between snapshots, that's a bit hard to pin down right now since I did some shuffling around and the snapshot deletions were from different subtrees. At the time I saw this I had just wiped all my snapshots in the cluster, ~2 weeks worth of daily snapshots in the largest subtree, adding up to just over a terabyte of raw cluster storage being freed going by stats graphs (5+4 EC, 8MB object CephFS, probably mostly large files and full-size objects). In fact I just took out the vast majority of that data from the tree that gets snapshotted (that really shouldn't have been there); if it turns out I can't repro after that I'm happy to revert it back though (but it would take another 2 weeks to recreate the exact scenario, if it comes to that). I also just deleted a legacy pool, which at the time was involved in the snaptrim but had only negligible data actually being snapshotted/deleted as part of it. Again, if it comes to that to make things repro, I don't mind recreating it and trying to force the same scenario. FWIW, the setup here is 3 nodes: - M1 Mac Mini (4p4e), 16GB RAM, OSDs + Mon - M2 Mac Mini (4p4e), 24GB RAM, OSDs + Mon + MDS + monitoring (prom) - M2 Pro Mac Mini (6p4e), 32GB RAM, OSDs + Mon + MDS + a bunch of other stuff Each node has 4x 16TB HDDs and 1x 1TB NVMe SSD, attached via USB (10G, except 5G for the HDDs on one node because reasons). The HDD block.db volumes are on internal Mac NVMe storage (recent change, just before the snapshot story; yes, I did force migrate the data over so there should be no DB files left on the HDDs). SSDs don't have separate block.db. 10GbE networking for all nodes. I have no reason to suspect any hardware/driver related issues relevant to the snaptrim story (I've had a few of those but I keep an eye out for it, and the current setup should be stable). One issue is that this may be hard to repro on demand. If I'm lucky I can just instantly create and delete a pile of snapshots (without much changes in between) and repro, but I might not be lucky. I'd usually debug this by increasing logging verbosity and/or adding my own debug logs, but if this ends up requiring days or weeks of real snapshots to repro, that's going to get old fast. My best idea right now if it comes to that is to attach gdb to an OSD currently experiencing the issue, let the cluster mark it down in the meantime, and hope I can step through the code and find out what the thrashing situation is before it notices it got marked down and starts doing something else. As I mentioned on Slack, one curious thing about my case is that it seems to have been doing around ~100MB reads from BlueFS (probably entire sst files?), but they *were* being satisfied from page cache, and despite that the CPU time was almost all spent copying the data in RAM in the kernel. So that suggests RocksDB wasn't actually *doing* almost anything with the data (other than checksumming it, the only other place I saw CPU usage), not even parsing through it once. It was just reading it and throwing it away repeatedly, or at least that's what it looks like to me. So I hope higher debug + gdb will point at where this behavior comes from, and whether it even makes any sense or it's just a bug. Same with the block cache not working as intended. I do want to ask whether there are any debug/scaffolding tools to possibly try to reproduce the RocksDB workload. I can't easily bring up a full OSD outside the cluster with all the data (it's not impossible, but a 16TB HDD is kind of a pain to copy around), but I do have block.db in a separate volume. If there is some way to bring up that BlueFS stand-alone, and then attempt to recreate the RocksDB workload (e.g. by logging it from the real OSD), that might lead to a really handy reproducer. I don't mind writing my own code, just want to know if there's a good starting place for that or any existing tooling I could jumpstart from. Cheers, - Hector On 2025/06/25 7:48, Mark Nelson wrote:
Hi Hector,
Just as a follow-up,
Here are the comments I mentioned in the ceph slack channel from the PR where we re-enabled bluestore_buffered_io. I recorded these notes back when aclamk and I were digging into the RocksDB code to see if there was anything we could do to improve the situation. They may be valuable if you want to dig into the RocksDB side of what is going on.
https://github.com/ceph/ceph/pull/38044#issuecomment-789192720
https://github.com/ceph/ceph/pull/38044#issuecomment-790157415
In the end we never solved the problem afaik. We still rely on bluestore_buffered_io and the linux page cache to hide excessive disk reads (especially the repeated prefetch reads mentioned in the PR!) when the block cache misses. In your case, with very limited memory and potentially limited page cache, we are likely hitting RocksDB exceptionally hard for metadata lookups and there's no guarantee that the page cache itself isn't thrashing as well when the block cache misses. If that's the case, the bluestore_buffered_io "fix" would not be able to mitigate the repeated prefetch reads we observed. That may be related to what you are observing in your setup as well.
Mark
On 6/24/25 08:06, Mark Nelson wrote:
Hi Hector,
Sorry I'm a bit late to the party on this one. I wrote the OSD memory autotuning code and am probably one of the most recent people to really dig in and refactor bluestore's caches. I'll respond inline below.
On 6/22/25 05:51, Hector Martin wrote:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right. I suspect that what you are seeing here are RocksDB reads, since we allow those to come from page cache. We have a long standing issue where RocksDB was repeatedly re-reading the same blocks for readahead when data isn't in the block cache and buffered IO is the crutch basically allowing it to perform well. Tyler Stachecki mentioned the bluefs_buffered_io setting and that indeed is directly related to this issue and why we''ve flip-flopped on whether to leave it enabled. This is a bit of a distraction imho though, see below.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
In bluestore itself we have two primary caches: The onode cache and the buffer cache. Onode cache stores the metadata about objects while buffer cache stores object data. A cache miss in either of these will result in data being read, but onode cache misses are far more expensive because it requires extra work to go fetch the data from RocksDB (even if the data is stored in RocksDB's block cache!) It may even result in extra reads of the SST files if the data can't easily be found in early RocksDB levels.
To me, what you are seeing sounds like severe onode cache misses, potentially combined with additional read throughput overhead due to cache thrashing during compaction if snaptrimming is resulting in a lot of writes or deletes (combined with listings).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
Just to start, the onode cache hit and miss rates in the OSD performance counter is a good place to start. As Anthony mentioned, things tend not to work very well once you drop below ~1.5-2GB of memory. There are some tricks you can play that may help (reducing various buffer sizes, pglog lengths, RocksDB WAL buffer sizes and counts, etc). The honest truth though is that Ceph is incredibly sensitive to onode cache missses, especially if the backend storage is not very fast.
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
Note how much of the perf call trace is spent in RocksDB fetching blocks as a result of trying to load BlueStore shared_blobs via onodes. Presumably your onode hit ratio is very low, and bluestore is trying to fetch a significant ratio of the onodes from RocksDB, which itself likely has very little block cache, so each onode read is coming from RocksDB, which is likely trying to fetch a block from disk, putting it in the block cache, but then may be immediately discarding it if there is very little memory for the next block.
One thing that's missing here is a description of the workload that is happening between snapshots. Are you doing a lot of IO that could result in severe fragmentation between snapshots?
Thanks,
Mark
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
Hi Hector, Responses inline below. On 6/24/25 10:11 PM, Hector Martin wrote:
Hi Mark,
Thanks a lot for the pointers and info, it's really helpful. Glad to help, and thanks for looking into it. If we can figure out how to disable bluefs_buffered_io without repercussions, I think it would be a decent win overall.
Since the issue is happening in a live cluster (which is a homelab I can screw around with to an extent, but not take down for very long periods of time or lose data in), and since I don't have a lot of spare hours in the coming weeks, I think this will probably be an investigation on the longer side of things. So don't expect fast follow-ups, but rest assured this is now on my radar and I'll be poking at it and seeing what I can figure out.
Re the workload between snapshots, that's a bit hard to pin down right now since I did some shuffling around and the snapshot deletions were from different subtrees. At the time I saw this I had just wiped all my snapshots in the cluster, ~2 weeks worth of daily snapshots in the largest subtree, adding up to just over a terabyte of raw cluster storage being freed going by stats graphs (5+4 EC, 8MB object CephFS, probably mostly large files and full-size objects). In fact I just took out the vast majority of that data from the tree that gets snapshotted (that really shouldn't have been there); if it turns out I can't repro after that I'm happy to revert it back though (but it would take another 2 weeks to recreate the exact scenario, if it comes to that).
A couple of potential hints here: First, 5+4 erasure coding. That means you have 9 chunks per object, each of which will have its own metadata in RocksDB. The larger 8MB CephFS objects may help offset this to some extent, but EC does tend to have higher overhead and especially might if you have a lot of small partial writes and snapshots. The second is having just wiped away all of your snapshots when you first saw the issue. That would trigger the creation of lots of tombstones in rocksdb that would only get cleaned up with subsequent compaction. This is just conjecture, but I wonder if you could have ended up with a bunch of tombstones in L0 that had to be read and iterated over if those SST files were evicted from the block cache regularly before a full compaction across the other levels took place.
I also just deleted a legacy pool, which at the time was involved in the snaptrim but had only negligible data actually being snapshotted/deleted as part of it. Again, if it comes to that to make things repro, I don't mind recreating it and trying to force the same scenario.
Same as above, just more tombstone insertion into RocksDB until compaction takes place.
FWIW, the setup here is 3 nodes: - M1 Mac Mini (4p4e), 16GB RAM, OSDs + Mon - M2 Mac Mini (4p4e), 24GB RAM, OSDs + Mon + MDS + monitoring (prom) - M2 Pro Mac Mini (6p4e), 32GB RAM, OSDs + Mon + MDS + a bunch of other stuff
Each node has 4x 16TB HDDs and 1x 1TB NVMe SSD, attached via USB (10G, except 5G for the HDDs on one node because reasons). The HDD block.db volumes are on internal Mac NVMe storage (recent change, just before the snapshot story; yes, I did force migrate the data over so there should be no DB files left on the HDDs). SSDs don't have separate block.db. 10GbE networking for all nodes. I have no reason to suspect any hardware/driver related issues relevant to the snaptrim story (I've had a few of those but I keep an eye out for it, and the current setup should be stable).
No real comment on the HW other than that it is what it is. The behaviors are the interesting bit here imho!
One issue is that this may be hard to repro on demand. If I'm lucky I can just instantly create and delete a pile of snapshots (without much changes in between) and repro, but I might not be lucky. I'd usually debug this by increasing logging verbosity and/or adding my own debug logs, but if this ends up requiring days or weeks of real snapshots to repro, that's going to get old fast. My best idea right now if it comes to that is to attach gdb to an OSD currently experiencing the issue, let the cluster mark it down in the meantime, and hope I can step through the code and find out what the thrashing situation is before it notices it got marked down and starts doing something else.
If you'd like, you can try my wallclock profiler. This was inspired by poor-mans-profiling via gdb. Just be aware that the libdw backend is kind of buggy and sometimes the callgraph ends up with misplaced bits. Libunwind is slower but more reliable: https://github.com/markhpc/uwpmp Just attach it to one of your OSD processes and you'll get an ascii callgraph of where time is being spent.
As I mentioned on Slack, one curious thing about my case is that it seems to have been doing around ~100MB reads from BlueFS (probably entire sst files?), but they *were* being satisfied from page cache, and despite that the CPU time was almost all spent copying the data in RAM in the kernel. So that suggests RocksDB wasn't actually *doing* almost anything with the data (other than checksumming it, the only other place I saw CPU usage), not even parsing through it once. It was just reading it and throwing it away repeatedly, or at least that's what it looks like to me. So I hope higher debug + gdb will point at where this behavior comes from, and whether it even makes any sense or it's just a bug. Same with the block cache not working as intended.
This is consistent with the behavior we've observed during prefetching, though usually a small amount of the data was used while the rest was thrown away. We hypothesized at the time that perhaps RocksDB is relying on some characteristic of the posix backend that hides this behavior and its being exposed via bluefs in BlueStore, but I don't think we ever came to any conclusion there.
I do want to ask whether there are any debug/scaffolding tools to possibly try to reproduce the RocksDB workload. I can't easily bring up a full OSD outside the cluster with all the data (it's not impossible, but a 16TB HDD is kind of a pain to copy around), but I do have block.db in a separate volume. If there is some way to bring up that BlueFS stand-alone, and then attempt to recreate the RocksDB workload (e.g. by logging it from the real OSD), that might lead to a really handy reproducer. I don't mind writing my own code, just want to know if there's a good starting place for that or any existing tooling I could jumpstart from.
I don't think there's anything quite like what you want, though Igor or Adam might have better advice. Ceph-bluestore-tool is probably the closest. You can however test a ceph objectstore directly via fio: https://github.com/ceph/ceph/blob/main/src/test/fio/README.md It might be tough to replicate the kind of behaviors you are observing with CephFS snapshots using fio alone though. It's still an open question of whether or not the snapshots themselves really matter, or if its just a question of throwing a bunch of metadata at RocksDB on top of bluefs and then deleting/fetching it aggressively. Mark
Cheers, - Hector
On 2025/06/25 7:48, Mark Nelson wrote:
Hi Hector,
Just as a follow-up,
Here are the comments I mentioned in the ceph slack channel from the PR where we re-enabled bluestore_buffered_io. I recorded these notes back when aclamk and I were digging into the RocksDB code to see if there was anything we could do to improve the situation. They may be valuable if you want to dig into the RocksDB side of what is going on.
https://github.com/ceph/ceph/pull/38044#issuecomment-789192720
https://github.com/ceph/ceph/pull/38044#issuecomment-790157415
In the end we never solved the problem afaik. We still rely on bluestore_buffered_io and the linux page cache to hide excessive disk reads (especially the repeated prefetch reads mentioned in the PR!) when the block cache misses. In your case, with very limited memory and potentially limited page cache, we are likely hitting RocksDB exceptionally hard for metadata lookups and there's no guarantee that the page cache itself isn't thrashing as well when the block cache misses. If that's the case, the bluestore_buffered_io "fix" would not be able to mitigate the repeated prefetch reads we observed. That may be related to what you are observing in your setup as well.
Mark
On 6/24/25 08:06, Mark Nelson wrote:
Hi Hector,
Sorry I'm a bit late to the party on this one. I wrote the OSD memory autotuning code and am probably one of the most recent people to really dig in and refactor bluestore's caches. I'll respond inline below.
On 6/22/25 05:51, Hector Martin wrote:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right. I suspect that what you are seeing here are RocksDB reads, since we allow those to come from page cache. We have a long standing issue where RocksDB was repeatedly re-reading the same blocks for readahead when data isn't in the block cache and buffered IO is the crutch basically allowing it to perform well. Tyler Stachecki mentioned the bluefs_buffered_io setting and that indeed is directly related to this issue and why we''ve flip-flopped on whether to leave it enabled. This is a bit of a distraction imho though, see below.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space). In bluestore itself we have two primary caches: The onode cache and the buffer cache. Onode cache stores the metadata about objects while buffer cache stores object data. A cache miss in either of these will result in data being read, but onode cache misses are far more expensive because it requires extra work to go fetch the data from RocksDB (even if the data is stored in RocksDB's block cache!) It may even result in extra reads of the SST files if the data can't easily be found in early RocksDB levels.
To me, what you are seeing sounds like severe onode cache misses, potentially combined with additional read throughput overhead due to cache thrashing during compaction if snaptrimming is resulting in a lot of writes or deletes (combined with listings).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned? Just to start, the onode cache hit and miss rates in the OSD performance counter is a good place to start. As Anthony mentioned, things tend not to work very well once you drop below ~1.5-2GB of memory. There are some tricks you can play that may help (reducing various buffer sizes, pglog lengths, RocksDB WAL buffer sizes and counts, etc). The honest truth though is that Ceph is incredibly sensitive to onode cache missses, especially if the backend storage is not very fast.
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user Note how much of the perf call trace is spent in RocksDB fetching blocks as a result of trying to load BlueStore shared_blobs via onodes. Presumably your onode hit ratio is very low, and bluestore is trying to fetch a significant ratio of the onodes from RocksDB, which itself likely has very little block cache, so each onode read is coming from RocksDB, which is likely trying to fetch a block from disk, putting it in the block cache, but then may be immediately discarding it if there is very little memory for the next block.
One thing that's missing here is a description of the workload that is happening between snapshots. Are you doing a lot of IO that could result in severe fragmentation between snapshots?
Thanks,
Mark
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
-- Best Regards, Mark Nelson Head of Research and Development Clyso GmbH p: +49 89 21552391 12 | a: Minnesota, USA w: https://clyso.com | e: mark.nelson@clyso.com We are hiring: https://www.clyso.com/jobs/
For those who have been following along, I figured it out. I left all the details with Mark on Slack, but TL;DR: The fix is *either one* (or both works too) of these: ceph config set osd rocksdb_cache_index_and_filter_blocks false (Ceph default: true, RocksDB default: false) ceph config set osd rocksdb_cache_shard_bits 0 (or 1, Ceph default: 4, RocksDB default: variable but probably 6) (Restart all OSDs after changing these settings for them to take effect, I don't think they apply live) The side effect of the first option is that it might increase unaccounted heap OSD memory usage (not managed by the cache autosize code), as filter blocks are preloaded and cached outside the block cache. The side effect of the second option is that it reduces the parallelism/core scalability of the block cache. I suspect the impact of either will be generally small for most typical deployments, and the benefit of not having horrible snaptrim thrashing far outweighs it for those affected. Both options were changed from their RocksDB defaults in Ceph commits long ago without any explanation / relevant commit messages, so I suspect this is a case of "some people flipped some knobs that seemed harmless and nobody really tested their impact". Other things have changed since then too, so I can't say when the impact likely began, as there are a lot of other factors involved. The root cause is that the sharding defaults to 16 shards, and that filter blocks are cached in the block cache instead of separately (not the RocksDB default). Filter blocks can be up to 17MB or thereabouts, which means with 16 shards, the minimum viable KV block cache size is ~272MB to ensure non-pathological behavior (if a filter block does not fit into its cache shard, it is not cached at all, causing the problem). Since snaptrim uses a lot of memory, it squeezes out the OSD caches and as soon as the RocksDB block cache size drops below that size or so (depending on how large filter blocks you ended up with on a particular OSD), you get pathological thrashing with potentially several gigabytes per second of kernel->user memory copies repeatedly rereading filter blocks from SSTs. Fixing this *might* mean that bluefs_buffered_io can be flipped to false too. As for who is likely affected, it depends on snaptrim memory usage vs. osd_memory_target. If you can guarantee 1GB or so (rough) for managed caches, then it might never affect you, though it still depends on what PriCache wants to do and it might still squeeze the KV cache under some other set of conditions. With the default of 4G for osd_memory_target, my guess is things are just about on the edge of being safe enough most of the time, which is probably why most people don't see things go horribly wrong. If you increase osd_memory_target above the default, you're probably safe. If you decrease it, you're in danger. Either of the above settings makes it impossible for this pathological situation to occur. rocksdb_cache_index_and_filter_blocks=false is safest, rocksdb_cache_shard_bits=0 could allow for some thrashing if multiple SSTs are involved, rocksdb_cache_shard_bits=1 is borderline, but neither should allow for the extreme pathological behavior where a single thread thrashes reads repeatedly at extreme speeds in any case (which is what I experienced). - Hector On 2025/06/22 21:51, Hector Martin wrote:
Hi all,
I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU usage and cluster performance plummets. Most of the CPU usage was accounted as "system", while actual disk I/O usage was low, so that didn't sound right.
I perf traced the system, and found that most of the usage is in __arch_copy_to_user in the kernel, in read() syscalls. That sounds like the OSD is thrashing buffered block device reads which the kernel satisfies from the page cache (hence no real I/O load, most of the CPU usage is from data copies), so it's repeatedly reading the same disk blocks, which doesn't sound right.
I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph config`, and CPU usage immediately dropped to near zero. However, waiting a bit eventually the CPU thrashing returns after memory usage increases. Restarting an OSD has a similar effect. I believe that something is wrong with the OSD bluestore cache allocation/flush policy, and when the cache becomes full it starts thrashing reads instead of evicting colder cached data (or perhaps some cache bucket is starving another cache bucket of space).
I would appreciate some hints on how to debug this. Are there any cache stats I should be looking at, or info on how the cache is partitioned?
Here is a perf call trace of the thrashing:
- OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref - 88.25% rocksdb::BlockFetcher::ReadBlockContents() [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli [ note: at this level the call trace splits into 4, but it all leads to the same place ] - 35.99% 0xaaaac0992e40 BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) - 35.98% __libc_pread - el0_svc - invoke_syscall - 35.97% __arm64_sys_pread64 - 35.96% vfs_read - blkdev_read_iter - 35.93% filemap_read - 35.28% copy_page_to_iter 35.01% __arch_copy_to_user
- Hector _______________________________________________ ceph-users mailing list -- ceph-users@ceph.io To unsubscribe send an email to ceph-users-leave@ceph.io
- Hector
Congrats on figuring this out Hector! This is a huge find! Comments below. On 8/11/25 4:31 AM, Hector Martin wrote: > For those who have been following along, I figured it out. I left all > the details with Mark on Slack, but TL;DR: The fix is *either one* (or > both works too) of these: > > ceph config set osd rocksdb_cache_index_and_filter_blocks false > (Ceph default: true, RocksDB default: false) > ceph config set osd rocksdb_cache_shard_bits 0 > (or 1, Ceph default: 4, RocksDB default: variable but probably 6) > > (Restart all OSDs after changing these settings for them to take effect, > I don't think they apply live) > > The side effect of the first option is that it might increase > unaccounted heap OSD memory usage (not managed by the cache autosize > code), as filter blocks are preloaded and cached outside the block > cache. The side effect of the second option is that it reduces the > parallelism/core scalability of the block cache. I suspect the impact of > either will be generally small for most typical deployments, and the > benefit of not having horrible snaptrim thrashing far outweighs it for > those affected. > > Both options were changed from their RocksDB defaults in Ceph commits > long ago without any explanation / relevant commit messages, so I > suspect this is a case of "some people flipped some knobs that seemed > harmless and nobody really tested their impact". Other things have > changed since then too, so I can't say when the impact likely began, as > there are a lot of other factors involved. I'll take ownership of that one since I was the one that flipped those knobs. ;) FWIW, a lot of the discussion around these issues was happening back in the bluestore standup in those days. I recall that the idea behind these changes is that we didn't want index and filter blocks to be able to consume memory outside the context of the cache. IE at the time we were concerned with runaway memory usage of the OSD, so having index and filter blocks cached with high (pinned!) priority in the block cache was preferable to having memory allocated for them without bound and no oversight in RocksDB. The testing we did at the time didn't involve running in scenarios where the RocksDB cache size was this small. We didn't have the osd_memory_target autotuning back then and the cache size was a static ratio of the overall bluestore cache size. Back in those days we did regularly see the OSD swings in memory usage depending on the workload. It seems to me this is really a combination of high memory pressure forcing the RocksDB block cache to shrink to the minimum 64MB, having 16 cache shards, and forcing index/filter blocks into the cache that leads to the bad behavior. > > The root cause is that the sharding defaults to 16 shards, and that > filter blocks are cached in the block cache instead of separately (not > the RocksDB default). Filter blocks can be up to 17MB or thereabouts, > which means with 16 shards, the minimum viable KV block cache size is > ~272MB to ensure non-pathological behavior (if a filter block does not > fit into its cache shard, it is not cached at all, causing the problem). > Since snaptrim uses a lot of memory, it squeezes out the OSD caches and > as soon as the RocksDB block cache size drops below that size or so > (depending on how large filter blocks you ended up with on a particular > OSD), you get pathological thrashing with potentially several gigabytes > per second of kernel->user memory copies repeatedly rereading filter > blocks from SSTs. My thought is that unless testing says us otherwise, we probably should drop the shard count down to around 4 and bump up the minimum cache allocation a bit. We could disable storing index/filter blocks in the block cache and now the osd_memory_target code will attempt to compensate for it (which we didn't have before), but it feels a bit like going backwards (ideally we would be accounting for all significant memory usage in the OSD). > > Fixing this *might* mean that bluefs_buffered_io can be flipped to false > too. This is one of the most exciting aspects of this discovery imho! > > As for who is likely affected, it depends on snaptrim memory usage vs. > osd_memory_target. If you can guarantee 1GB or so (rough) for managed > caches, then it might never affect you, though it still depends on what > PriCache wants to do and it might still squeeze the KV cache under some > other set of conditions. With the default of 4G for osd_memory_target, > my guess is things are just about on the edge of being safe enough most > of the time, which is probably why most people don't see things go > horribly wrong. If you increase osd_memory_target above the default, > you're probably safe. If you decrease it, you're in danger. Hence the bluefs_buffered_io crutch. Even if you hit this, if you have enough page cache to keep the SST files cached, it doesn't really affect you either. > > Either of the above settings makes it impossible for this pathological > situation to occur. rocksdb_cache_index_and_filter_blocks=false is > safest, rocksdb_cache_shard_bits=0 could allow for some thrashing if > multiple SSTs are involved, rocksdb_cache_shard_bits=1 is borderline, > but neither should allow for the extreme pathological behavior where a > single thread thrashes reads repeatedly at extreme speeds in any case > (which is what I experienced). I'd slightly disagree with the conclusion that rocksdb_cache_index_and_filter_blocks=false is safest, especially when running in extremely memory constrained scenarios. It's safest from the angle that this data will now be forced into memory, but it's not safe from the viewpoint that it will contribute to osd_memory_target overage without oversight. It takes control out of the prioritycache's hands and forces the memory to be used. That's why I'm leaning toward reducing the number of cache shards and ensuring the minimum memory allocation for the block cache is large enough that we can fit the index/filter blocks into the cache. Having said that, there is a reasonable view as well that if you are *extremely* memory constrained, loading the index/filter blocks from disk is the correct behavior even if it's horribly slow. Mark > > - Hector > > > On 2025/06/22 21:51, Hector Martin wrote: >> Hi all, >> >> I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version >> 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU >> usage and cluster performance plummets. Most of the CPU usage was >> accounted as "system", while actual disk I/O usage was low, so that >> didn't sound right. >> >> I perf traced the system, and found that most of the usage is in >> __arch_copy_to_user in the kernel, in read() syscalls. That sounds like >> the OSD is thrashing buffered block device reads which the kernel >> satisfies from the page cache (hence no real I/O load, most of the CPU >> usage is from data copies), so it's repeatedly reading the same disk >> blocks, which doesn't sound right. >> >> I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph >> config`, and CPU usage immediately dropped to near zero. However, >> waiting a bit eventually the CPU thrashing returns after memory usage >> increases. Restarting an OSD has a similar effect. I believe that >> something is wrong with the OSD bluestore cache allocation/flush policy, >> and when the cache becomes full it starts thrashing reads instead of >> evicting colder cached data (or perhaps some cache bucket is starving >> another cache bucket of space). >> >> I would appreciate some hints on how to debug this. Are there any cache >> stats I should be looking at, or info on how the cache is partitioned? >> >> Here is a perf call trace of the thrashing: >> >>> - OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) >>> - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) >>> - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) >>> - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) >>> - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) >>> - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) >>> - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) >>> - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) >>> - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv >>> - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) >>> - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) >>> - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) >>> - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std >>> - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ >>> - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) >>> - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch >>> - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) >>> - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) >>> - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std >>> - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl >>> - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo >>> - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor >>> - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks >>> - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb >>> - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi >>> - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks >>> - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref >>> - 88.25% rocksdb::BlockFetcher::ReadBlockContents() >> [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] >>> - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli >> [ note: at this level the call trace splits into 4, but it all leads to >> the same place ] >>> - 35.99% 0xaaaac0992e40 >>> BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const >>> BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) >>> - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) >>> - 35.98% __libc_pread >>> - el0_svc >>> - invoke_syscall >>> - 35.97% __arm64_sys_pread64 >>> - 35.96% vfs_read >>> - blkdev_read_iter >>> - 35.93% filemap_read >>> - 35.28% copy_page_to_iter >>> 35.01% __arch_copy_to_user >> - Hector >> _______________________________________________ >> ceph-users mailing list -- ceph-users@ceph.io >> To unsubscribe send an email to ceph-users-leave@ceph.io > - Hector > -- Best Regards, Mark Nelson Head of Research and Development Clyso GmbH p: +49 89 21552391 12 | a: Minnesota, USA w: https://clyso.com | e: mark.nelson@clyso.com We are hiring: https://www.clyso.com/jobs/
On 2025/08/12 1:00, Mark Nelson wrote: > Congrats on figuring this out Hector! This is a huge find! Comments below. > > > On 8/11/25 4:31 AM, Hector Martin wrote: >> For those who have been following along, I figured it out. I left all >> the details with Mark on Slack, but TL;DR: The fix is *either one* (or >> both works too) of these: >> >> ceph config set osd rocksdb_cache_index_and_filter_blocks false >> (Ceph default: true, RocksDB default: false) >> ceph config set osd rocksdb_cache_shard_bits 0 >> (or 1, Ceph default: 4, RocksDB default: variable but probably 6) >> >> (Restart all OSDs after changing these settings for them to take effect, >> I don't think they apply live) >> >> The side effect of the first option is that it might increase >> unaccounted heap OSD memory usage (not managed by the cache autosize >> code), as filter blocks are preloaded and cached outside the block >> cache. The side effect of the second option is that it reduces the >> parallelism/core scalability of the block cache. I suspect the impact of >> either will be generally small for most typical deployments, and the >> benefit of not having horrible snaptrim thrashing far outweighs it for >> those affected. >> >> Both options were changed from their RocksDB defaults in Ceph commits >> long ago without any explanation / relevant commit messages, so I >> suspect this is a case of "some people flipped some knobs that seemed >> harmless and nobody really tested their impact". Other things have >> changed since then too, so I can't say when the impact likely began, as >> there are a lot of other factors involved. > > I'll take ownership of that one since I was the one that flipped those > knobs. ;) > > FWIW, a lot of the discussion around these issues was happening back in > the bluestore standup in those days. I recall that the idea behind > these changes is that we didn't want index and filter blocks to be able > to consume memory outside the context of the cache. IE at the time we > were concerned with runaway memory usage of the OSD, so having index and > filter blocks cached with high (pinned!) priority in the block cache was > preferable to having memory allocated for them without bound and no > oversight in RocksDB. The testing we did at the time didn't involve > running in scenarios where the RocksDB cache size was this small. We > didn't have the osd_memory_target autotuning back then and the cache > size was a static ratio of the overall bluestore cache size. Back in > those days we did regularly see the OSD swings in memory usage depending > on the workload. > > It seems to me this is really a combination of high memory pressure > forcing the RocksDB block cache to shrink to the minimum 64MB, having 16 > cache shards, and forcing index/filter blocks into the cache that leads > to the bad behavior. Yup. In particular, index/filter blocks are *way* larger than data blocks, so while 64MB and 16 shards is still entirely reasonable for everything else, it falls over badly here. Dare I say, the underlying issue is shoving index/filter blocks into the same cache as data blocks, which is arguably a RocksDB flaw. Ideally I'd say they should go into a dedicated cache, managed to share space with the main block cache, but with no sharding itself. > > >> >> The root cause is that the sharding defaults to 16 shards, and that >> filter blocks are cached in the block cache instead of separately (not >> the RocksDB default). Filter blocks can be up to 17MB or thereabouts, >> which means with 16 shards, the minimum viable KV block cache size is >> ~272MB to ensure non-pathological behavior (if a filter block does not >> fit into its cache shard, it is not cached at all, causing the problem). >> Since snaptrim uses a lot of memory, it squeezes out the OSD caches and >> as soon as the RocksDB block cache size drops below that size or so >> (depending on how large filter blocks you ended up with on a particular >> OSD), you get pathological thrashing with potentially several gigabytes >> per second of kernel->user memory copies repeatedly rereading filter >> blocks from SSTs. > > My thought is that unless testing says us otherwise, we probably should > drop the shard count down to around 4 and bump up the minimum cache > allocation a bit. We could disable storing index/filter blocks in the > block cache and now the osd_memory_target code will attempt to > compensate for it (which we didn't have before), but it feels a bit like > going backwards (ideally we would be accounting for all significant > memory usage in the OSD). Yeah, that works. Shard bits = 2 (4 shards) and bump up the minimum cache size to 96MB or something should do the trick, assuming filter blocks much larger than 16MB don't happen (not sure what the distribution of those is or what limits their size, I only know what I see on my setup). > > >> >> Fixing this *might* mean that bluefs_buffered_io can be flipped to false >> too. > > This is one of the most exciting aspects of this discovery imho! > > >> >> As for who is likely affected, it depends on snaptrim memory usage vs. >> osd_memory_target. If you can guarantee 1GB or so (rough) for managed >> caches, then it might never affect you, though it still depends on what >> PriCache wants to do and it might still squeeze the KV cache under some >> other set of conditions. With the default of 4G for osd_memory_target, >> my guess is things are just about on the edge of being safe enough most >> of the time, which is probably why most people don't see things go >> horribly wrong. If you increase osd_memory_target above the default, >> you're probably safe. If you decrease it, you're in danger. > > Hence the bluefs_buffered_io crutch. Even if you hit this, if you have > enough page cache to keep the SST files cached, it doesn't really affect > you either. Oh it does. Remember, the issue I'm hitting is that merely *copying stuff from page cache* eats all CPU, there is zero disk I/O. The underlying complexity blowup here, I suspect, is that in order to read/interpret *a small part* of the filter block, RocksDB has to load *the whole block* every time. So if the block is already in RocksDB cache, there is no copying involved and the complexity is effectively O(accesses). When the block is being repeatedly loaded from the kernel page cache, that becomes O(accesses * filter block size), and you get a complexity explosion. I'm not sure if attachments are allowed here, but let's just say that when I repro'd this earlier today, I was watching the Grafana graph, and the instant an OSD hit the runaway condition, the random_read I/O throughput graph shot up and rescaled to the point all other lines got compressed down to look like zero. *looks back* yeah, it was a near instantaneous jump from ~34MB/s to almost 8GB/s for the first affected OSD. If I look back at some of the painful days where all the OSDs spent a while thrashing, ~all of them end up hovering around 3GB/s. With 4 OSDs per host, that's 12GB/s total kernel->user copy throughput happening on the system; even if we ignore the overhead of the kernel/user special case, that's the ~entire bandwidth of the fastest available grade of a single DDR4 DIMM. Good thing the little Apple boxes I'm running this on are famous for having oodles of memory bandwidth, otherwise it would have been worse! :^) I'm pretty sure there are two levels of pain here. The level that inspired bluefs_buffered_io=true probably involves something like either a) some general cache thrashing, but not on every access loop within a thread, just overall across threads (general cache contention), or b) the same kind of pathological thrashing I saw, but with very limited duration (perhaps not snaptrim or some other less aggressive workload or something else changed to make it worse now/for me). When you have just a *little* bit of this corner case, then you see it as direct I/O pain and the kernel page cache "fixes it". But when it's as horribly pathological as the case I'm hitting, that only partially helps. I didn't get around to testing bluefs_buffered_io=false with my reproducing environment, but I'm pretty sure the outcome of that would have been that snaptrim just never comes even close to completing/catching up, especially if I had done it before I'd moved DB to SSDs. There's also the part where the bottleneck here is kernel->user copies, which is probably something that performs differently depending on a bunch of factors including security mitigations, architecture, and all sorts of other stuff. It might be that on some systems, these copies are fast enough that even if you hit the pathological case I did, it flies under the radar a bit more easily. If I look at the grafana graph, the explosion isn't truly instantaneous, there's just an inflection point. On snaptrim, the first affected OSD starts out reading around 1.5-2MB/s. This stays for around 5 minutes, as memory usage grows but the cache remains healthily sized. As the cache starts to get squeezed, it grows within the span of about 5 minutes to 34MB/s. At that point, it hits the "a single block doesn't fit" tipping point and explodes. So there is clearly a steady state of memory capacity where you're doing up to >10x the I/O that you'd be doing otherwise, and that's where bluefs_buffered_io=true would fix things. But when you hit the point where a single block doesn't fit at all, even that doesn't save you. So, that does mean that the calculations above and which I made only avoid the worst-case here. I think if you want to avoid thrashing to the point where you can flip bluefs_buffered_io=false, the cache/shard sizing might need to be more conservative... > > >> >> Either of the above settings makes it impossible for this pathological >> situation to occur. rocksdb_cache_index_and_filter_blocks=false is >> safest, rocksdb_cache_shard_bits=0 could allow for some thrashing if >> multiple SSTs are involved, rocksdb_cache_shard_bits=1 is borderline, >> but neither should allow for the extreme pathological behavior where a >> single thread thrashes reads repeatedly at extreme speeds in any case >> (which is what I experienced). > > > I'd slightly disagree with the conclusion that > rocksdb_cache_index_and_filter_blocks=false is safest, especially when > running in extremely memory constrained scenarios. It's safest from the > angle that this data will now be forced into memory, but it's not safe > from the viewpoint that it will contribute to osd_memory_target overage > without oversight. It takes control out of the prioritycache's hands > and forces the memory to be used. That's why I'm leaning toward > reducing the number of cache shards and ensuring the minimum memory > allocation for the block cache is large enough that we can fit the > index/filter blocks into the cache. Right, what I meant is that it's safest in terms of never thrashing, not that it's safest in general. Sorry for being unclear. > Having said that, there is a > reasonable view as well that if you are *extremely* memory constrained, > loading the index/filter blocks from disk is the correct behavior even > if it's horribly slow. Eh, given how bad what I've seen is, I don't think that is ever correct. If you're on a system that is so horribly memory constrained that you legitimately can't load those blocks from disk (and honestly, you never should be, the problem here is just the sharding interaction, not that anyone literally doesn't have 16MB of RAM for a single filter block), you probably also have dinky small CPUs and low bandwidth memory that completely falls over in this case. The slightly smarter solution here would be to autotune the shard count based on some other metric. The only issue here is that (with the current code) this cannot be done at runtime, so it would have to be something like a computation based on osd_memory_target at boot time (which would make that option somewhat counter-intuitively a mostly-runtime option but with boot-time impact). Or someone could add runtime shard merging/splitting to the cache code... should be easier than PG merging at least! :-) - Hector > > > Mark > > >> >> - Hector >> >> >> On 2025/06/22 21:51, Hector Martin wrote: >>> Hi all, >>> >>> I have a small 3-node cluster (4 HDD + 1 SSD OSD per node, ceph version >>> 19.2.2) and noticed that during snaptrim ops (?), the OSDs max out CPU >>> usage and cluster performance plummets. Most of the CPU usage was >>> accounted as "system", while actual disk I/O usage was low, so that >>> didn't sound right. >>> >>> I perf traced the system, and found that most of the usage is in >>> __arch_copy_to_user in the kernel, in read() syscalls. That sounds like >>> the OSD is thrashing buffered block device reads which the kernel >>> satisfies from the page cache (hence no real I/O load, most of the CPU >>> usage is from data copies), so it's repeatedly reading the same disk >>> blocks, which doesn't sound right. >>> >>> I increased osd_memory_target to 2.4G (from 1.2G) live with `ceph >>> config`, and CPU usage immediately dropped to near zero. However, >>> waiting a bit eventually the CPU thrashing returns after memory usage >>> increases. Restarting an OSD has a similar effect. I believe that >>> something is wrong with the OSD bluestore cache allocation/flush policy, >>> and when the cache becomes full it starts thrashing reads instead of >>> evicting colder cached data (or perhaps some cache bucket is starving >>> another cache bucket of space). >>> >>> I would appreciate some hints on how to debug this. Are there any cache >>> stats I should be looking at, or info on how the cache is partitioned? >>> >>> Here is a perf call trace of the thrashing: >>> >>>> - OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*) >>>> - 95.04% ceph::osd::scheduler::PGOpItem::run(OSD*, OSDShard*, boost::intrusive_ptr<PG>&, ThreadPool::TPHandle&) >>>> - OSD::dequeue_op(boost::intrusive_ptr<PG>, boost::intrusive_ptr<OpRequest>, ThreadPool::TPHandle&) >>>> - 95.03% PrimaryLogPG::do_request(boost::intrusive_ptr<OpRequest>&, ThreadPool::TPHandle&) >>>> - PGBackend::handle_message(boost::intrusive_ptr<OpRequest>) >>>> - ECBackend::_handle_message(boost::intrusive_ptr<OpRequest>) >>>> - 88.86% ECBackend::handle_sub_write(pg_shard_t, boost::intrusive_ptr<OpRequest>, ECSubWrite&, ZTracer::Trace const&, ECListener&) >>>> - 88.78% non-virtual thunk to PrimaryLogPG::queue_transactions(std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusive_ptr<OpRequest>) >>>> - BlueStore::queue_transactions(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, std::vector<ceph::os::Transaction, std::allocator<ceph::os::Transaction> >&, boost::intrusiv >>>> - 88.75% BlueStore::_txc_add_transaction(BlueStore::TransContext*, ceph::os::Transaction*) >>>> - 88.71% BlueStore::_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) >>>> - BlueStore::_do_remove(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&) >>>> - 88.69% BlueStore::_do_truncate(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, unsigned long, std >>>> - 88.68% BlueStore::_wctx_finish(BlueStore::TransContext*, boost::intrusive_ptr<BlueStore::Collection>&, boost::intrusive_ptr<BlueStore::Onode>&, BlueStore::Writ >>>> - 88.65% BlueStore::Collection::load_shared_blob(boost::intrusive_ptr<BlueStore::SharedBlob>) >>>> - 88.64% RocksDBStore::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::ch >>>> - 88.61% rocksdb::DBImpl::Get(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*) >>>> - rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) >>>> - 88.43% rocksdb::Version::Get(rocksdb::ReadOptions const&, rocksdb::LookupKey const&, rocksdb::PinnableSlice*, rocksdb::PinnableWideColumns*, std >>>> - 88.41% rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Sl >>>> - rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bo >>>> - 88.32% rocksdb::BlockBasedTable::FullFilterKeyMayMatch(rocksdb::FilterBlockReader*, rocksdb::Slice const&, bool, rocksdb::SliceTransfor >>>> - rocksdb::FullFilterBlockReader::MayMatch(rocksdb::Slice const&, bool, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*, rocks >>>> - 88.29% rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::GetOrReadFilterBlock(bool, rocksdb::GetContext*, rocksdb >>>> - rocksdb::FilterBlockReaderCommon<rocksdb::ParsedFullFilterBlock>::ReadFilterBlock(rocksdb::BlockBasedTable const*, rocksdb::Fi >>>> - rocksdb::Status rocksdb::BlockBasedTable::RetrieveBlock<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePrefetchBuffer*, rocks >>>> - rocksdb::Status rocksdb::BlockBasedTable::MaybeReadBlockAndLoadToCache<rocksdb::ParsedFullFilterBlock>(rocksdb::FilePref >>>> - 88.25% rocksdb::BlockFetcher::ReadBlockContents() >>> [ note: split, the rest is + 19.42% rocksdb::VerifyBlockChecksum] >>>> - 68.80% rocksdb::RandomAccessFileReader::Read(rocksdb::IOOptions const&, unsigned long, unsigned long, rocksdb::Sli >>> [ note: at this level the call trace splits into 4, but it all leads to >>> the same place ] >>>> - 35.99% 0xaaaac0992e40 >>>> BlueRocksRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const >>>> BlueFS::_read_random(BlueFS::FileReader*, unsigned long, unsigned long, char*) >>>> - KernelDevice::read_random(unsigned long, unsigned long, char*, bool) >>>> - 35.98% __libc_pread >>>> - el0_svc >>>> - invoke_syscall >>>> - 35.97% __arm64_sys_pread64 >>>> - 35.96% vfs_read >>>> - blkdev_read_iter >>>> - 35.93% filemap_read >>>> - 35.28% copy_page_to_iter >>>> 35.01% __arch_copy_to_user >>> - Hector >>> _______________________________________________ >>> ceph-users mailing list -- ceph-users@ceph.io >>> To unsubscribe send an email to ceph-users-leave@ceph.io >> - Hector >> - Hector
participants (7)
-
Anthony D'Atri
-
Eugen Block
-
Hector Martin
-
Janne Johansson
-
Mark Nelson
-
Stefan Kooman
-
Tyler Stachecki