Skip to content

Commit 53aee32

Browse files
AlnisMmeta-codesync[bot]
authored andcommitted
update slab rebalancing docs
Summary: Fixing some inaccuracies and adding documentation around some configs. ___ overriding_review_checks_triggers_an_audit_and_retroactive_review Oncall Short Name: cachelib Differential Revision: D104251642 fbshipit-source-id: b478a5f7c69cca06511dc7b8723e9e265b6ea83c
1 parent e7efb98 commit 53aee32

1 file changed

Lines changed: 151 additions & 40 deletions

File tree

website/docs/Cache_Library_User_Guides/pool_rebalance_strategy.md

Lines changed: 151 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,68 +9,117 @@ If your cachelib use case always allocates objects of a single size, then
99
rebalancing is almost always not required for you. Rebalancing of cache
1010
becomes *important only when you store variable sized objects* in cache and
1111
your workload's footprint of access across these objects can potentially
12-
change over time. Often when you cache objects of variable size, the
12+
change over time. Often, when you cache objects of variable size, the
1313
distribution of `find()` and `allocate()` across object sizes would vary over
14-
time. This leads to poor fragmentation in the cache memory footprint. For
15-
example, imagine you had a cache of 30 GB and store objects of size 100 bytes,
16-
500 bytes, and 1,000 bytes, each occupying 10 GB when warmed up. When your
17-
application workload changes over time, the optimal sizes for these objects
18-
could vary as well requiring more memory for one vs. other. With pool
19-
rebalancing, this kind of workload change would usually result in metrics like
20-
eviction age and hit ratios being sub-optimal over time.
14+
time. This can leave slabs assigned to allocation classes that no longer match
15+
the current workload. For example, imagine you had a cache of 30 GB and store
16+
objects of size 100 bytes, 500 bytes, and 1,000 bytes, each occupying 10 GB
17+
when warmed up. When your application workload changes over time, the optimal
18+
sizes for these objects could vary as well, requiring more memory for one
19+
object size than another. Without pool rebalancing, this kind of workload
20+
change would usually result in metrics like eviction age and hit ratios being
21+
sub-optimal over time.
2122

2223
Cachelib offers several rebalancing strategies to offset this behavior by
2324
asking the cache to restructure the underlying memory allocated among objects
2425
of different sizes.
2526

2627
## How does it work?
2728

28-
Internally, cachelib divides up memory into slabs and allocates slabs across
29-
objects of various sizes. When pool rebalancing is enabled, cachelib evicts
30-
objects of one size in favor of other and moves the backing slabs to the other
31-
objects to enable caching more of them. Cachelib can do this automatically and
32-
periodically. However, you will have to pick a strategy that matters to you
33-
and configure how often the rebalancing happens.
34-
35-
Rebalancing is an asynchronous operation and does not impact the latencies of
36-
other cachelib operations like `find()`, `insertOrReplace()`, or `allocate()`.
37-
Rebalancing moves memory at the rate of 4 MB for every interval that you
38-
configure if you would like to estimate a good rate.
29+
Internally, cachelib divides memory into 4 MB slabs and assigns slabs to
30+
allocation classes within a pool. Each allocation class serves a specific item
31+
size range and has its own eviction container. Pool rebalancing is an
32+
intra-pool operation: cachelib releases one slab from a victim allocation class
33+
and either gives it to a receiver allocation class in the same pool or returns
34+
it to the pool's free slab list.
35+
36+
The pool rebalancer is a periodic background worker. For each regular pool, a
37+
worker run asks the configured strategy to pick a victim allocation class and,
38+
when the strategy supports it, a receiver allocation class. Cachelib then
39+
releases one slab from the victim and either gives it to the receiver in the
40+
same pool or returns it to the pool's free slab list. Active items on the
41+
selected slab are either copied into new allocations through the
42+
`enableMovingOnSlabRelease()` callback (Cachelib refers to this as *moving*),
43+
or evicted if they cannot be relocated.
44+
45+
A successful strategy action moves one slab, or 4 MB, per pool per interval, so
46+
with multiple regular pools the total movement scales linearly with pool count.
47+
48+
Slab release runs asynchronously, but it is still real cache work. It may run
49+
user move callbacks, execute evictions, and wait for outstanding item handles
50+
on the selected slab before memory can be freed. Keep item handles short-lived
51+
so eviction and slab rebalancing are not blocked. `slabRebalanceTimeout`
52+
controls how long slab release waits while marking an item as moving before it
53+
aborts; the default is 10 minutes, and `0` waits forever.
3954

