3030#include " kv_bucket_iface.h"
3131#include " paging_visitor.h"
3232
33+ #include < folly/lang/Assume.h>
3334#include < platform/platform_time.h>
3435
3536#include < cmath>
4344#include < phosphor/phosphor.h>
4445#include < memory>
4546
47+ double EvictionRatios::getForState (vbucket_state_t state) {
48+ switch (state) {
49+ case vbucket_state_replica:
50+ return replica;
51+ case vbucket_state_active:
52+ case vbucket_state_pending:
53+ return activeAndPending;
54+ case vbucket_state_dead:
55+ return 0 ;
56+ }
57+ folly::assume_unreachable ();
58+ }
59+
60+ void EvictionRatios::setForState (vbucket_state_t state, double value) {
61+ switch (state) {
62+ case vbucket_state_replica:
63+ replica = value;
64+ return ;
65+ case vbucket_state_active:
66+ case vbucket_state_pending:
67+ activeAndPending = value;
68+ return ;
69+ case vbucket_state_dead:
70+ // no-op
71+ return ;
72+ }
73+ folly::assume_unreachable ();
74+ }
75+
4676ItemPager::ItemPager (EventuallyPersistentEngine& e, EPStats& st)
4777 : GlobalTask(&e, TaskId::ItemPager, 10 , false ),
4878 engine(e),
4979 stats(st),
5080 available(new std::atomic<bool >(true )),
51- phase(REPLICA_ONLY),
5281 doEvict(false ),
5382 sleepTime(std::chrono::milliseconds(
5483 e.getConfiguration().getPagerSleepTimeMs())),
5584 notified(false ) {
56- // For the hifi_mfu algorithm if a couchbase/persistent bucket we
57- // want to start visiting the replica vbucket first. However for
58- // ephemeral we do not evict from replica vbuckets and therefore
59- // we start with active and pending vbuckets.
60- phase = (engine.getConfiguration ().getBucketType () == " persistent" )
61- ? REPLICA_ONLY
62- : ACTIVE_AND_PENDING_ONLY;
6385}
6486
6587bool ItemPager::run (void ) {
@@ -72,7 +94,7 @@ bool ItemPager::run(void) {
7294 // be that we've gone over HWM have been notified to run, then came back
7395 // down (e.g. 1 byte under HWM), we should still page in this scenario.
7496 // Notified would be false if we were woken by the periodic scheduler
75- bool wasNotified = notified;
97+ const bool wasNotified = notified;
7698
7799 // Clear the notification flag before starting the task's actions
78100 notified.store (false );
@@ -95,53 +117,82 @@ bool ItemPager::run(void) {
95117
96118 ++stats.pagerRuns ;
97119
98- double toKill = (current - static_cast <double >(lower)) / current;
120+ if (current <= lower) {
121+ // early exit - no need to run a paging visitor
122+ return true ;
123+ }
99124
100- EP_LOG_DEBUG (" Using {} bytes of memory, paging out {} of items." ,
101- stats.getEstimatedTotalMemoryUsed (),
102- (toKill * 100.0 ));
125+ VBucketFilter replicaFilter;
126+ VBucketFilter activePendingFilter;
103127
104- // compute active vbuckets evicition bias factor
105- Configuration& cfg = engine.getConfiguration ();
106- size_t activeEvictPerc = cfg.getPagerActiveVbPcnt ();
107- double bias = static_cast <double >(activeEvictPerc) / 50 ;
128+ for (auto vbid : kvBucket->getVBucketsInState (vbucket_state_replica)) {
129+ replicaFilter.addVBucket (vbid);
130+ }
131+
132+ for (auto vbid : kvBucket->getVBucketsInState (vbucket_state_active)) {
133+ activePendingFilter.addVBucket (vbid);
134+ }
135+ for (auto vbid : kvBucket->getVBucketsInState (vbucket_state_pending)) {
136+ activePendingFilter.addVBucket (vbid);
137+ }
138+
139+ ssize_t bytesToEvict = current - lower;
140+
141+ const double replicaEvictableMem = getEvictableBytes (replicaFilter);
142+ const double activePendingEvictableMem =
143+ getEvictableBytes (activePendingFilter);
144+
145+ double replicaEvictionRatio = 0.0 ;
146+ double activeAndPendingEvictionRatio = 0.0 ;
147+
148+ if (kvBucket->canEvictFromReplicas ()) {
149+ // try evict from replicas first if we can
150+ replicaEvictionRatio =
151+ std::min (1.0 , bytesToEvict / replicaEvictableMem);
152+
153+ bytesToEvict -= replicaEvictableMem;
154+ }
155+
156+ if (bytesToEvict > 0 ) {
157+ // replicas are not sufficient (or are not eligible for eviction if
158+ // ephemeral). Not enough memory can be reclaimed from them to
159+ // reach the low watermark.
160+ // Consider active and pending vbuckets too.
161+ // active and pending share an eviction ratio, it need only be
162+ // set once
163+ activeAndPendingEvictionRatio =
164+ std::min (1.0 , bytesToEvict / activePendingEvictableMem);
165+ }
166+
167+ EP_LOG_DEBUG (
168+ " Using {} bytes of memory, paging out {}% of active and "
169+ " pending items, {}% of replica items." ,
170+ stats.getEstimatedTotalMemoryUsed (),
171+ (activeAndPendingEvictionRatio * 100.0 ),
172+ (replicaEvictionRatio * 100.0 ));
108173
109174 VBucketFilter filter;
110- // For the hifi_mfu algorithm use the phase to filter which vbuckets
111- // we want to visit (either replica or active/pending vbuckets).
112- vbucket_state_t state;
113- if (phase == REPLICA_ONLY) {
114- state = vbucket_state_replica;
115- } else if (phase == ACTIVE_AND_PENDING_ONLY) {
116- state = vbucket_state_active;
117- auto acceptableVBs = kvBucket->getVBucketsInState (state);
118- for (auto vb : acceptableVBs) {
119- filter.addVBucket (vb);
120- }
121- state = vbucket_state_pending;
122- } else {
123- throw std::invalid_argument (
124- " ItemPager::run - "
125- " phase is invalid for hifi_mfu eviction algorithm" );
175+
176+ if (replicaEvictionRatio > 0.0 ) {
177+ filter = filter.filter_union (replicaFilter);
126178 }
127- auto acceptableVBs = kvBucket-> getVBucketsInState (state);
128- for ( auto vb : acceptableVBs ) {
129- filter. addVBucket (vb );
179+
180+ if (activeAndPendingEvictionRatio > 0.0 ) {
181+ filter = filter. filter_union (activePendingFilter );
130182 }
131183
132- bool isEphemeral = (cfg.getBucketType () == " ephemeral" );
184+ // compute active vbuckets evicition bias factor
185+ const Configuration& cfg = engine.getConfiguration ();
133186
134187 auto pv = std::make_unique<PagingVisitor>(
135188 *kvBucket,
136189 stats,
137- toKill,
190+ EvictionRatios{activeAndPendingEvictionRatio,
191+ replicaEvictionRatio},
138192 available,
139193 ITEM_PAGER,
140194 false ,
141- bias,
142195 filter,
143- &phase,
144- isEphemeral,
145196 cfg.getItemEvictionAgePercentage (),
146197 cfg.getItemEvictionFreqCounterAgeThreshold ());
147198
@@ -165,6 +216,40 @@ void ItemPager::scheduleNow() {
165216 }
166217}
167218
219+ /* *
220+ * Visitor used to aggregate how much memory could potentially be reclaimed
221+ * by evicting every eligible item from specified vbuckets
222+ */
223+ class VBucketEvictableMemVisitor : public VBucketVisitor {
224+ public:
225+ explicit VBucketEvictableMemVisitor (const VBucketFilter& filter)
226+ : filter(filter) {
227+ }
228+
229+ void visitBucket (const VBucketPtr& vb) override {
230+ if (!filter.empty () && filter (vb->getId ())) {
231+ totalEvictableMemory += vb->getPageableMemUsage ();
232+ }
233+ }
234+
235+ size_t getTotalEvictableMemory () const {
236+ return totalEvictableMemory;
237+ }
238+
239+ private:
240+ const VBucketFilter& filter;
241+ size_t totalEvictableMemory = 0 ;
242+ };
243+
244+ size_t ItemPager::getEvictableBytes (const VBucketFilter& filter) const {
245+ KVBucket* kvBucket = engine.getKVBucket ();
246+
247+ VBucketEvictableMemVisitor visitor (filter);
248+ kvBucket->visit (visitor);
249+
250+ return visitor.getTotalEvictableMemory ();
251+ }
252+
168253ExpiredItemPager::ExpiredItemPager (EventuallyPersistentEngine *e,
169254 EPStats &st, size_t stime,
170255 ssize_t taskTime) :
@@ -218,19 +303,15 @@ bool ExpiredItemPager::run(void) {
218303
219304 VBucketFilter filter;
220305 Configuration& cfg = engine->getConfiguration ();
221- bool isEphemeral =
222- (engine->getConfiguration ().getBucketType () == " ephemeral" );
223306 auto pv = std::make_unique<PagingVisitor>(
224307 *kvBucket,
225308 stats,
226- -1 ,
309+ EvictionRatios{0.0 /* active&pending */ ,
310+ 0.0 /* replica */ }, // evict nothing
227311 available,
228312 EXPIRY_PAGER,
229313 true ,
230- 1 ,
231314 filter,
232- /* pager_phase */ nullptr ,
233- isEphemeral,
234315 cfg.getItemEvictionAgePercentage (),
235316 cfg.getItemEvictionFreqCounterAgeThreshold ());
236317
0 commit comments