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

Use CommandObject(s) as cache-key #3875

Merged
merged 13 commits into from
Jul 4, 2024
6 changes: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,6 @@
<version>2.9.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.openhft</groupId>
<artifactId>zero-allocation-hashing</artifactId>
<version>0.16</version>
<scope>test</scope>
</dependency>

<!-- UNIX socket connection support -->
<dependency>
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/redis/clients/jedis/CommandObject.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package redis.clients.jedis;

import java.util.Iterator;
import redis.clients.jedis.args.Rawable;

public class CommandObject<T> {

private final CommandArguments arguments;
Expand All @@ -17,4 +20,39 @@ public CommandArguments getArguments() {
public Builder<T> getBuilder() {
return builder;
}

@Override
public int hashCode() {
int hashCode = 1;
for (Rawable e : arguments) {
hashCode = 31 * hashCode + e.hashCode();
}
hashCode = 31 * hashCode + builder.hashCode();
return hashCode;
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof CommandObject)) {
return false;
}

Iterator<Rawable> e1 = arguments.iterator();
Iterator<Rawable> e2 = ((CommandObject) o).arguments.iterator();
while (e1.hasNext() && e2.hasNext()) {
Rawable o1 = e1.next();
Rawable o2 = e2.next();
if (!(o1 == null ? o2 == null : o1.equals(o2))) {
return false;
}
}
if (e1.hasNext() || e2.hasNext()) {
return false;
}

return builder == ((CommandObject) o).builder;
}
}
6 changes: 6 additions & 0 deletions src/main/java/redis/clients/jedis/args/Rawable.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ public interface Rawable {
* @return binary
*/
byte[] getRaw();

@Override
int hashCode();

@Override
boolean equals(Object o);
}
11 changes: 3 additions & 8 deletions src/main/java/redis/clients/jedis/args/RawableFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,12 @@ public int hashCode() {
/**
* A {@link Rawable} wrapping a {@link String}.
*/
public static class RawString implements Rawable {
public static class RawString extends Raw {

private final byte[] raw;
// TODO: private final String str; ^ implements Rawable

public RawString(String str) {
this.raw = SafeEncoder.encode(str);
}

@Override
public byte[] getRaw() {
return raw;
super(SafeEncoder.encode(str));
}
}

Expand Down
37 changes: 11 additions & 26 deletions src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,34 @@
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;

import redis.clients.jedis.csc.hash.CommandLongHasher;
import redis.clients.jedis.csc.hash.SimpleCommandHasher;
import redis.clients.jedis.CommandObject;

public class CaffeineClientSideCache extends ClientSideCache {

private final Cache<Long, Object> cache;

public CaffeineClientSideCache(Cache<Long, Object> caffeineCache) {
this(caffeineCache, SimpleCommandHasher.INSTANCE);
}
private final Cache<CommandObject, Object> cache;

public CaffeineClientSideCache(Cache<Long, Object> caffeineCache, CommandLongHasher commandHasher) {
super(commandHasher);
public CaffeineClientSideCache(Cache<CommandObject, Object> caffeineCache) {
this.cache = caffeineCache;
}

@Override
protected final void invalidateAllHashes() {
protected final void invalidateFullCache() {
cache.invalidateAll();
}

@Override
protected void invalidateHashes(Iterable<Long> hashes) {
cache.invalidateAll(hashes);
protected void invalidateCache(Iterable<CommandObject<?>> commands) {
cache.invalidateAll(commands);
}

@Override
protected void putValue(long hash, Object value) {
cache.put(hash, value);
protected <T> void putValue(CommandObject<T> command, T value) {
cache.put(command, value);
}

@Override
protected Object getValue(long hash) {
return cache.getIfPresent(hash);
protected <T> T getValue(CommandObject<T> command) {
return (T) cache.getIfPresent(command);
}

public static Builder builder() {
Expand All @@ -50,9 +43,6 @@ public static class Builder {
private long expireTime = DEFAULT_EXPIRE_SECONDS;
private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;

// not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)'
private CommandLongHasher commandHasher = SimpleCommandHasher.INSTANCE;

private Builder() { }

public Builder maximumSize(int size) {
Expand All @@ -65,19 +55,14 @@ public Builder ttl(int seconds) {
return this;
}

public Builder commandHasher(CommandLongHasher commandHasher) {
this.commandHasher = commandHasher;
return this;
}

public CaffeineClientSideCache build() {
Caffeine cb = Caffeine.newBuilder();

cb.maximumSize(maximumSize);

cb.expireAfterWrite(expireTime, expireTimeUnit);

return new CaffeineClientSideCache(cb.build(), commandHasher);
return new CaffeineClientSideCache(cb.build());
}
}
}
36 changes: 15 additions & 21 deletions src/main/java/redis/clients/jedis/csc/ClientSideCache.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package redis.clients.jedis.csc;

import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -11,7 +10,6 @@

import redis.clients.jedis.CommandObject;
import redis.clients.jedis.annots.Experimental;
import redis.clients.jedis.csc.hash.CommandLongHasher;
import redis.clients.jedis.util.SafeEncoder;

/**
Expand All @@ -25,25 +23,23 @@ public abstract class ClientSideCache {
protected static final int DEFAULT_MAXIMUM_SIZE = 10_000;
protected static final int DEFAULT_EXPIRE_SECONDS = 100;

private final Map<ByteBuffer, Set<Long>> keyToCommandHashes = new ConcurrentHashMap<>();
private final CommandLongHasher commandHasher;
private final Map<ByteBuffer, Set<CommandObject<?>>> keyToCommandHashes = new ConcurrentHashMap<>();
private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile

protected ClientSideCache(CommandLongHasher commandHasher) {
this.commandHasher = commandHasher;
protected ClientSideCache() {
}

public void setCacheable(ClientSideCacheable cacheable) {
this.cacheable = Objects.requireNonNull(cacheable, "'cacheable' must not be null");
}

protected abstract void invalidateAllHashes();
protected abstract void invalidateFullCache();

protected abstract void invalidateHashes(Iterable<Long> hashes);
protected abstract void invalidateCache(Iterable<CommandObject<?>> commands);

protected abstract void putValue(long hash, Object value);
protected abstract <T> void putValue(CommandObject<T> command, T value);

protected abstract Object getValue(long hash);
protected abstract <T> T getValue(CommandObject<T> command);

public final void clear() {
invalidateAllKeysAndCommandHashes();
Expand All @@ -63,7 +59,7 @@ public final void invalidate(List list) {
}

private void invalidateAllKeysAndCommandHashes() {
invalidateAllHashes();
invalidateFullCache();
keyToCommandHashes.clear();
}

Expand All @@ -76,9 +72,9 @@ private void invalidateKeyAndRespectiveCommandHashes(Object key) {
// final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key);
final ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);

Set<Long> hashes = keyToCommandHashes.get(mapKey);
if (hashes != null) {
invalidateHashes(hashes);
Set<CommandObject<?>> commands = keyToCommandHashes.get(mapKey);
if (commands != null) {
invalidateCache(commands);
keyToCommandHashes.remove(mapKey);
}
}
Expand All @@ -89,23 +85,21 @@ public final <T> T get(Function<CommandObject<T>, T> loader, CommandObject<T> co
return loader.apply(command);
}

final long hash = commandHasher.hash(command);

T value = (T) getValue(hash);
T value = getValue(command);
if (value != null) {
return value;
}

value = loader.apply(command);
if (value != null) {
putValue(hash, value);
putValue(command, value);
for (Object key : keys) {
ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);
if (keyToCommandHashes.containsKey(mapKey)) {
keyToCommandHashes.get(mapKey).add(hash);
keyToCommandHashes.get(mapKey).add(command);
} else {
Set<Long> set = new HashSet<>();
set.add(hash);
Set<CommandObject<?>> set = ConcurrentHashMap.newKeySet();
set.add(command);
keyToCommandHashes.put(mapKey, set);
}
}
Expand Down
53 changes: 12 additions & 41 deletions src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,36 @@

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.hash.HashFunction;
import java.util.concurrent.TimeUnit;

import redis.clients.jedis.csc.hash.CommandLongHasher;
import redis.clients.jedis.csc.hash.GuavaCommandHasher;
import redis.clients.jedis.CommandObject;

public class GuavaClientSideCache extends ClientSideCache {

private final Cache<Long, Object> cache;

public GuavaClientSideCache(Cache<Long, Object> guavaCache) {
this(guavaCache, GuavaCommandHasher.DEFAULT_HASH_FUNCTION);
}

public GuavaClientSideCache(Cache<Long, Object> guavaCache, HashFunction hashFunction) {
this(guavaCache, new GuavaCommandHasher(hashFunction));
}
private final Cache<CommandObject, Object> cache;

public GuavaClientSideCache(Cache<Long, Object> guavaCache, CommandLongHasher commandHasher) {
super(commandHasher);
public GuavaClientSideCache(Cache<CommandObject, Object> guavaCache) {
super();
this.cache = guavaCache;
}

@Override
protected final void invalidateAllHashes() {
protected final void invalidateFullCache() {
cache.invalidateAll();
}

@Override
protected void invalidateHashes(Iterable<Long> hashes) {
cache.invalidateAll(hashes);
protected void invalidateCache(Iterable<CommandObject<?>> commands) {
cache.invalidateAll(commands);
}

@Override
protected void putValue(long hash, Object value) {
cache.put(hash, value);
protected <T> void putValue(CommandObject<T> command, T value) {
cache.put(command, value);
}

@Override
protected Object getValue(long hash) {
return cache.getIfPresent(hash);
protected <T> T getValue(CommandObject<T> command) {
return (T) cache.getIfPresent(command);
}

public static Builder builder() {
Expand All @@ -55,10 +44,6 @@ public static class Builder {
private long expireTime = DEFAULT_EXPIRE_SECONDS;
private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;

// not using a default value to avoid an object creation like 'new GuavaHashing(hashFunction)'
private HashFunction hashFunction = null;
private CommandLongHasher commandHasher = null;

private Builder() { }

public Builder maximumSize(int size) {
Expand All @@ -71,28 +56,14 @@ public Builder ttl(int seconds) {
return this;
}

public Builder hashFunction(HashFunction function) {
this.hashFunction = function;
this.commandHasher = null;
return this;
}

public Builder commandHasher(CommandLongHasher commandHasher) {
this.commandHasher = commandHasher;
this.hashFunction = null;
return this;
}

public GuavaClientSideCache build() {
CacheBuilder cb = CacheBuilder.newBuilder();

cb.maximumSize(maximumSize);

cb.expireAfterWrite(expireTime, expireTimeUnit);

return hashFunction != null ? new GuavaClientSideCache(cb.build(), new GuavaCommandHasher(hashFunction))
: commandHasher != null ? new GuavaClientSideCache(cb.build(), commandHasher)
: new GuavaClientSideCache(cb.build());
return new GuavaClientSideCache(cb.build());
}
}
}
Loading
Loading