Skip to content

Commit 94907cc

Browse files
Jiayue Baofacebook-github-bot
authored andcommitted
Track Jemalloc external fragmentation to bound the cache
Summary: Currently, size-awareness isn't tracking Jemalloc external fragmentation. And a bad external fragmentation can potentially cause OOM issue. In this diff, we utilize two Jemalloc counters `stats.allocated` and `stats.active` to track the external fragmentation: ``` external fragmentation = stats.active - stats.allocated. ``` the total active object size will be: ``` totalActiveObjSize = totalAllocatedObjSize / stats.allocated * stats.active ``` Also add two Jemalloc counters: - `objcache.jemalloc_active_bytes` - `objcache.jemalloc_allocated_bytes` Reviewed By: therealgymmy, jaesoo-fb Differential Revision: D45460371 fbshipit-source-id: 71b5bc261581d9cc0c2f914237aa3766bf54ff12
1 parent b2785af commit 94907cc

File tree

4 files changed

+59
-0
lines changed

4 files changed

+59
-0
lines changed

cachelib/experimental/objcache2/ObjectCache-inl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ void ObjectCache<AllocatorT>::getObjectCacheCounters(
336336
visitor("objcache.evictions", evictions_.get(),
337337
util::CounterVisitor::CounterType::RATE);
338338
visitor("objcache.object_size_bytes", getTotalObjectSize());
339+
if (sizeController_) {
340+
sizeController_->getCounters(visitor);
341+
}
339342
}
340343

341344
template <typename AllocatorT>

cachelib/experimental/objcache2/ObjectCacheConfig.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ struct ObjectCacheConfig {
130130
SerializeCb serializeCallback,
131131
DeserializeCb deserializeCallback);
132132

133+
// Enable tracking Jemalloc external fragmentation.
134+
ObjectCacheConfig& enableFragmentationTracking();
135+
133136
ObjectCacheConfig& setItemReaperInterval(std::chrono::milliseconds interval);
134137

135138
ObjectCacheConfig& setEvictionPolicyConfig(
@@ -164,6 +167,10 @@ struct ObjectCacheConfig {
164167
// If this is enabled, user has to pass the object size upon insertion
165168
bool objectSizeTrackingEnabled{false};
166169

170+
// If this is enabled, we will track Jemalloc external fragmentation and add
171+
// the fragmentation bytes on top of total object size to bound the cache
172+
bool fragmentationTrackingEnabled{false};
173+
167174
// Period to fire size controller in milliseconds. 0 means size controller is
168175
// disabled.
169176
int sizeControllerIntervalMs{0};
@@ -278,6 +285,12 @@ ObjectCacheConfig<T>& ObjectCacheConfig<T>::setSizeControllerThrottlerConfig(
278285
return *this;
279286
}
280287

288+
template <typename T>
289+
ObjectCacheConfig<T>& ObjectCacheConfig<T>::enableFragmentationTracking() {
290+
fragmentationTrackingEnabled = true;
291+
return *this;
292+
}
293+
281294
template <typename T>
282295
ObjectCacheConfig<T>& ObjectCacheConfig<T>::setEventTracker(
283296
EventTrackerSharedPtr&& ptr) {
@@ -394,6 +407,12 @@ const ObjectCacheConfig<T>& ObjectCacheConfig<T>::validate() const {
394407
"Only one of sizeControllerIntervalMs and cacheSizeLimit is set");
395408
}
396409
}
410+
411+
if (fragmentationTrackingEnabled && !objectSizeTrackingEnabled) {
412+
throw std::invalid_argument(
413+
"Object size tracking has to be enabled to have fragmentation "
414+
"tracking");
415+
}
397416
return *this;
398417
}
399418

cachelib/experimental/objcache2/ObjectCacheSizeController-inl.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ void ObjectCacheSizeController<AllocatorT>::work() {
2424
return;
2525
}
2626
auto totalObjSize = objCache_.getTotalObjectSize();
27+
if (objCache_.config_.fragmentationTrackingEnabled &&
28+
folly::usingJEMalloc()) {
29+
auto [jemallocAllocatedBytes, jemallocActiveBytes] =
30+
trackJemallocMemStats();
31+
// proportionally add Jemalloc external fragmentation bytes (i.e.
32+
// jemallocActiveBytes - jemallocAllocatedBytes)
33+
totalObjSize = static_cast<size_t>(
34+
1.0 * totalObjSize / jemallocAllocatedBytes * jemallocActiveBytes);
35+
}
36+
2737
// Do the calculation only when total object size or total object number
2838
// achieves the threshold. This is to avoid unreliable calculation of average
2939
// object size when the cache is new and only has a few objects.
@@ -107,6 +117,17 @@ void ObjectCacheSizeController<AllocatorT>::expandCacheByEntriesNum(
107117
entries, before, objCache_.getNumPlaceholders(), currentEntriesLimit_);
108118
}
109119

120+
template <typename AllocatorT>
121+
void ObjectCacheSizeController<AllocatorT>::getCounters(
122+
const util::CounterVisitor& visitor) const {
123+
if (folly::usingJEMalloc()) {
124+
auto [jemallocAllocatedBytes, jemallocActiveBytes] =
125+
trackJemallocMemStats();
126+
visitor("objcache.jemalloc_active_bytes", jemallocActiveBytes);
127+
visitor("objcache.jemalloc_allocated_bytes", jemallocAllocatedBytes);
128+
}
129+
}
130+
110131
template <typename AllocatorT>
111132
ObjectCacheSizeController<AllocatorT>::ObjectCacheSizeController(
112133
ObjectCache& objCache, const util::Throttler::Config& throttlerConfig)

cachelib/experimental/objcache2/ObjectCacheSizeController.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#pragma once
1818

19+
#include <folly/memory/Malloc.h>
20+
1921
#include "cachelib/common/PeriodicWorker.h"
2022

2123
namespace facebook {
@@ -35,12 +37,26 @@ class ObjectCacheSizeController : public PeriodicWorker {
3537
return currentEntriesLimit_.load(std::memory_order_relaxed);
3638
}
3739

40+
void getCounters(const util::CounterVisitor& visitor) const;
41+
3842
private:
3943
void work() override final;
4044

4145
void shrinkCacheByEntriesNum(size_t entries);
4246
void expandCacheByEntriesNum(size_t entries);
4347

48+
std::pair<size_t, size_t> trackJemallocMemStats() const {
49+
size_t jemallocAllocatedBytes;
50+
size_t jemallocActiveBytes;
51+
size_t epoch = 1;
52+
size_t sz;
53+
sz = sizeof(size_t);
54+
mallctl("epoch", nullptr, nullptr, &epoch, sizeof(epoch));
55+
mallctl("stats.allocated", &jemallocAllocatedBytes, &sz, nullptr, 0);
56+
mallctl("stats.active", &jemallocActiveBytes, &sz, nullptr, 0);
57+
return {jemallocAllocatedBytes, jemallocActiveBytes};
58+
}
59+
4460
// threshold in percentage to determine whether the size-controller should do
4561
// the calculation
4662
const size_t kSizeControllerThresholdPct = 50;

0 commit comments

Comments
 (0)