Skip to content

Commit cf8a806

Browse files
author
Peter Alfonsi
committed
Implemented/tested counter+removal list setup to allow more removals
Signed-off-by: Peter Alfonsi <[email protected]>
1 parent 0085306 commit cf8a806

File tree

2 files changed

+184
-15
lines changed

2 files changed

+184
-15
lines changed

server/src/main/java/org/opensearch/common/cache/tier/keystore/RBMIntKeyLookupStore.java

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
import org.opensearch.common.metrics.CounterMetric;
3636

37+
import java.util.ArrayList;
38+
import java.util.HashMap;
3739
import java.util.HashSet;
3840
import java.util.concurrent.locks.Lock;
3941
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -73,7 +75,8 @@ public int getValue() {
7375
protected final int modulo;
7476
KeyStoreStats stats;
7577
protected RoaringBitmap rbm;
76-
private HashSet<Integer> collidedInts;
78+
private HashMap<Integer, CounterMetric> collidedIntCounters;
79+
private HashMap<Integer, HashSet<Integer>> removalSets;
7780
protected RBMSizeEstimator sizeEstimator;
7881
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
7982
protected final Lock readLock = lock.readLock();
@@ -89,7 +92,8 @@ public RBMIntKeyLookupStore(KeystoreModuloValue moduloValue, long memSizeCapInBy
8992
sizeEstimator = new RBMSizeEstimator(modulo);
9093
this.stats = new KeyStoreStats(memSizeCapInBytes, calculateMaxNumEntries(memSizeCapInBytes));
9194
this.rbm = new RoaringBitmap();
92-
collidedInts = new HashSet<>();
95+
this.collidedIntCounters = new HashMap<>();
96+
this.removalSets = new HashMap<>();
9397
}
9498

9599
protected int calculateMaxNumEntries(long memSizeCapInBytes) {
@@ -105,7 +109,14 @@ private final int transform(int value) {
105109

106110
private void handleCollisions(int transformedValue) {
107111
stats.numCollisions.inc();
108-
collidedInts.add(transformedValue);
112+
CounterMetric numCollisions = collidedIntCounters.get(transformedValue);
113+
if (numCollisions == null) { // First time the transformedValue has had a collision
114+
numCollisions = new CounterMetric();
115+
numCollisions.inc(2);
116+
collidedIntCounters.put(transformedValue, numCollisions); // Initialize the number of colliding keys to 2
117+
} else {
118+
numCollisions.inc();
119+
}
109120
}
110121

111122
@Override
@@ -130,6 +141,16 @@ public boolean add(Integer value) throws Exception {
130141
stats.size.inc();
131142
return true;
132143
}
144+
// If the value is already pending removal, take it out of the removalList
145+
HashSet<Integer> removalSet = removalSets.get(transformedValue);
146+
if (removalSet != null) {
147+
removalSet.remove(value);
148+
// Don't increment the counter - this is handled by handleCollisions() later
149+
if (removalSet.isEmpty()) {
150+
removalSets.remove(transformedValue);
151+
}
152+
}
153+
133154
handleCollisions(transformedValue);
134155
return false;
135156
} finally {
@@ -159,6 +180,13 @@ public Integer getInternalRepresentation(Integer value) {
159180
return Integer.valueOf(transform(value));
160181
}
161182

183+
/**
184+
* Attempts to remove a value from the keystore. WARNING: Removing keys which have not been added to the keystore
185+
* may cause undefined behavior, including future false negatives!!
186+
* @param value The value to attempt to remove.
187+
* @return true if the value was removed, false otherwise
188+
* @throws Exception
189+
*/
162190
@Override
163191
public boolean remove(Integer value) throws Exception {
164192
if (value == null) {
@@ -170,24 +198,54 @@ public boolean remove(Integer value) throws Exception {
170198
if (!rbm.contains(transformedValue)) { // saves additional transform() call
171199
return false;
172200
}
201+
// move below into write lock
173202
stats.numRemovalAttempts.inc();
174-
if (collidedInts.contains(transformedValue)) {
175-
return false;
176-
}
177203
} finally {
178204
readLock.unlock();
179205
}
180206
writeLock.lock();
181207
try {
182-
rbm.remove(transformedValue);
183-
stats.size.dec();
184-
stats.numSuccessfulRemovals.inc();
208+
CounterMetric numCollisions = collidedIntCounters.get(transformedValue);
209+
if (numCollisions != null) {
210+
// This transformed value has had a collision before
211+
HashSet<Integer> removalSet = removalSets.get(transformedValue);
212+
if (removalSet == null) {
213+
// First time a removal has been attempted for this transformed value
214+
HashSet<Integer> newRemovalSet = new HashSet<>();
215+
newRemovalSet.add(value); // Add the key value, not the transformed value, to the list of attempted removals for this transformedValue
216+
removalSets.put(transformedValue, newRemovalSet);
217+
numCollisions.dec();
218+
} else {
219+
if (removalSet.contains(value)) {
220+
return false; // We have already attempted to remove this value. Do nothing
221+
}
222+
removalSet.add(value);
223+
numCollisions.dec();
224+
// If numCollisions has reached zero, we can safely remove all values in removalList
225+
if (numCollisions.count() == 0) {
226+
removeFromRBM(transformedValue);
227+
collidedIntCounters.remove(transformedValue);
228+
removalSets.remove(transformedValue);
229+
return true;
230+
}
231+
}
232+
return false;
233+
}
234+
// Otherwise, there's not been a collision for this transformedValue, so we can safely remove
235+
removeFromRBM(transformedValue);
185236
return true;
186237
} finally {
187238
writeLock.unlock();
188239
}
189240
}
190241

242+
// Helper fn for remove()
243+
private void removeFromRBM(int transformedValue) {
244+
rbm.remove(transformedValue);
245+
stats.size.dec();
246+
stats.numSuccessfulRemovals.inc();
247+
}
248+
191249
@Override
192250
public int getSize() {
193251
readLock.lock();
@@ -218,7 +276,7 @@ public boolean isCollision(Integer value1, Integer value2) {
218276

219277
@Override
220278
public long getMemorySizeInBytes() {
221-
return sizeEstimator.getSizeInBytes((int) stats.size.count()) + RBMSizeEstimator.getHashsetMemSizeInBytes(collidedInts.size());
279+
return sizeEstimator.getSizeInBytes((int) stats.size.count()); // + RBMSizeEstimator.getHashsetMemSizeInBytes(collidedInts.size());
222280
}
223281

224282
@Override
@@ -234,7 +292,8 @@ public boolean isFull() {
234292
@Override
235293
public void regenerateStore(Integer[] newValues) throws Exception {
236294
rbm.clear();
237-
collidedInts = new HashSet<>();
295+
collidedIntCounters = new HashMap<>();
296+
removalSets = new HashMap<>();
238297
stats.size = new CounterMetric();
239298
stats.numAddAttempts = new CounterMetric();
240299
stats.numCollisions = new CounterMetric();
@@ -265,6 +324,14 @@ public boolean valueHasHadCollision(Integer value) {
265324
if (value == null) {
266325
return false;
267326
}
268-
return collidedInts.contains(transform(value));
327+
return collidedIntCounters.containsKey(transform(value));
328+
}
329+
330+
CounterMetric getNumCollisionsForValue(int value) { // package private for testing
331+
return collidedIntCounters.get(transform(value));
332+
}
333+
334+
HashSet<Integer> getRemovalSetForValue(int value) {
335+
return removalSets.get(transform(value));
269336
}
270337
}

server/src/test/java/org/opensearch/common/cache/tier/keystore/RBMIntKeyLookupStoreTests.java

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@
3434
import org.opensearch.common.Randomness;
3535
import org.opensearch.common.cache.tier.keystore.RBMIntKeyLookupStore;
3636
import org.opensearch.common.cache.tier.keystore.RBMSizeEstimator;
37+
import org.opensearch.common.metrics.CounterMetric;
3738
import org.opensearch.test.OpenSearchTestCase;
3839

3940
import java.util.ArrayList;
41+
import java.util.HashSet;
4042
import java.util.Random;
4143
import java.util.concurrent.Executors;
4244
import java.util.concurrent.Future;
@@ -277,12 +279,10 @@ public void testNullInputs() throws Exception {
277279
}
278280

279281
public void testMemoryCapValueInitialization() {
280-
// double[] logModulos = new double[] { 0.0, 31.2, 30, 29, 28, 13 };
281-
// 0, 31, 29, 28, 26
282282
RBMIntKeyLookupStore.KeystoreModuloValue[] mods = RBMIntKeyLookupStore.KeystoreModuloValue.values();
283283
double[] expectedMultipliers = new double[] { 1.2, 1.2, 1, 1, 1 };
284284
double[] expectedSlopes = new double[] { 0.637, 0.637, 0.619, 0.614, 0.629 };
285-
double[] expectedIntercepts = new double[] { 3.091, 3.091, 2.993, 2.905, 2.603 }; // check the numbers closer later
285+
double[] expectedIntercepts = new double[] { 3.091, 3.091, 2.993, 2.905, 2.603 };
286286
long memSizeCapInBytes = (long) 100.0 * RBMSizeEstimator.BYTES_IN_MB;
287287
double delta = 0.01;
288288
for (int i = 0; i < mods.length; i++) {
@@ -295,4 +295,106 @@ public void testMemoryCapValueInitialization() {
295295
}
296296

297297
}
298+
299+
public void testRemovalLogic() throws Exception {
300+
RBMIntKeyLookupStore.KeystoreModuloValue moduloValue = RBMIntKeyLookupStore.KeystoreModuloValue.TWO_TO_TWENTY_SIX;
301+
int modulo = moduloValue.getValue();
302+
RBMIntKeyLookupStore kls = new RBMIntKeyLookupStore(moduloValue, 0L);
303+
304+
// Test standard sequence: add K1, K2, K3 which all transform to C, then:
305+
// Remove K3
306+
// Remove K2, re-add it, re-remove it twice (duplicate should do nothing)
307+
// Remove K1, which should finally actually remove everything
308+
int c = -42;
309+
int k1 = c + modulo;
310+
int k2 = c + 2 * modulo;
311+
int k3 = c + 3 * modulo;
312+
kls.add(k1);
313+
assertTrue(kls.contains(k1));
314+
assertTrue(kls.contains(k3));
315+
kls.add(k2);
316+
CounterMetric numCollisions = kls.getNumCollisionsForValue(k2);
317+
assertNotNull(numCollisions);
318+
assertEquals(2, numCollisions.count());
319+
kls.add(k3);
320+
assertEquals(3, numCollisions.count());
321+
assertEquals(1, kls.getSize());
322+
323+
boolean removed = kls.remove(k3);
324+
assertFalse(removed);
325+
HashSet<Integer> removalSet = kls.getRemovalSetForValue(k3);
326+
assertEquals(1, removalSet.size());
327+
assertTrue(removalSet.contains(k3));
328+
assertEquals(2, numCollisions.count());
329+
assertEquals(1, kls.getSize());
330+
331+
removed = kls.remove(k2);
332+
assertFalse(removed);
333+
assertEquals(2, removalSet.size());
334+
assertTrue(removalSet.contains(k2));
335+
assertEquals(1, numCollisions.count());
336+
assertEquals(1, kls.getSize());
337+
338+
kls.add(k2);
339+
assertEquals(1, removalSet.size());
340+
assertFalse(removalSet.contains(k2));
341+
assertEquals(2, numCollisions.count());
342+
assertEquals(1, kls.getSize());
343+
344+
removed = kls.remove(k2);
345+
assertFalse(removed);
346+
assertEquals(2, removalSet.size());
347+
assertTrue(removalSet.contains(k2));
348+
assertEquals(1, numCollisions.count());
349+
assertEquals(1, kls.getSize());
350+
351+
removed = kls.remove(k2);
352+
assertFalse(removed);
353+
assertEquals(2, removalSet.size());
354+
assertTrue(removalSet.contains(k2));
355+
assertEquals(1, numCollisions.count());
356+
assertEquals(1, kls.getSize());
357+
358+
removed = kls.remove(k1);
359+
assertTrue(removed);
360+
assertNull(kls.getRemovalSetForValue(k1));
361+
assertNull(kls.getNumCollisionsForValue(k1));
362+
assertEquals(0, kls.getSize());
363+
}
364+
365+
public void testRemovalLogicWithHashCollision() throws Exception {
366+
RBMIntKeyLookupStore.KeystoreModuloValue moduloValue = RBMIntKeyLookupStore.KeystoreModuloValue.TWO_TO_TWENTY_SIX;
367+
int modulo = moduloValue.getValue();
368+
RBMIntKeyLookupStore kls = new RBMIntKeyLookupStore(moduloValue, 0L);
369+
370+
// Test adding K1 twice (maybe two keys hash to K1), then removing it twice.
371+
// We expect it to be unable to remove the last one, but there should be no false negatives.
372+
int c = 77;
373+
int k1 = c + modulo;
374+
int k2 = c + 2 * modulo;
375+
kls.add(k1);
376+
kls.add(k2);
377+
CounterMetric numCollisions = kls.getNumCollisionsForValue(k1);
378+
assertEquals(2, numCollisions.count());
379+
kls.add(k1);
380+
assertEquals(3, numCollisions.count());
381+
382+
boolean removed = kls.remove(k1);
383+
assertFalse(removed);
384+
HashSet<Integer> removalSet = kls.getRemovalSetForValue(k1);
385+
assertTrue(removalSet.contains(k1));
386+
assertEquals(2, numCollisions.count());
387+
388+
removed = kls.remove(k2);
389+
assertFalse(removed);
390+
assertTrue(removalSet.contains(k2));
391+
assertEquals(1, numCollisions.count());
392+
393+
removed = kls.remove(k1);
394+
assertFalse(removed);
395+
assertTrue(removalSet.contains(k1));
396+
assertEquals(1, numCollisions.count());
397+
assertTrue(kls.contains(k1));
398+
assertTrue(kls.contains(k2));
399+
}
298400
}

0 commit comments

Comments
 (0)