4055
## Enabling pool rebalancing
4156

42-
To enable pool rebalancing, specify these two parameters:
57+
`CacheAllocatorConfig` has pool rebalancing enabled by default with the base
58+
`RebalanceStrategy` and a 1 second interval. The base strategy only reacts to
59+
allocation failures: it picks the allocation class with recent allocation
60+
failures as the receiver, and picks a victim from the allocation class with the
61+
most slabs when the strategy did not choose a victim.
62+
63+
To disable pool rebalancing, set a null strategy or a zero interval:
4364

44-
1. **Strategy** for re-evaluating metrics about your cache and figuring out a rebalancing action
65+
```cpp
66+
config.enablePoolRebalancing(nullptr, std::chrono::seconds{0});
67+
```
68+
69+
To override the default behavior, specify these parameters:
70+
71+
1. **Strategy** for re-evaluating metrics about your cache and figuring out a
72+
rebalancing action
4573
2. **Interval** of executing the rebalancing
74+
3. **Allocation-failure wakeup behavior** (optional). By default, allocation
75+
failures wake the rebalancer before the next scheduled interval. Pass `true`
76+
as the third argument to `enablePoolRebalancing()` to disable that forced
77+
wakeup.
4678
4779
For example:
4880
49-
5081
```cpp
5182
auto rebalanceStrategy =
5283
std::make_shared<cachelib::LruTailAgeStrategy>(rebalanceConfig);
5384
5485
config.enablePoolRebalancing(
5586
rebalanceStrategy,
56-
std::chrono::seconds(kRebalanceIntervalSecs)
87+
std::chrono::seconds(kRebalanceIntervalSecs),
88+
false // keep allocation-failure forced wakeups enabled
5789
);
5890
```
5991

92+
You can also override the rebalance strategy for a specific pool by passing a
93+
strategy to `addPool()` or by calling `overridePoolRebalanceStrategy()`.
6094

6195
### Picking a strategy
6296

63-
Cachelib offers a few pre-package strategies for rebalancing that you can pick
97+
Cachelib offers a few pre-packaged strategies for rebalancing that you can pick
6498
from. They differ by what they try to optimize for based on traditional wisdom
6599
of large scale caches like social graph caches and general purpose look-aside
66100
key value caches. These are good defaults to start with, but you can also come
67101
up with your own implementation if you have other goals.
68102

69-
#### Lru TailAge
103+
#### Base strategy
70104

71-
LruTailAge is a fair policy that ensures that objects of different sizes get the same eviction age in cache. For example, in steady state for your cache, you could have 100 byte objects getting 1 hr lifetime vs 1000 byte objects getting 30 min lifetime. This strategy tries to make the eviction age for various sizes similar. You can configure the following parameters(LruTailAgeStrategy::Config) (whose default values are pretty good to begin with):
105+
`RebalanceStrategy` is the default strategy. It does not optimize hit rate or
106+
eviction-age fairness. It only helps allocation classes that have seen
107+
allocation failures since the previous rebalancer run. This is a conservative
108+
default that helps a class get at least one slab when it cannot allocate or
109+
evict within its current allocation class.
72110

73-
* `tailAgeDiffRatio`
111+
#### LRU Tail Age
112+
113+
`LruTailAgeStrategy` is a fair policy that tries to make objects of different
114+
sizes get similar eviction ages in cache. For example, in steady state for your
115+
cache, you could have 100 byte objects getting a 1 hour lifetime while 1,000
116+
byte objects get a 30 minute lifetime. This strategy picks the allocation class
117+
with the oldest projected eviction age as the victim and the allocation class
118+
with the youngest eviction age as the receiver.
119+
120+
You can configure the following parameters in `LruTailAgeStrategy::Config`:
121+
122+
* `tailAgeDifferenceRatio`
74123
This defines how tight the tail age of various object sizes you want them to be. Setting it to 0.1 means that you don't want the min and max age to differ by more than 10%.
75124

