Cache 에 특정 키의 만료 시각(long, epoch millis)을 반환하는 expireAt(K key) 를 추가하세요. 키가 없거나 이미 만료됐으면 -1 을 반환합니다. 힌트: store.get(key) 로 Entry 를 꺼내 expireAtMillis 를 확인합니다.
public long expireAt(K key) {
Entry<V> e = store.get(key);
if (e == null || e.expireAtMillis <= clock.millis()) {
return -1;
}
return e.expireAtMillis;
}Entry 의 필드가 private 이므로 Cache 클래스 내부에 메서드를 추가해야 접근할 수 있습니다.
Cache<K, V> 를 감싸서, 없는 값도 Optional.empty() 로 짧게 캐시하는 getOptional(key, loader) 를 추가하세요. 힌트: V 대신 Optional<V> 를 캐시 값 타입으로 쓰는 별도 Cache<K, Optional<V>> 인스턴스를 만듭니다.
Cache<String, Optional<String>> negativeCache =
new Cache<>(Clock.systemUTC(), Duration.ofMinutes(1), 100);
Optional<String> result = negativeCache.get("missing-id",
id -> Optional.ofNullable(lookupFromDb(id)));일반 캐시와 음수 캐시는 TTL 이 다른 경우가 많아 인스턴스를 분리하는 편이 관리하기 쉽습니다.
Main.demoStampede 의 threadCount 를 20으로 바꾸고 다시 실행해, loader 실행 횟수가 여전히 1인지 확인하세요. 힌트: CountDownLatch start 로 모든 스레드를 동시에 출발시키는 구조는 그대로 두고 개수만 바꾸면 됩니다.
int threadCount = 20; // 10 -> 20 으로 변경스레드 수를 늘려도 store.compute 가 키 단위로 원자적이므로 결과는 항상 1입니다.
CodeService.findByGroup 에 groupCode 가 "TEMP" 로 시작하면 캐시하지 않도록 condition 속성을 추가하세요. 힌트: condition 은 메서드 실행 전에 평가되는 SpEL 식입니다.
@Cacheable(cacheNames = "codes", key = "#groupCode",
condition = "!#groupCode.startsWith('TEMP')")
public List<CodeDto> findByGroup(String groupCode) {
return codeMapper.selectByGroup(groupCode);
}condition 은 실행 전 평가, unless 는 실행 후 결과를 보고 평가한다는 차이를 기억해 두면 두 속성을 헷갈리지 않습니다.
운영 중인 캐시의 hit ratio 가 계속 낮게(10% 미만) 나온다면 의심할 원인 3가지를 이 레슨 내용에서 골라 적어 보세요.
세 가지 모두 코드를 고치지 않고 로그나 캐시 키 목록만 확인해도 원인을 좁힐 수 있습니다.