naver / arcus-spring

ARCUS as a caching provider for the Spring Cache Abstraction
Apache License 2.0
26 stars 16 forks source link

FEATURE: support key generator for other cache implementations #31

Closed hjyun328 closed 3 years ago

hjyun328 commented 3 years ago

arcus-spring에서 제공하는 키 생성 클래스인 StringKeyGenerator, SimpleStringKeyGenerator를 다른 Cache 구현체에서 사용하면, 반복적인 Cache Miss가 발생하여, 이를 수정하였습니다.

만약 아래와 같이 응용 코드에서 ArcusCacheManager, EhCacheManager를 둘 다 사용하고, arcus-spring에서 제공되는 StringKeyGenerator를 사용할 경우

import com.navercorp.arcus.spring.cache.StringKeyGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@EnableCaching
@Configuration
public class CacheConfiguration implements CachingConfigurer {

  @Qualifier("ehCacheManager")
  @Autowired
  private CacheManager ehCacheManager;

  @Qualifier("arcusCacheManager")
  @Autowired
  private CacheManager arcusCacheManager;

  @Bean
  @Override
  public CacheManager cacheManager() {
    CompositeCacheManager cacheManager = new CompositeCacheManager();
    cacheManager.setCacheManagers(List.of(ehCacheManager, arcusCacheManager));
    return cacheManager;
  }

  @Override
  public KeyGenerator keyGenerator() {
    return new StringKeyGenerator();
  }

  ... (생략) ...

}

EhCache에서는 똑같은 Key를 가지고 요청하여도 항상 CacheMiss가 발생합니다. EhCache는 아래와 같이 캐시 저장소에 접근할 때 해시 테이블 접근을 위해 Object의 hashCode 메소드와 해시 충돌 처리를 위해 equals 메소드를 사용합니다.

package net.sf.ehcache.store.chm;

  ... (생략) ...

class SelectableConcurrentHashMap {

  ... (생략) ...

  public Element get(Object key) { // StringKeyGenerator, SimpleStringKeyGenerator를 사용할 경우 key는 ArcusStringKey 클래스
    int hash = hash(key.hashCode());
    return segmentFor(hash).get(key, hash);
  }

  Element get(final Object key, final int hash) {  // StringKeyGenerator, SimpleStringKeyGenerator를 사용할 경우 key는 ArcusStringKey 클래스
    readLock().lock();
    try {
      if (count != 0) { // read-volatile
        HashEntry e = getFirst(hash);
        while (e != null) {
          if (e.hash == hash && key.equals(e.key) && !e.value.equals(DUMMY_PINNED_ELEMENT)) {
            e.accessed = true;
            return e.value;
          }
          e = e.next;
        }
      }
      return null;
    } finally {
      readLock().unlock();
    }
  }

  ... (생략) ...

}

StringKeyGenerator, SimpleStringKeyGenerator는 Key 생성시 ArcusStringKey라는 클래스의 Object를 리턴하는데, 이 클래스의 hashCode, equals 메소드가 구현되어있지 않아, jvm에서 생성하는 hashCode를 사용하게 됩니다. 따라서 같은 Key를 요청했음에도 불구하고 생성되는 ArcusStringKey 인스턴스의 hashCode는 매번 다르기 때문에 CacheMiss가 항상 발생하게 됩니다.

package java.lang;

class Object {

  ... (생략) ...

  @HotSpotIntrinsicCandidate
  public native int hashCode();

  public boolean equals(Object obj) {
    return (this == obj);
  }

  ... (생략) ...

}

따라서 ArcusStringKey의 hashCode와 equals를 override하여, StringKeyGenerator, SimpleStringKeyGenerator 클래스가 다른 캐시 구현체에서도 사용될 수 있도록 수정하였습니다.

jhpark816 commented 3 years ago

@hjyun328 SimpleStringKeyGenerator로 생성한 ArcusStringKey의 hashCode는 항상 0이 되는 건가요 ?

hjyun328 commented 3 years ago

@jhpark816

리뷰 반영하였습니다.

테스트결과 EhCache에도 잘 동작됩니다.