Skip to content

Commit

Permalink
Test different functionalities of client side cache (#3828)
Browse files Browse the repository at this point in the history
  • Loading branch information
sazzad16 authored Apr 29, 2024
1 parent bb99c16 commit 82c0226
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 61 deletions.
18 changes: 12 additions & 6 deletions src/main/java/redis/clients/jedis/csc/ClientSideCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public final void clear() {
invalidateAllKeysAndCommandHashes();
}

public final void removeKey(Object key) {
invalidateKeyAndRespectiveCommandHashes(key);
}

public final void invalidate(List list) {
if (list == null) {
invalidateAllKeysAndCommandHashes();
Expand All @@ -64,11 +68,13 @@ private void invalidateAllKeysAndCommandHashes() {
}

private void invalidateKeyAndRespectiveCommandHashes(Object key) {
if (!(key instanceof byte[])) {
throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key));
}

final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key);
// if (!(key instanceof byte[])) {
// // This should be called internally. That's why throwing AssertionError instead of IllegalArgumentException.
// throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key));
// }
//
// final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key);
final ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);

Set<Long> hashes = keyToCommandHashes.get(mapKey);
if (hashes != null) {
Expand Down Expand Up @@ -111,7 +117,7 @@ public final <T> T get(Function<CommandObject<T>, T> loader, CommandObject<T> co
private ByteBuffer makeKeyForKeyToCommandHashes(Object key) {
if (key instanceof byte[]) return makeKeyForKeyToCommandHashes((byte[]) key);
else if (key instanceof String) return makeKeyForKeyToCommandHashes(SafeEncoder.encode((String) key));
else throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key));
else throw new IllegalArgumentException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key));
}

private static ByteBuffer makeKeyForKeyToCommandHashes(byte[] b) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.cache.CacheBuilder;
import com.github.benmanes.caffeine.cache.stats.CacheStats;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.hamcrest.Matchers;
Expand All @@ -22,73 +25,35 @@
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPooled;

public class ClientSideCacheLibsTest {

public class CaffeineClientSideCacheTest {
protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1);

protected Jedis control;

@Before
public void setUp() throws Exception {
control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build());
control.flushAll();
}

@After
public void tearDown() throws Exception {
control.close();
}

private static final Supplier<JedisClientConfig> clientConfig
= () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build();

private static final Supplier<GenericObjectPoolConfig<Connection>> singleConnectionPoolConfig
= () -> {
ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();
poolConfig.setMaxTotal(1);
return poolConfig;
};

@Test
public void guavaSimple() {
GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10)
.hashFunction(com.google.common.hash.Hashing.farmHashFingerprint64()).build();
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) {
control.set("foo", "bar");
assertEquals("bar", jedis.get("foo"));
control.del("foo");
assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ?
}
}

@Test
public void guavaMore() {

com.google.common.cache.Cache guava = CacheBuilder.newBuilder().recordStats().build();

try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava),
singleConnectionPoolConfig.get())) {
control.set("foo", "bar");
assertEquals(0, guava.size());
assertEquals("bar", jedis.get("foo"));
assertEquals(1, guava.size());
control.flushAll();
assertEquals(1, guava.size());
assertEquals("bar", jedis.get("foo"));
assertEquals(1, guava.size());
jedis.ping();
assertEquals(0, guava.size());
assertNull(jedis.get("foo"));
assertEquals(0, guava.size());
}

com.google.common.cache.CacheStats stats = guava.stats();
assertEquals(1L, stats.hitCount());
assertThat(stats.missCount(), Matchers.greaterThan(0L));
}


@Test
public void caffeineSimple() {
public void simple() {
CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10).build();
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) {
control.set("foo", "bar");
Expand All @@ -97,12 +62,12 @@ public void caffeineSimple() {
assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ?
}
}