76125
* `minTailAgeDifference`
@@ -80,51 +129,111 @@ This specifies a threshold of how big the actual diff ratio should be to warrant
80129
This specifies the minimum amount of memory in slabs that specific object size can not go below while rebalancing. Keep in mind that this is specified in slabs and not in bytes.
81130

82131
* `numSlabsFreeMem`
83-
When you specify rebalancing under this mode, cachlib aggressively moves memory from object sizes that have a lot of free memory. This specifies the threshold for triggering that behavior.
132+
If an allocation class has more than this many slabs worth of free memory
133+
(i.e. `numSlabsFreeMem * Slab::kSize` bytes) and has not recently evicted,
134+
it is prioritized as a victim.
84135

85136
* `slabProjectionLength`
86-
This lets you estimate the min and max by picking a projected eviction age instead of the real eviction age. This can sometimes let you get better results.
137+
How many slabs worth of items to project when computing the victim's
138+
projected tail age.
87139

88-
For example:
140+
* `queueSelector`
141+
Which eviction-age queue to use: hot, warm, or cold. Not every eviction
142+
policy has separate hot, warm, and cold queues; when it does not, the policy
143+
defines how these values map to its available eviction-age stats.
89144

145+
* `getWeight`
146+
An optional weight function for weighted tail age. When this is set,
147+
`tailAgeDifferenceRatio` and `minTailAgeDifference` are ignored.
148+
149+
For example:
90150

