lsa: Reduce reclamation latency

Currently eviction is performed until occupancy of the whole region
drops below the 85% threshold. This may take a while if region had
high occupancy and is large. We could improve the situation by only
evicting until occupancy of the sparsest segment drops below the
threshold, as is done by this change.

I tested this using a c-s read workload in which the condition
triggers in the cache region, with 1G per shard:

 lsa-timing - Reclamation cycle took 12.934 us.
 lsa-timing - Reclamation cycle took 47.771 us.
 lsa-timing - Reclamation cycle took 125.946 us.
 lsa-timing - Reclamation cycle took 144356 us.
 lsa-timing - Reclamation cycle took 655.765 us.
 lsa-timing - Reclamation cycle took 693.418 us.
 lsa-timing - Reclamation cycle took 509.869 us.
 lsa-timing - Reclamation cycle took 1139.15 us.

The 144ms pause is when large eviction is necessary.

The change improves worst case latency. Reclamation time statistics
over 30 second period after cache fills up, in microseconds:

Before:

  avg = 1524.283148
  stdev = 11021.021118
  min = 12.934000
  max = 144356.000000
  sum = 257603.852000
  samples = 169

After:

  avg = 1317.362414
  stdev = 1913.542802
  min = 263.935000
  max = 19244.600000
  sum = 175209.201000
  samples = 133

Refs #1634.

Message-Id: <1484730859-11969-1-git-send-email-tgrabiec@scylladb.com>
This commit is contained in:
Tomasz Grabiec
2017-01-18 10:14:19 +01:00
committed by Avi Kivity
parent b880bdccef
commit d61002cc33
2 changed files with 23 additions and 47 deletions

View File

@@ -102,7 +102,7 @@ SEASTAR_TEST_CASE(test_compaction_with_multiple_regions) {
std::vector<managed_ref<int>> allocated1;
std::vector<managed_ref<int>> allocated2;
int count = 32 * 1024 * 4;
int count = 32 * 1024 * 4 * 2;
with_allocator(reg1.allocator(), [&] {
for (int i = 0; i < count; i++) {

View File

@@ -1253,6 +1253,12 @@ private:
}
};
bool is_compactible(float max_occupancy) const {
return _reclaiming_enabled
&& (_closed_occupancy.free_space() >= 2 * segment::size)
&& (_segments.top()->occupancy().used_fraction() < max_occupancy)
&& (_segments.top()->occupancy().free_space() >= max_managed_object_size);
}
public:
explicit region_impl(region* region, region_group* group = nullptr)
: _region(region), _group(group), _id(next_id())
@@ -1318,17 +1324,11 @@ public:
// while (is_compactible()) { compact(); }
//
bool is_compactible() const {
return _reclaiming_enabled
&& (_closed_occupancy.free_space() >= 2 * segment::size)
&& (_closed_occupancy.used_fraction() < max_occupancy_for_compaction)
&& (_segments.top()->occupancy().free_space() >= max_managed_object_size);
return is_compactible(max_occupancy_for_compaction);
}
bool is_idle_compactible() {
return _reclaiming_enabled
&& (_closed_occupancy.free_space() >= 2 * segment::size)
&& (_closed_occupancy.used_fraction() < max_occupancy_for_compaction_on_idle)
&& (_segments.top()->occupancy().free_space() >= max_managed_object_size);
return is_compactible(max_occupancy_for_compaction_on_idle);
}
virtual void* alloc(allocation_strategy::migrate_fn migrator, size_t size, size_t alignment) override {
@@ -1447,21 +1447,6 @@ public:
return _segments.top()->occupancy();
}
// Tries to release one full segment back to the segment pool.
void compact() {
if (!is_compactible()) {
return;
}
compaction_lock _(*this);
auto in_use = shard_segment_pool.segments_in_use();
while (shard_segment_pool.segments_in_use() >= in_use) {
compact_single_segment_locked();
}
}
void compact_single_segment_locked() {
segment* seg = _segments.top();
logger.debug("Compacting segment {} from region {}, {}", seg, id(), seg->occupancy());
@@ -1471,8 +1456,8 @@ public:
shard_segment_pool.on_segment_compaction();
}
// Compacts only a single segment
void compact_on_idle() {
// Compacts a single segment
void compact() {
compaction_lock _(*this);
compact_single_segment_locked();
}
@@ -1720,31 +1705,22 @@ void tracker::impl::full_compaction() {
}
static void reclaim_from_evictable(region::impl& r, size_t target_mem_in_use) {
while (true) {
auto deficit = shard_segment_pool.total_memory_in_use() - target_mem_in_use;
auto occupancy = r.occupancy();
auto used = occupancy.used_space();
if (used == 0) {
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()) {
if (r.evict_some() == memory::reclaiming_result::reclaimed_nothing) {
logger.debug("Unable to evict more, evicted {} bytes", used - r.occupancy().used_space());
return;
}
if (shard_segment_pool.total_memory_in_use() <= target_mem_in_use) {
logger.debug("Target met after evicting {} bytes", used - r.occupancy().used_space());
return;
}
if (r.empty()) {
size_t used;
do {
used = r.occupancy().used_space();
logger.debug("Evicting {} from region {}, occupancy={}", r.id(), r.occupancy());
while (!r.is_compactible()) {
if (r.empty() || r.evict_some() == memory::reclaiming_result::reclaimed_nothing) {
logger.debug("Still not compactible but unable to evict more, evicted {} bytes", used - r.occupancy().used_space());
return;
}
}
logger.debug("Compacting after evicting {} bytes", used - r.occupancy().used_space());
r.compact();
}
} while (shard_segment_pool.total_memory_in_use() > target_mem_in_use);
logger.debug("Target met after evicting {} bytes", used - r.occupancy().used_space());
}
struct reclaim_timer {
@@ -1804,7 +1780,7 @@ reactor::idle_cpu_handler_result tracker::impl::compact_on_idle(reactor::work_wa
return reactor::idle_cpu_handler_result::no_more_work;
}
r->compact_on_idle();
r->compact();
boost::range::push_heap(_regions, cmp);
}