diff --git a/row_cache.cc b/row_cache.cc index 545fea2e40..ffe0dd93b9 100644 --- a/row_cache.cc +++ b/row_cache.cc @@ -16,15 +16,15 @@ cache_tracker& global_cache_tracker() { return instance; } -cache_tracker::cache_tracker() - : _reclaimer([this] { - logger.warn("Clearing cache from reclaimer hook"); - // FIXME: perform incremental eviction. We should first switch to a - // compacting memory allocator to avoid problems with memory - // fragmentation. - clear(); - }) { +cache_tracker::cache_tracker() { setup_collectd(); + + _region.make_evictable([this] { + with_allocator(_region.allocator(), [this] { + assert(!_lru.empty()); + _lru.pop_back_and_dispose(current_deleter()); + }); + }); } cache_tracker::~cache_tracker() { diff --git a/row_cache.hh b/row_cache.hh index 27d342feef..a126ffac43 100644 --- a/row_cache.hh +++ b/row_cache.hh @@ -83,7 +83,6 @@ public: private: uint64_t _hits = 0; uint64_t _misses = 0; - memory::reclaimer _reclaimer; std::unique_ptr _collectd_registrations; logalloc::region _region; lru_type _lru; diff --git a/utils/logalloc.cc b/utils/logalloc.cc index 62a18d9c5c..6619099ebd 100644 --- a/utils/logalloc.cc +++ b/utils/logalloc.cc @@ -19,8 +19,6 @@ struct segment; static logging::logger logger("lsa"); static thread_local tracker tracker_instance; -using eviction_fn = std::function; - class tracker::impl { std::vector _regions; scollectd::registrations _collectd_registrations; @@ -549,6 +547,10 @@ public: impl(impl&&) = delete; impl(const impl&) = delete; + bool empty() const { + return occupancy().used_space() == 0; + } + occupancy_stats occupancy() const { occupancy_stats total{}; total += _closed_occupancy; @@ -687,6 +689,20 @@ public: _compactible = compactible; } + // + // Returns true if this pool is evictable and evict_some() will make some forward progress, so + // this will eventually stop: + // + // while (evictable()) { evict_some(); } + // + bool is_evictable() const { + return _evictable; + } + + void evict_some() { + _eviction_fn(); + } + void make_not_evictable() { _evictable = false; _eviction_fn = {}; @@ -717,6 +733,10 @@ void region::full_compaction() { _impl->full_compaction(); } +void region::make_evictable(eviction_fn fn) { + _impl->make_evictable(std::move(fn)); +} + allocation_strategy& region::allocator() { return *_impl; } @@ -750,6 +770,40 @@ void tracker::impl::full_compaction() { logger.debug("Compaction done, {}", occupancy()); } +static void reclaim_from_evictable(region::impl& r, size_t target_segments_in_use) { + while (true) { + auto deficit = (shard_segment_pool.segments_in_use() - target_segments_in_use) * segment::size; + auto occupancy = r.occupancy(); + auto used = occupancy.used_space(); + if (used == 0) { + // FIXME: There could be still some objects which are allocated + // using that region but were too large and are not managed by + // LSA. We should avoid having large objects in the first place, + // and make the managed_blob object fracture them internally. To + // handle eviction of large objects we should first move the + // segment pool service into the seastar allocator, so that + // evicting large objects counts towards that pool. It also makes + // sense to have the reclaimer coupled with that segment pool, and + // not with the page pool like it is now. + break; + } + auto used_target = used - std::min(used, deficit - std::min(deficit, occupancy.free_space())); + logger.debug("Evicting {} bytes from region {}, occupancy={}", used - used_target, r.id(), r.occupancy()); + while (r.occupancy().used_space() > used_target || !r.is_compactible()) { + r.evict_some(); + if (shard_segment_pool.segments_in_use() <= target_segments_in_use) { + logger.debug("Target met after evicting {} bytes", used - r.occupancy().used_space()); + return; + } + if (r.empty()) { + return; + } + } + logger.debug("Compacting after evicting {} bytes", used - r.occupancy().used_space()); + r.compact(); + } +} + size_t tracker::impl::reclaim(size_t bytes) { // // Algorithm outline. @@ -760,11 +814,13 @@ size_t tracker::impl::reclaim(size_t bytes) { // the region which has the sparsest segment. We do it until we released // enough segments or there are no more regions we can compact. // - // TODO: eviction step involving evictable pools. + // When compaction is not sufficient to reclaim space, we evict data from + // evictable regions. // + size_t in_use = shard_segment_pool.segments_in_use(); constexpr auto max_bytes = std::numeric_limits::max() - segment::size; - auto segments_to_release = align_up(std::min(max_bytes, bytes), segment::size) >> segment::size_shift; + auto segments_to_release = std::min(in_use, align_up(std::min(max_bytes, bytes), segment::size) >> segment::size_shift); auto cmp = [] (region::impl* c1, region::impl* c2) { if (c1->is_compactible() != c2->is_compactible()) { @@ -773,9 +829,7 @@ size_t tracker::impl::reclaim(size_t bytes) { return c2->min_occupancy() < c1->min_occupancy(); }; - size_t in_use = shard_segment_pool.segments_in_use(); - - auto target = in_use - std::min(segments_to_release, in_use); + auto target = in_use - segments_to_release; logger.debug("Compacting, {} segments in use ({} B), trying to release {} ({} B).", in_use, in_use * segment::size, segments_to_release, segments_to_release * segment::size); @@ -795,7 +849,6 @@ size_t tracker::impl::reclaim(size_t bytes) { if (!r->is_compactible()) { logger.warn("Unable to release segments, no compactible pools."); - // FIXME: Evict data from evictable pools. break; } @@ -804,9 +857,24 @@ size_t tracker::impl::reclaim(size_t bytes) { boost::range::push_heap(_regions, cmp); } - size_t nr_released = in_use - shard_segment_pool.segments_in_use(); - logger.debug("Released {} segments.", nr_released); + auto released_during_compaction = in_use - shard_segment_pool.segments_in_use(); + if (released_during_compaction < segments_to_release) { + logger.debug("Considering evictable regions."); + // FIXME: Fair eviction + for (region::impl* r : _regions) { + if (r->is_evictable()) { + reclaim_from_evictable(*r, target); + if (shard_segment_pool.segments_in_use() <= target) { + break; + } + } + } + } + + auto nr_released = in_use - shard_segment_pool.segments_in_use(); + logger.debug("Released {} segments (wanted {}), {} during compaction.", + nr_released, segments_to_release, released_during_compaction); return nr_released * segment::size; } diff --git a/utils/logalloc.hh b/utils/logalloc.hh index 5428d55685..c7b287ffe6 100644 --- a/utils/logalloc.hh +++ b/utils/logalloc.hh @@ -13,6 +13,8 @@ namespace logalloc { struct occupancy_stats; +using eviction_fn = std::function; + // Controller for all LSA regions. There's one per shard. class tracker { public: @@ -147,6 +149,11 @@ public: // compactible, it won't be considered by tracker::reclaim(). By default region is // compactible after construction. void set_compactible(bool); + + // Makes this region an evictable region. Supplied function will be called + // when data from this region needs to be evicted in order to reclaim space. + // The function should free some space from this region. + void make_evictable(eviction_fn); }; }