Skip to content

Commit

Permalink
Use CommandObject(s) as cache-key (#3875)
Browse files Browse the repository at this point in the history
and remove hashing of CommandObject(s).
  • Loading branch information
sazzad16 authored Jul 4, 2024
1 parent 819447c commit a89b2a9
Show file tree
Hide file tree
Showing 22 changed files with 123 additions and 306 deletions.
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

0 comments on commit a89b2a9

Please sign in to comment.