mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 15:28:03 +00:00
The online-delete rotation's cache freshen called TaggedCache::getKeys(),
which held the cache mutex while copying every key. On the dev box's 26
million entry tree-node cache that hold lasted 5-6 s, froze every job
that touches the cache, and dropped the RocksDB node out of sync once per
rotation: each "getKeys held the lock" warning was followed within 1-5 s
by "View of consensus changed" (5 of 5 rotations on 2026-09-15).
Copy the keys one map partition at a time instead. TaggedCache gains
forEachKeyPartition(), which holds the mutex only while one partition's
keys are copied and runs the callback with the mutex released, so the
longest hold shrinks by the partition count (8 on the dev box). The
freshen.keys rotation phase no longer exists as one step, so its span,
stage value, harness entries and docs are removed; the per-partition hold
still shows on the cache lock-hold peak gauge.
Measure what the freshen achieves, which no existing signal did.
DatabaseRotating gains duplicateCopyForwardTotal(), counting archive
copies made on duplicate fetches (the rotation's own copy walk and
freshen); copyForwardTotal() deliberately excludes those. The freshen
phase records rotation_freshen_keys_total{cache,outcome} and stamps
key_count, cache and keys_copied on its span; the copy phase stamps
nodes_copied. A warn log line per freshen reports the same numbers, and
the ledger-sync-health dashboard gets a Rotation Freshen Yield panel.
Log the "STATE->" operating-mode change at warn instead of info. It is
the only record of a mode change with an exact timestamp; the
state_changes_total counter is scraped once a minute and cannot order a
flap against a multi-second event.
Tests: five GTests for forEachKeyPartition (every key once, empty cache,
mutex free during the callback, concurrent insert, lock-hold peak), three
for duplicateCopyForwardTotal over two memory backends, one for the new
counter's series, and the new name literals.
390 lines
7.8 KiB
C++
390 lines
7.8 KiB
C++
#pragma once
|
|
|
|
#include <xrpl/beast/hash/uhash.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace xrpl {
|
|
|
|
template <typename Key>
|
|
static std::size_t
|
|
extract(Key const& key)
|
|
{
|
|
return key;
|
|
}
|
|
|
|
template <>
|
|
inline std::size_t
|
|
extract(std::string const& key)
|
|
{
|
|
return ::beast::Uhash<>{}(key);
|
|
}
|
|
|
|
template <
|
|
typename Key,
|
|
typename Value,
|
|
typename Hash,
|
|
typename Pred = std::equal_to<Key>,
|
|
typename Alloc = std::allocator<std::pair<Key const, Value>>>
|
|
class PartitionedUnorderedMap
|
|
{
|
|
std::size_t partitions_;
|
|
|
|
public:
|
|
using key_type = Key;
|
|
using mapped_type = Value;
|
|
using value_type = std::pair<Key const, mapped_type>;
|
|
using size_type = std::size_t;
|
|
using difference_type = std::size_t;
|
|
using hasher = Hash;
|
|
using key_equal = Pred;
|
|
using allocator_type = Alloc;
|
|
using reference = value_type&;
|
|
using const_reference = value_type const&;
|
|
using pointer = value_type*;
|
|
using const_pointer = value_type const*;
|
|
using map_type = std::unordered_map<key_type, mapped_type, hasher, key_equal, allocator_type>;
|
|
using partition_map_type = std::vector<map_type>;
|
|
|
|
struct Iterator
|
|
{
|
|
using iterator_category = std::forward_iterator_tag;
|
|
partition_map_type* map{nullptr};
|
|
partition_map_type::iterator ait{};
|
|
map_type::iterator mit;
|
|
|
|
Iterator() = default;
|
|
|
|
Iterator(partition_map_type* m) : map(m)
|
|
{
|
|
}
|
|
|
|
reference
|
|
operator*() const
|
|
{
|
|
return *mit;
|
|
}
|
|
|
|
pointer
|
|
operator->() const
|
|
{
|
|
return &(*mit);
|
|
}
|
|
|
|
void
|
|
inc()
|
|
{
|
|
++mit;
|
|
while (mit == ait->end())
|
|
{
|
|
++ait;
|
|
if (ait == map->end())
|
|
return;
|
|
mit = ait->begin();
|
|
}
|
|
}
|
|
|
|
// ++it
|
|
Iterator&
|
|
operator++()
|
|
{
|
|
inc();
|
|
return *this;
|
|
}
|
|
|
|
// it++
|
|
Iterator
|
|
operator++(int)
|
|
{
|
|
Iterator tmp(*this);
|
|
inc();
|
|
return tmp;
|
|
}
|
|
|
|
friend bool
|
|
operator==(Iterator const& lhs, Iterator const& rhs)
|
|
{
|
|
return lhs.map == rhs.map && lhs.ait == rhs.ait && lhs.mit == rhs.mit;
|
|
}
|
|
};
|
|
|
|
struct ConstIterator
|
|
{
|
|
using iterator_category = std::forward_iterator_tag;
|
|
|
|
partition_map_type* map{nullptr};
|
|
partition_map_type::iterator ait{};
|
|
map_type::iterator mit;
|
|
|
|
ConstIterator() = default;
|
|
|
|
ConstIterator(partition_map_type* m) : map(m)
|
|
{
|
|
}
|
|
|
|
ConstIterator(Iterator const& orig) : map(orig.map), ait(orig.ait), mit(orig.mit)
|
|
{
|
|
}
|
|
|
|
const_reference
|
|
operator*() const
|
|
{
|
|
return *mit;
|
|
}
|
|
|
|
const_pointer
|
|
operator->() const
|
|
{
|
|
return &(*mit);
|
|
}
|
|
|
|
void
|
|
inc()
|
|
{
|
|
++mit;
|
|
while (mit == ait->end())
|
|
{
|
|
++ait;
|
|
if (ait == map->end())
|
|
return;
|
|
mit = ait->begin();
|
|
}
|
|
}
|
|
|
|
// ++it
|
|
ConstIterator&
|
|
operator++()
|
|
{
|
|
inc();
|
|
return *this;
|
|
}
|
|
|
|
// it++
|
|
ConstIterator
|
|
operator++(int)
|
|
{
|
|
ConstIterator tmp(*this);
|
|
inc();
|
|
return tmp;
|
|
}
|
|
|
|
friend bool
|
|
operator==(ConstIterator const& lhs, ConstIterator const& rhs)
|
|
{
|
|
return lhs.map == rhs.map && lhs.ait == rhs.ait && lhs.mit == rhs.mit;
|
|
}
|
|
};
|
|
|
|
private:
|
|
std::size_t
|
|
partitioner(Key const& key) const
|
|
{
|
|
return extract(key) % partitions_;
|
|
}
|
|
|
|
template <class T>
|
|
static void
|
|
end(T& it)
|
|
{
|
|
it.ait = it.map->end();
|
|
it.mit = it.map->back().end();
|
|
}
|
|
|
|
template <class T>
|
|
static void
|
|
begin(T& it)
|
|
{
|
|
for (it.ait = it.map->begin(); it.ait != it.map->end(); ++it.ait)
|
|
{
|
|
if (it.ait->begin() == it.ait->end())
|
|
continue;
|
|
it.mit = it.ait->begin();
|
|
return;
|
|
}
|
|
end(it);
|
|
}
|
|
|
|
public:
|
|
PartitionedUnorderedMap(std::optional<std::size_t> partitions = std::nullopt)
|
|
// Set partitions to the number of hardware threads if the parameter
|
|
// is either empty or set to 0.
|
|
: partitions_(
|
|
partitions && (*partitions != 0u) ? *partitions : std::thread::hardware_concurrency())
|
|
{
|
|
map_.resize(partitions_);
|
|
XRPL_ASSERT(
|
|
partitions_,
|
|
"xrpl::PartitionedUnorderedMap::PartitionedUnorderedMap : "
|
|
"nonzero partitions");
|
|
}
|
|
|
|
std::size_t
|
|
partitions() const
|
|
{
|
|
return partitions_;
|
|
}
|
|
|
|
partition_map_type&
|
|
map()
|
|
{
|
|
return map_;
|
|
}
|
|
|
|
partition_map_type const&
|
|
map() const
|
|
{
|
|
return map_;
|
|
}
|
|
|
|
Iterator
|
|
begin()
|
|
{
|
|
Iterator it(&map_);
|
|
begin(it);
|
|
return it;
|
|
}
|
|
|
|
ConstIterator
|
|
cbegin() const
|
|
{
|
|
ConstIterator it(&map_);
|
|
begin(it);
|
|
return it;
|
|
}
|
|
|
|
ConstIterator
|
|
begin() const
|
|
{
|
|
return cbegin();
|
|
}
|
|
|
|
Iterator
|
|
end()
|
|
{
|
|
Iterator it(&map_);
|
|
end(it);
|
|
return it;
|
|
}
|
|
|
|
ConstIterator
|
|
cend() const
|
|
{
|
|
ConstIterator it(&map_);
|
|
end(it);
|
|
return it;
|
|
}
|
|
|
|
ConstIterator
|
|
end() const
|
|
{
|
|
return cend();
|
|
}
|
|
|
|
private:
|
|
template <class T>
|
|
void
|
|
find(key_type const& key, T& it) const
|
|
{
|
|
it.ait = it.map->begin() + partitioner(key);
|
|
it.mit = it.ait->find(key);
|
|
if (it.mit == it.ait->end())
|
|
end(it);
|
|
}
|
|
|
|
public:
|
|
Iterator
|
|
find(key_type const& key)
|
|
{
|
|
Iterator it(&map_);
|
|
find(key, it);
|
|
return it;
|
|
}
|
|
|
|
ConstIterator
|
|
find(key_type const& key) const
|
|
{
|
|
ConstIterator it(&map_);
|
|
find(key, it);
|
|
return it;
|
|
}
|
|
|
|
template <class T, class U>
|
|
std::pair<Iterator, bool>
|
|
emplace(std::piecewise_construct_t const&, T&& keyTuple, U&& valueTuple)
|
|
{
|
|
auto const& key = std::get<0>(keyTuple);
|
|
Iterator it(&map_);
|
|
it.ait = it.map->begin() + partitioner(key);
|
|
auto [eit, inserted] = it.ait->emplace(
|
|
std::piecewise_construct, std::forward<T>(keyTuple), std::forward<U>(valueTuple));
|
|
it.mit = eit;
|
|
return {it, inserted};
|
|
}
|
|
|
|
template <class T, class U>
|
|
std::pair<Iterator, bool>
|
|
emplace(T&& key, U&& val)
|
|
{
|
|
Iterator it(&map_);
|
|
it.ait = it.map->begin() + partitioner(key);
|
|
auto [eit, inserted] = it.ait->emplace(std::forward<T>(key), std::forward<U>(val));
|
|
it.mit = eit;
|
|
return {it, inserted};
|
|
}
|
|
|
|
void
|
|
clear()
|
|
{
|
|
for (auto& p : map_)
|
|
p.clear();
|
|
}
|
|
|
|
Iterator
|
|
erase(ConstIterator position)
|
|
{
|
|
Iterator it(&map_);
|
|
it.ait = position.ait;
|
|
it.mit = position.ait->erase(position.mit);
|
|
|
|
while (it.mit == it.ait->end())
|
|
{
|
|
++it.ait;
|
|
if (it.ait == it.map->end())
|
|
break;
|
|
it.mit = it.ait->begin();
|
|
}
|
|
|
|
return it;
|
|
}
|
|
|
|
std::size_t
|
|
size() const
|
|
{
|
|
std::size_t ret = 0;
|
|
for (auto& p : map_)
|
|
ret += p.size();
|
|
return ret;
|
|
}
|
|
|
|
Value&
|
|
operator[](Key const& key)
|
|
{
|
|
return map_[partitioner(key)][key];
|
|
}
|
|
|
|
private:
|
|
mutable partition_map_type map_{};
|
|
};
|
|
|
|
} // namespace xrpl
|