
memcached는 우선 multi-set을 지원하지 않는다. 그래서 @ReadThroughMultiCache를 활용하여 getBulk를 하게 되는 경우, 여러 건이 캐시에 존재하지 않을 때 setBulk가 아니라 일일이 하나씩 set을 호출하게 된다.
그런데, 왜 순차적으로 호출되게 했을까? 병렬로 호출되면 안되는걸까?
simple-spring-memcached의 내부를 살펴보자.
우리는 @ReadThroughMutliCache를 사용할 때, generateKeysFromResults라는 옵션을 사용하고 있다. ReadThroughMultiCacheAdvice.cacheMulti(~) 메서드 내부 124번 라인을 보면 generateByKeysFromResult 라는 메서드를 호출한다.

해당 메서드 내부를 들어가보면, setSilently를 호출하는 부분이 있다.

즉, @ReadThroughMutliCache 애노테이션을 붙인 메서드의 실행 결과를 받아와서 각 결과에 대해 일일이 setSilently를 호출해주는 것을 알 수 있다.
setSilently 안에서는 set을 호출하고, 그 set은 cacheClient를 활용해 set을 한다.



그리고 우리가 사용하고 있는 MemcachedClientWrapper 는 ssm에서 제공하는 객체이며, 내부적으로 set을 하고 return f.get()을 한다.


여기서의 memcachedClient.set(key, exp, value)의 반환값은 Future<Boolean>으로 비동기로 동작하도록 설계된 것임을 알 수 있다. (Future를 통해)
해당 set의 구현체 내부로 들어가보면, 내부적으로 asyncStore라는 것을 호출한다.

asyncStore의 내부가 핵심인데 아래처럼 구현되어있다.

이 로직을 잠깐 살펴보자면, 내부적으로 OperationFuture라는 클래스를 사용하는데, 해당 클래스는 Future 클래스를 구현하며 Operation 클래스를 상속하고 있다.
(참고로, ssm은 spymemcached를 추상화하여 애노테이션 기반으로 memcached의 기능을 이용할 수 있도록 해주는 라이브러리이다. 이걸 알아야 다음 내용이 이해가 됨.)
spymemcached의 CacheLoader의 push 메서드를 살펴보면, 여기 내부에서도 set을 호출하고 있는데, while 루프를 돌면서 호출한다.

정리하자면 spymemcached는 단일 스레드에서 루프를 돌며 operation을 수행하고, selector를 통해서 operation 수행 결과를 받아오도록 동작하고 있다.
즉, spymemcached는 원래 비동기로 결과값을 받아오도록 구현되어있는데, 메인 스레드에서 CountDownLatch(1)을 설정하고, memcachedClient의 caller인 memcachedClientWrapper에서 f.get()을 호출하는데, 아래 이미지에서도 볼 수 있지만 이 get()이 내부에서 latch.await을 호출하기 때문에 블로킹이 일어나고, latch.complete이 호출되기 전까지 블로킹이 해제되지 않는 것이다.

즉, 단건의 set에 대해 한 건당 await - complete을 호출하면서 블로킹이 되는 것이다.
CountDownLatch에 대해 부가설명을 하자면 CountDownLatch는 몇 번 카운트다운 할 것인지를 인자로 생성할 수 있고, latch의 카운트 다운을 1씩 줄이다가 0이 되는 순간 latch를 종료한다. 그리고 await을 호출하면 count가 0이 될때까지 대기한다.
요약하자면,
// memcachedClientWrapper.set(..)
@Override
public boolean set(final String key, final int exp, final Object value) throws TimeoutException, CacheException {
Future<Boolean> f = null;
try {
f = memcachedClient.set(key, exp, value);
return f.get();
} catch (InterruptedException | ExecutionException e) {
cancel(f);
throw new CacheException(e);
}
}
memcachedClient에게 set을 맡기고 나서, 이 오퍼레이션에 대한 동작은 CacheLoader에게 던져져 비동기로 수행되도록 spymemcached 자체에서는 구현이 되어있지만, 우리가 사용하는 ssm에서 이용하는 MemcachedClientWrapper의 set에서 내부적으로 f.get()을 호출하고, 이 내부에서 latch.await()을 호출하기 때문에 블로킹이 되어 병렬로 호출되는 것이다. 실제로 f.get()의 반환값이 쓰이는 곳은 아무데도 없다.
이 부분(f.get())을 주석하고 단순히 true를 반환하게 한 결과, 제일 처음 flame graph가 아래처럼 변했음을 알 수 있다.