91151
```cpp
92152
cachelib::LruTailAgeStrategy::Config cfg(ratio, kLruTailAgeStrategyMinSlabs);
93-
cfg.slabProjectionLength = 0; // dont project or estimate tail age
153+
cfg.slabProjectionLength = 0; // don't project or estimate tail age
94154
cfg.numSlabsFreeMem = 10; // ok to have ~40 MB free memory in unused allocations
95155
auto rebalanceStrategy = std::make_shared<cachelib::LruTailAgeStrategy>(cfg);
96156

97157
// every 5 seconds, re-evaluate the eviction ages and rebalance the cache.
98158
config.enablePoolRebalancing(std::move(rebalanceStrategy), std::chrono::seconds(5));
99159
```
100160
161+
#### Hits per slab
101162
102-
#### Hit based
163+
`HitsPerSlabStrategy` tries to optimize the overall hit ratio rather than ensuring a fairness in the cache eviction age. This should result in a relatively higher hit ratio. However, it might potentially make your cache contain more of objects that give hits vs. objects that are expensive to recompute. For example, the cost of miss on objects is not uniform.
103164
104-
HitBased approach tries to optimize the overall hit ratio rather than ensuring a fairness in the cache eviction age. This should result in a relatively higher hit ratio. However, it might potentially make your cache contain more of objects that give hits vs. objects that are expensive to recompute. For example, the cost of miss on objects is not uniform. To control the downsides of such implications, cachelib offers these parameters(HitsPerSlabStrategy::Config). Most of these are similar to the LruTailAge parameters, however, their semantics could slightly differ in the following ways:
165+
You can configure the following parameters in `HitsPerSlabStrategy::Config`:
105166
106167
* `minDiff`
107-
Like tailAgeDiffRatio, this controls the minimum improvement that should trigger a rebalancing.
168+
The minimum absolute improvement in hits per slab required before a rebalance
169+
happens.
170+
171+
* `diffRatio`
172+
The minimum relative improvement required before a rebalance happens. Both
173+
`minDiff` and `diffRatio` must be satisfied.
174+
175+
* `minSlabs`
176+
The minimum number of slabs to retain in every allocation class. An
177+
allocation class with `minSlabs` or fewer slabs cannot be picked as the
178+
victim.
179+
180+
* `numSlabsFreeMem` and `enableVictimByFreeMem`
181+
When enabled, prioritize allocation classes with more than
182+
`numSlabsFreeMem` slab equivalents of free memory as victims.
183+
108184
* `minLruTailAge`
109-
When using hit based rebalancing, if you want to ensure some level of fairness by guaranteeing some eviction age, you can configure it through this parameter.
185+
Require a victim to have at least this eviction age, and prioritize receivers
186+
below this eviction age. Use this to preserve some eviction-age fairness
187+
while optimizing hits.
188+
189+
* `maxLruTailAge`
190+
Prefer victims above this eviction age and receivers below this eviction age.
191+
If no receiver satisfies the limit, the strategy falls back to the hits-based
192+
choice.
193+
194+
* `updateHitsOnEveryAttempt`
195+
Update the hit-count baseline on every rebalance attempt, even if no slab is
196+
moved. By default, the baseline is updated after successful rebalances.
197+
198+
* `getWeight`
199+
An optional weight function to bias hit-per-slab values. Higher weights make
200+
an allocation class more likely to receive slabs and less likely to donate.
201+
202+
* `classIdTargetEvictionAge`
203+
Optional per-class target eviction ages. Victims must meet their target, and
204+
receivers below target are prioritized.
110205
111206
#### Marginal hits
112207
113208
This strategy ensures that the marginal hits (estimated by the hits in the tail part of LRU) across different object sizes are similar. Unlike hit based strategy which counts for historical count of hits across the entire cache, this tracks which objects could marginally benefit from getting more memory. To enable this, you need to use the MM2Q eviction policy and enable tail hits tracking (`Allocator::Config::enableTailHitsTracking()`).
114209
210+
You can configure the following parameters in `MarginalHitsStrategy::Config`:
211+
212+
* `movingAverageParam`
213+
The smoothing parameter used for marginal-hit rankings.
214+
215+
* `minSlabs`
216+
The minimum number of slabs to retain in every allocation class. An
217+
allocation class with `minSlabs` or fewer slabs cannot be picked as the
218+
victim.
219+
220+
* `maxFreeMemSlabs`
221+
An allocation class can be picked as the receiver only when its free memory
222+
is below this many slab equivalents.
223+
115224
#### Free memory
116225
117226
This strategy frees a slab from an allocation class that satisfies all of the following requirements:
118227
- this allocation class has total slabs above `minSlabs`
119-
- this allocation class has free slabs above `numFreeSlabs`
228+
- this allocation class has more than `numFreeSlabs` slab equivalents of total free memory
120229
- this allocation class has the most total free memory among all non-evicting (i.e. no eviction is currently happening) allocation classes in the pool
121230
122231
Note: this strategy does not specify a target allocation class to receive the freed slab.
123-
Here are the parameters to configure this strategy:
232+
You can configure the following parameters in `FreeMemStrategy::Config`:
124233
* `minSlabs`
125234
The minimum number of slabs to retain in every allocation class. Default is 1.
126235
* `numFreeSlabs`
127-
The threshold of required free slabs. Default is 3.
236+
The free-memory threshold, in slab equivalents. Default is 3.
128237
* `maxUnAllocatedSlabs`
129238
FreeMem strategy will not rebalance anything if the number of free slabs in this pool is more than this number. Default is 1000.
130239
@@ -136,14 +245,16 @@ In addition, if you have some application specific context on how you can improv
136245
```cpp
137246
virtual RebalanceContext pickVictimAndReceiverImpl(
138247
const CacheBase& /*cache*/,
139-
PoolId /*pid*/
248+
PoolId /*pid*/,
249+
const PoolStats& /*poolStats*/
140250
) {
141251
return {};
142252
}
143253
144254
virtual ClassId pickVictimImpl(
145255
const CacheBase& /*cache*/,
146-
PoolId /*pid*/
256+
PoolId /*pid*/,
257+
const PoolStats& /*poolStats*/
147258
) {
148259
return Slab::kInvalidClassId;
149260
}

0 commit comments

Comments
 (0)