-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CASSANDRA-11452: Improved resistence to hash collision attacks
When the candidate and victim's hash code are equal they have the same estimated frequency. This results in the candidate being rejected. If a hash collision attack then the cache will not admit any new entries and the hit rate will be very low (causing poor response times). Ideally we'd use a 64-bit hash, but Java does not provide that natively and, for now, I'd prefer not exposing a builder function (K -> long). A longHashCode() would not fix the problem, but greatly reduce its likelihood. This patch evicts the victim in the case of a collision. This should allow candidates to flow through the eden -> probation segments. This should have a similar effect as using freq1 >= freq2 comparision (not strictly >) without degrading the hit rate in the LIRS traces. @blambov please review so that we can iterate and find a good compromise solution
- Loading branch information
Showing
10 changed files
with
260 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ | |
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
import javax.annotation.Nonnegative; | ||
import javax.annotation.Nonnull; | ||
import javax.annotation.concurrent.NotThreadSafe; | ||
|
||
/** | ||
|
@@ -29,7 +28,7 @@ | |
* @author [email protected] (Ben Manes) | ||
*/ | ||
@NotThreadSafe | ||
final class FrequencySketch<E> { | ||
final class FrequencySketch { | ||
|
||
/* | ||
* This class maintains a 4-bit CountMinSketch [1] with periodic aging to provide the popularity | ||
|
@@ -115,16 +114,16 @@ public boolean isNotInitialized() { | |
/** | ||
* Returns the estimated number of occurrences of an element, up to the maximum (15). | ||
* | ||
* @param e the element to count occurrences of | ||
* @param hashCode the hash code of the element to count occurrences of | ||
* @return the estimated number of occurrences of the element; possibly zero but never negative | ||
*/ | ||
@Nonnegative | ||
public int frequency(@Nonnull E e) { | ||
public int frequency(int hashCode) { | ||
if (isNotInitialized()) { | ||
return 0; | ||
} | ||
|
||
int hash = spread(e.hashCode()); | ||
int hash = spread(hashCode); | ||
int start = (hash & 3) << 2; | ||
int frequency = Integer.MAX_VALUE; | ||
for (int i = 0; i < 4; i++) { | ||
|
@@ -140,14 +139,14 @@ public int frequency(@Nonnull E e) { | |
* of all elements will be periodically down sampled when the observed events exceeds a threshold. | ||
* This process provides a frequency aging to allow expired long term entries to fade away. | ||
* | ||
* @param e the element to add | ||
* @param hashCode the hash code of the element to count occurrences of | ||
*/ | ||
public void increment(@Nonnull E e) { | ||
public void increment(int hashCode) { | ||
if (isNotInitialized()) { | ||
return; | ||
} | ||
|
||
int hash = spread(e.hashCode()); | ||
int hash = spread(hashCode); | ||
int start = (hash & 3) << 2; | ||
|
||
// Loop unrolling improves throughput by 5m ops/s | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,21 +30,21 @@ | |
* @author [email protected] (Ben Manes) | ||
*/ | ||
public final class FrequencySketchTest { | ||
final Integer item = ThreadLocalRandom.current().nextInt(); | ||
final int item = ThreadLocalRandom.current().nextInt(); | ||
|
||
@Test | ||
public void construc() { | ||
FrequencySketch<Integer> sketch = new FrequencySketch<>(); | ||
FrequencySketch sketch = new FrequencySketch(); | ||
assertThat(sketch.table, is(nullValue())); | ||
} | ||
|
||
@Test(dataProvider = "sketch", expectedExceptions = IllegalArgumentException.class) | ||
public void ensureCapacity_negative(FrequencySketch<Integer> sketch) { | ||
public void ensureCapacity_negative(FrequencySketch sketch) { | ||
sketch.ensureCapacity(-1); | ||
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void ensureCapacity_smaller(FrequencySketch<Integer> sketch) { | ||
public void ensureCapacity_smaller(FrequencySketch sketch) { | ||
int size = sketch.table.length; | ||
sketch.ensureCapacity(size / 2); | ||
assertThat(sketch.table.length, is(size)); | ||
|
@@ -53,7 +53,7 @@ public void ensureCapacity_smaller(FrequencySketch<Integer> sketch) { | |
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void ensureCapacity_larger(FrequencySketch<Integer> sketch) { | ||
public void ensureCapacity_larger(FrequencySketch sketch) { | ||
int size = sketch.table.length; | ||
sketch.ensureCapacity(size * 2); | ||
assertThat(sketch.table.length, is(size * 2)); | ||
|
@@ -62,21 +62,21 @@ public void ensureCapacity_larger(FrequencySketch<Integer> sketch) { | |
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void increment_once(FrequencySketch<Integer> sketch) { | ||
public void increment_once(FrequencySketch sketch) { | ||
sketch.increment(item); | ||
assertThat(sketch.frequency(item), is(1)); | ||
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void increment_max(FrequencySketch<Integer> sketch) { | ||
public void increment_max(FrequencySketch sketch) { | ||
for (int i = 0; i < 20; i++) { | ||
sketch.increment(item); | ||
} | ||
assertThat(sketch.frequency(item), is(15)); | ||
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void increment_distinct(FrequencySketch<Integer> sketch) { | ||
public void increment_distinct(FrequencySketch sketch) { | ||
sketch.increment(item); | ||
sketch.increment(item + 1); | ||
assertThat(sketch.frequency(item), is(1)); | ||
|
@@ -87,7 +87,7 @@ public void increment_distinct(FrequencySketch<Integer> sketch) { | |
@Test | ||
public void reset() { | ||
boolean reset = false; | ||
FrequencySketch<Integer> sketch = new FrequencySketch<>(); | ||
FrequencySketch sketch = new FrequencySketch(); | ||
sketch.ensureCapacity(64); | ||
|
||
for (int i = 1; i < 20 * sketch.table.length; i++) { | ||
|
@@ -103,20 +103,20 @@ public void reset() { | |
|
||
@Test | ||
public void heavyHitters() { | ||
FrequencySketch<Double> sketch = makeSketch(512); | ||
FrequencySketch sketch = makeSketch(512); | ||
for (int i = 100; i < 100_000; i++) { | ||
sketch.increment((double) i); | ||
sketch.increment(Double.hashCode(i)); | ||
} | ||
for (int i = 0; i < 10; i += 2) { | ||
for (int j = 0; j < i; j++) { | ||
sketch.increment((double) i); | ||
sketch.increment(Double.hashCode(i)); | ||
} | ||
} | ||
|
||
// A perfect popularity count yields an array [0, 0, 2, 0, 4, 0, 6, 0, 8, 0] | ||
int[] popularity = new int[10]; | ||
for (int i = 0; i < 10; i++) { | ||
popularity[i] = sketch.frequency((double) i); | ||
popularity[i] = sketch.frequency(Double.hashCode(i)); | ||
} | ||
for (int i = 0; i < popularity.length; i++) { | ||
if ((i == 0) || (i == 1) || (i == 3) || (i == 5) || (i == 7) || (i == 9)) { | ||
|
@@ -136,8 +136,8 @@ public Object[][] providesSketch() { | |
return new Object[][] {{ makeSketch(512) }}; | ||
} | ||
|
||
private static <T> FrequencySketch<T> makeSketch(long maximumSize) { | ||
FrequencySketch<T> sketch = new FrequencySketch<>(); | ||
private static FrequencySketch makeSketch(long maximumSize) { | ||
FrequencySketch sketch = new FrequencySketch(); | ||
sketch.ensureCapacity(maximumSize); | ||
ensureRandomSeed(sketch); | ||
return sketch; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.