@Test
public void caffeineMore() {

com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build();

public void individualCommandsAndThenStats() {
Cache caffeine = Caffeine.newBuilder().recordStats().build();
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
new CaffeineClientSideCache(caffeine, new OpenHftCommandHasher()),
singleConnectionPoolConfig.get())) {
Expand All @@ -119,9 +84,51 @@ public void caffeineMore() {
assertNull(jedis.get("foo"));
assertEquals(0, caffeine.estimatedSize());
}

com.github.benmanes.caffeine.cache.stats.CacheStats stats = caffeine.stats();
CacheStats stats = caffeine.stats();
assertEquals(1L, stats.hitCount());
assertThat(stats.missCount(), Matchers.greaterThan(0L));
}

@Test
public void maximumSize() {
final long maxSize = 10;
final long maxEstimatedSize = 40;
int count = 1000;
for (int i = 0; i < count; i++) {
control.set("k" + i, "v" + i);
}

Cache caffeine = Caffeine.newBuilder().maximumSize(maxSize).recordStats().build();
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine))) {
for (int i = 0; i < count; i++) {
jedis.get("k" + i);
assertThat(caffeine.estimatedSize(), Matchers.lessThan(maxEstimatedSize));
}
}
assertThat(caffeine.stats().evictionCount(), Matchers.greaterThan(count - maxEstimatedSize));
}

@Test
public void timeToLive() throws InterruptedException {
int count = 1000;
for (int i = 0; i < count; i++) {
control.set("k" + i, "v" + i);
}

Cache caffeine = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).recordStats().build();
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine))) {
for (int i = 0; i < count; i++) {
jedis.get("k" + i);
}
}
assertThat(caffeine.estimatedSize(), Matchers.equalTo((long) count));
assertThat(caffeine.stats().evictionCount(), Matchers.equalTo(0L));

TimeUnit.SECONDS.sleep(2);
caffeine.cleanUp();
assertThat(caffeine.estimatedSize(), Matchers.equalTo(0L));
assertThat(caffeine.stats().evictionCount(), Matchers.equalTo((long) count));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package redis.clients.jedis.csc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.function.Supplier;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.Connection;
import redis.clients.jedis.ConnectionPoolConfig;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.HostAndPorts;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPooled;

public class ClientSideCacheFunctionalityTest {

protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1);

protected Jedis control;

@Before
public void setUp() throws Exception {
control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build());
control.flushAll();
}

@After
public void tearDown() throws Exception {
control.close();
}

private static final Supplier<JedisClientConfig> clientConfig
= () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build();

private static final Supplier<GenericObjectPoolConfig<Connection>> singleConnectionPoolConfig
= () -> {
ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();
poolConfig.setMaxTotal(1);
return poolConfig;
};

@Test
public void flushEntireCache() {
int count = 1000;
for (int i = 0; i < count; i++) {
control.set("k" + i, "v" + i);
}

HashMap<Long, Object> map = new HashMap<>();
ClientSideCache clientSideCache = new MapClientSideCache(map);
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) {
for (int i = 0; i < count; i++) {
jedis.get("k" + i);
}
}

assertEquals(count, map.size());
clientSideCache.clear();
assertEquals(0, map.size());
}

@Test
public void removeSpecificKey() {
int count = 1000;
for (int i = 0; i < count; i++) {
control.set("k" + i, "v" + i);
}

// By using LinkedHashMap, we can get the hashes (map keys) at the same order of the actual keys.
LinkedHashMap<Long, Object> map = new LinkedHashMap<>();
ClientSideCache clientSideCache = new MapClientSideCache(map);
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) {
for (int i = 0; i < count; i++) {
jedis.get("k" + i);
}
}

ArrayList<Long> commandHashes = new ArrayList<>(map.keySet());
assertEquals(count, map.size());
for (int i = 0; i < count; i++) {
String key = "k" + i;
Long hash = commandHashes.get(i);
assertTrue(map.containsKey(hash));
clientSideCache.removeKey(key);
assertFalse(map.containsKey(hash));
}
}

@Test
public void multiKeyOperation() {
control.set("k1", "v1");
control.set("k2", "v2");

HashMap<Long, Object> map = new HashMap<>();
try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map))) {
jedis.mget("k1", "k2");
assertEquals(1, map.size());
}
}

}
Loading

0 comments on commit 82c0226

Please sign in to comment.