Кэш кофеина - много ключей к одному значению

У меня есть кеш кофеина с сопоставлением ключей-> значений. Существует несколько реализаций интерфейса Key с различными методами equals. Чтобы удалить значение из кеша на основе someOtherVal, мне пришлось использовать код вроде cache.asMap().keySet().removeIf(comp::isSame), который работает очень медленно.

Есть ли какое-либо другое решение для такого рода ключей для сопоставления одного значения в кеше? Одна вещь, которая приходит мне в голову, - это иметь 2 экземпляра кэша, один с Cache<Key, String>, а другой с Cache<someOtherVal, Key>, и всякий раз, когда я хочу удалить значение, я нахожу Key, используя этот другой кеш.

Тогда вопрос только в том, как синхронизировать эти 2 кеша? Есть ли уже решения для этого?

import java.time.Duration;
import java.util.Objects;
import java.util.UUID;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Stopwatch;

public class Removal {
    private static final int MAX = 1_000_000;

    interface Key{
        String getSomeOtherVal();
        default boolean isSame(Key k){
            return Objects.equals(k.getSomeOtherVal(),getSomeOtherVal());
        }
    }

    static class KeyImpl implements Key{
        int id;
        String someOtherVal;

        public KeyImpl(int id, String someOtherVal) {
            this.id = id;
            this.someOtherVal = someOtherVal;
        }

        public int getId() {
            return id;
        }

        @Override
        public String getSomeOtherVal() {
            return someOtherVal;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            KeyImpl key = (KeyImpl)o;
            return id == key.id;
        }

        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
    }

    Cache<Key, String> cache = Caffeine.newBuilder().build();

    public static void main(String[] args) {
        Removal s = new Removal();
        s.fill();
        Duration sRem = s.slowRemovalFirst100();
        Duration fRem = s.fastRemoval100To200();
        System.out.println("Slow removal in " + sRem);
        System.out.println("Fast removal in " + fRem);
    }

    private Duration slowRemovalFirst100(){
        Stopwatch sw = Stopwatch.createStarted();
        for(int i=0; i<100; i++){
            Key comp = new KeyImpl(i, String.valueOf(i));
            cache.asMap().keySet().removeIf(comp::isSame);  //Finds a key by some other property and then removes it (SLOW)
            //System.out.println("Removed " + i);
        }
        return sw.stop().elapsed();
    }

    private Duration fastRemoval100To200(){
        Stopwatch sw = Stopwatch.createStarted();
        for(int i=100; i<200; i++){
            Key comp = new KeyImpl(i, String.valueOf(i));
            cache.invalidate(comp); //Uses direct access to map by key (FAST)
            //System.out.println("Removed " + i);
        }
        return sw.stop().elapsed();
    }

    private void fill(){
        for(int i=0; i<MAX; i++){
            cache.put(new KeyImpl(i, String.valueOf(i)), UUID.randomUUID().toString());
        }
    }
}

Результат выполнения этого кода на моей машине:

Slow removal in PT2.807105177S
Fast removal in PT0.000126183S

где вы можете увидеть такую ​​большую разницу ...

1
0
4 540
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, мне удалось это решить:

public class IndexedCache<K,V> implements Cache<K,V> {

    @Delegate
    private Cache<K, V> cache;
    private Map<Class<?>, Map<Object, Set<K>>> indexes;

    private IndexedCache(Builder<K, V> bldr){
        this.indexes = bldr.indexes;
        cache = bldr.caf.build();
    }

    public <R> void invalidateAllWithIndex(Class<R> clazz, R value) {
        cache.invalidateAll(indexes.get(clazz).getOrDefault(value, new HashSet<>()));
    }

    public static class Builder<K, V>{
        Map<Class<?>, Function<K, ?>> functions = new HashMap<>();
        Map<Class<?>, Map<Object, Set<K>>> indexes = new ConcurrentHashMap<>();
        Caffeine<K,V> caf;

        public <R> Builder<K,V> withIndex(Class<R> clazz, Function<K, R> function){
            functions.put(clazz, function);
            indexes.put(clazz, new ConcurrentHashMap<>());
            return this;
        }

        public IndexedCache<K, V> buildFromCaffeine(Caffeine<Object, Object> caffeine) {
            caf = caffeine.writer(new CacheWriter<K, V>() {

                @Override
                public void write( K k, V v) {
                    for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
                        indexesEntry.getValue().computeIfAbsent(functions.get(indexesEntry.getKey()).apply(k), (ky)-> new HashSet<>())
                        .add(k);
                    }
                }

                @Override
                public void delete( K k,  V v,  RemovalCause removalCause) {
                    for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
                        indexesEntry.getValue().remove(functions.get(indexesEntry.getKey()).apply(k));
                    }
                }
            });
            return new IndexedCache<>(this);
        }
    }

}

и это вариант использования:

@AllArgsConstructor
    @Data
    @EqualsAndHashCode(onlyExplicitlyIncluded = true)
    static class CompositeKey{
        @EqualsAndHashCode.Include
        Integer k1;
        String k2;
        Long k3;
    }


    public static void main(String[] args) {


        Caffeine<Object, Object> cfein = Caffeine.newBuilder().softValues().maximumSize(200_000);

        IndexedCache<CompositeKey, String> cache = new IndexedCache.Builder<CompositeKey, String>()
                .withIndex(Long.class, ck -> ck.getK3())
                .withIndex(String.class, ck -> ck.getK2())
                .buildFromCaffeine(cfein);


        for(int i=0; i<100; i++){
            cache.put(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)), "sdfsdf");
        }


        for(int i=0; i<10; i++){
            //use equals method of CompositeKey to do equals comp.
            cache.invalidate(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)));
        }

        for(int i=10; i<20; i++){
            //use Long index
            cache.invalidateAllWithIndex(Long.class, Long.valueOf(i));
        }

        for(int i=20; i<30; i++){
            //use String index
            cache.invalidateAllWithIndex(String.class, String.valueOf(i));
        }


        int y = 4;

    }

вот ссылка на мое обсуждение: https://github.com/ben-manes/caffeine/issues/279

Другие вопросы по теме