|
| 1 | +# Improving the entry cache eviction |
| 2 | + |
| 3 | +## Why changing things ? |
| 4 | + |
| 5 | +Computing the acl involving large groups is costly. So we had better keep them in the acl cache. Some of the reason are: |
| 6 | + |
| 7 | +- creating acl cache for large group is costly a group is evicted out of the acl if its entry pointer changed. (i.e that the group was removed from the entry cache then reloaded) |
| 8 | + |
| 9 | +- If values are not already sorted, the time spent to sort the membership values is significant. |
| 10 | + |
| 11 | +A way to improvie thing is to try to keep the large group entries in the entry cache as long as possible. |
| 12 | + |
| 13 | +## Changes |
| 14 | + |
| 15 | + * The main idea is to add a list holding a configured number of entries having the highest weight (based upon load time and group size) |
| 16 | +that pin the entries instead of storing them in the LRU where they could be evicted. |
| 17 | + * Handling a new flag in the entries for PINNED entries to avoid looking in pinned entries list for not pinned entries |
| 18 | + * Refactor the monitoring data to use a struct instead of lots of paramaters |
| 19 | + * Expose average entry load time (in microseconds) and pinned-entry metrics in cache monitoring output. |
| 20 | + |
| 21 | +## New config parameters |
| 22 | + |
| 23 | +New parameters in the backend config entry |
| 24 | + |
| 25 | +| Parameter | Type | Default value | Description | Comments | |
| 26 | +|:------------------------------- |:------------ |:------------- |:------------------------------------------------------------------------------------------------------------------------- | ------- | |
| 27 | +| nsslapd-cache-preserved-entries | Integer >= 0 | 10 | Number of entries that are preserved from eviction | Should stay reasonably small because pinned entries consume memory that cannot be used for something else | |
| 28 | +| nsslapd-cache-debug-pattern | String | NULL | Debug feature allowing to log INFO message when an entry whose dn matches the value is added/removed from the entry cache | Intended for the CI tests and not really usefull for the users | |
| 29 | + |
| 30 | + |
| 31 | +## Internal Data changes |
| 32 | + |
| 33 | +| Struct | field name | Description | |
| 34 | +|:-------------- |:------------------------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 35 | +| backentry | ep\_weight | Entry weight (load time in microseconds) | |
| 36 | +| cache | c\_stats(\*) | cache\_stats statistics | |
| 37 | +| cache | c\_lrus[2] | LRU queues anchor | |
| 38 | +| cache | c\_inst | ldbm\_instance struct (to access the config parameters) | |
| 39 | +| ldbm\_instance | cache\_preserved\_entries | number of entries to preserve | |
| 40 | +| ldbm\_instance | cache\_debug\_pattern | nsslapd-cache-debug-pattern value (may be NULL): Regular expression used to log INFO messages in error log when adding/removing entries in cache (if their dn matches) | |
| 41 | +| ldbm\_instance | cache\_debug\_re | Compiled version of cache\_debug\_pattern | |
| 42 | + |
| 43 | +(\*) All statistics fields of struct cache are moved in new cache\_stats struct |
| 44 | + |
| 45 | +(\*\*) Using the generic word **weight** instead of loadingtime because its meaning may change |
| 46 | + |
| 47 | +#### Entry Cache statistics |
| 48 | + |
| 49 | + struct cache_stats |
| 50 | + |
| 51 | +| Field | Description | |
| 52 | +| ------------------- | ----------------------------------------- | |
| 53 | +| uint64\_t hits | for analysis of hits/misses | |
| 54 | +| uint64\_t tries | for analysis of hits/misses | |
| 55 | +| uint64\_t nentries | current \# entries in cache | |
| 56 | +| int64\_t maxentries | max entries allowed (-1: no limit) | |
| 57 | +| uint64\_t size | current size in bytes | |
| 58 | +| uint64\_t maxsize | max size in bytes | |
| 59 | +| uint64\_t weight | Total weight of all entries | |
| 60 | +| uint64\_t nehw | current \# entries having weight in cache | |
| 61 | + |
| 62 | +## Test |
| 63 | +dirsrvtests/tests/suites/features/entrycache_eviction_test.py verifying pinned-entry caching and eviction thresholds. |
| 64 | + |
| 65 | +## Alternatives |
| 66 | + |
| 67 | +### Alternatives rejected after testing them |
| 68 | + |
| 69 | +These alternatives have been implemented and rejected because of the tests results: |
| 70 | + |
| 71 | +#### LRU eviction algorithm version 1 |
| 72 | + |
| 73 | +##### Implementation |
| 74 | + |
| 75 | +Entry weight is the elapsed time (in microseconds) while loading the entry in the cache. |
| 76 | +Have a configurable factor |
| 77 | +While evicting the entries from the cache keep all entry whose loading time is higher than the configurable factor multiplied by the average loading time. (The idea behind that is that if the entry is long to load we want to try to keep it) |
| 78 | +We can store (in the backentry) the time needed to compute an entry when reading it if not in the cache (and probably also when adding it in the cache too) |
| 79 | +(Simply by getting current time when getting the original backentry then just before replacing it.) and compute statistics like the average time (by keeping the number of entry (which is already done) and the total time of all the entries that are in the cache) and determine a threshold (tunable ?) about when we keep an entry in the cache |
| 80 | +IMHO a 100 or 1000 factor will do . |
| 81 | +Comparing average time to entry time has an advantage: big group entries get naturally evicted if too many of them are in the cache (because in such case, the average building time will then increase until some of the protected entry are no more protected from eviction) |
| 82 | + |
| 83 | +##### Rejection cause: |
| 84 | + |
| 85 | +But after the first tests I discovered that it is pretty hard to determine the right value for the threshold to get reliable results. The value is between 1.2 and 1.5 |
| 86 | + |
| 87 | +#### LRU eviction algorithm version 2 |
| 88 | + |
| 89 | +##### Implementation |
| 90 | + |
| 91 | +While evicting the entries: |
| 92 | + |
| 93 | +- build an array of preserved entries. |
| 94 | + |
| 95 | + - Taking the first n entries in LRU queue walking the LRU queue from its head. |
| 96 | + - Sorting them |
| 97 | + |
| 98 | +- while walking the entry to evict (stating from the queue tail): |
| 99 | + |
| 100 | + - insert the costly entry in the array if neeeded. |
| 101 | + At the end put back the remainding entries in the head of LRU (which tend to sort the LRU queue by putting the hight weight around the head (and avoid trying to evict them) |
| 102 | + |
| 103 | + ##### Rejection reason |
| 104 | + |
| 105 | + Behavior is more predictable than first version but moving the entries has some drawback: |
| 106 | + |
| 107 | +- some entries that should be eveicted are no more evicted (i.e in practice more than n entries get preserved |
| 108 | + |
| 109 | + #### Weight computation version 1 |
| 110 | + |
| 111 | + ##### Implementation |
| 112 | + |
| 113 | + My first trials were done using the elapsed time while loading the entry in micro seconds as weight |
| 114 | + |
| 115 | + ##### Rejection reason |
| 116 | + |
| 117 | + Noise caused by the cpu context switch makes too many regular entry having higher load time than the large groups |
| 118 | + |
| 119 | + ### Alternatives considered but rejected |
| 120 | + |
| 121 | + Some other alternatives has been rejected without being impmlemented |
| 122 | + |
| 123 | + ##### Use a flag instead of a weight |
| 124 | + |
| 125 | + ##### Implementation |
| 126 | + |
| 127 | + Having a flag for large groups in the backentry and simply skip them. |
| 128 | + |
| 129 | + ##### Rejection reason |
| 130 | + |
| 131 | + Cannot prioritize which group will be preserved (between two large groups) and could get in trouble if there are two many large groups. (no more spaces for regular entries) |
| 132 | + |
| 133 | + #### Using getrusage instead of clock_gettime |
| 134 | + |
| 135 | + ##### Implementation |
| 136 | + |
| 137 | + Using getrusage(RUSAGE\_THREAD, \&usage) instead of clock\_gettime |
| 138 | + |
| 139 | + ##### Rejection reason |
| 140 | + |
| 141 | + Although it provides more accurate data and limit the cpu context switch noise, getrusage is 10 times more costly than clock\_gettime. |
| 142 | + |
| 143 | + #### Using weight variance to decide if evicting the entry |
| 144 | + |
| 145 | + ##### Implementation |
| 146 | + |
| 147 | + checking if w \> a+y\*v would allow to preserve x% of the entries |
| 148 | + |
| 149 | + ##### Rejection reason |
| 150 | + |
| 151 | + it seems an overhead especially since we need to compute a square root to do that. maybe checking w2 \> a2\+y\*v2 will do something but I am not convinced |
| 152 | + |
| 153 | + #### Using number of members as weight |
| 154 | + |
| 155 | + ##### Implementation |
| 156 | + |
| 157 | + Use the number of members as weight instead of the loading time. |
| 158 | + |
| 159 | + ##### Rejection reason |
| 160 | + |
| 161 | + We may perhaps also preserve some other complex entries |
| 162 | + |
| 163 | +#### Pinning group entries by their dn |
| 164 | + |
| 165 | +##### Implementation |
| 166 | + |
| 167 | + Have a parameter holding a list of DN and never put the entries having these dn |
| 168 | + in the LRU (or skip these DN while evicting entries) |
| 169 | + |
| 170 | +##### Rejection reason |
| 171 | + |
| 172 | +- Comparing DNs may slow things especially if the list contains more than a few dns |
| 173 | +- Admin have to explicitly specifies the groups list |
| 174 | + |
| 175 | +#### Handling pinned entries within the LRU |
| 176 | +##### Implementation |
| 177 | +Embedding the handling of the pinned entries within the LRU code as originally intented |
| 178 | + |
| 179 | +##### Rejection reason |
| 180 | +Would make more difficult the replacement of LRU by something more efficient like ARC |
0 commit comments