Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test different functionalities of client side cache #3828

Merged
merged 7 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading