캐시(cache, 문화어: 캐쉬, 고속완충기, 고속완충기억기)는 컴퓨터 과학에서 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다.

graph LR C1[Client] --> App[Spring Boot Application] C2[Client] --> App[Spring Boot Application] C3[Client] --> App[Spring Boot Application] C4[Client] --> App[Spring Boot Application] App[Spring Boot Application] -->|Connection| D[DB] App[Spring Boot Application] -.->|No Connection| D[DB] App[Spring Boot Application] -.->|No Connection| D[DB] App[Spring Boot Application] -.->|No Connection| D[DB]

Spring의 장점 중 PSA(Portable Service Abstractions)라는 것이 있다. 이는 쉬운 서비스 추상화라고 하는데, 각각의 외부 서비스를 간단한 인터페이스만으로 쉽게 사용할 수 있도록 설계되어있다. Cache에서는 Redis, Ehcache, ConcurrentMap 등을 CacheManager 인터페이스로 추상화되어 있고, CacheManager 인터페이스를 이용하여 또 다른 cache 라이브러리를 사용할 수도 있다.

언제 사용하면 좋을까?

아래 그림과 같은 메뉴가 단적인 예시가 될 수 있다.

  • 무거운 비즈니즈 로직을 수행할 떄
  • 주기적으로나 언제나 동일한 데이터를 가질 떄
  • RDB에 대한 부하를 분산하고자 할 때

Dependency

1
2
3
4
dependencies { 
compile('org.springframework.boot:spring-boot-starter-cache')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

Config

@EnableCaching을 명시해주면 바로 사용할 수 있고 추가적인 설정이 없다면 ConcurrentMap를 사용하여 Caching하게 된다. 또한 RedisEhcache 라이브러리를 추가하면 Spring Boot의 Auto Detect 기능으로 인해 해당 라이브러리를 자동적으로 이용하게 된다.

1
2
3
4
5
@Configuration
@EnableCaching
public CacheConfig {

}

Component

무거운 비즈니스 로직이 있다고 가정하고 약 3초의 sleep을 주도록 해보자.
아래 코드를 간단히 설명하자면, book이라는 캐시 영역에 isbn을 키로 갖는 데이터를 Caching 해두는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class SimpleBookRepository implements BookRepository {

private static final Logger logger = LoggerFactory.getLogger(SimpleBookRepository.class);

@Override
@Cacheable(value="book", key="#isbn")
public Book getByIsbn(String isbn) {
simulateSlowService();
return new Book(isbn, "Some book");
}

private void simulateSlowService() {
try {
long time = 3000L;
Thread.sleep(time);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}

@Override
@CacheEvict(value="book", key="#isbn")
public void refresh(String isbn) {
logger.info("cache clear => " + isbn);
}
}

@Cacheable: 캐시 생성

  • value: 캐시 이름
  • key: 키

@CacheEvict: 캐기 초기화

  • value: 캐시 이름
  • key: 키

Test Code

로직에 대한 소요시간을 측정해 보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootCacheApplicationTests {

@Autowired
private BookRepository repository;

private long startTime;
private long endTime;

private static final Logger logger = LoggerFactory.getLogger(SpringBootCacheApplicationTests.class);

@Before
public void onBefore() {
startTime = System.currentTimeMillis();
}

@After
public void onAfter() {
endTime = System.currentTimeMillis();
logger.info("소요시간: {}ms", endTime - startTime);
}

@Test
public void test1() {
repository.getByIsbn("a");
}

@Test
public void test2() {
repository.getByIsbn("a");
}


@Test
public void test3() {
repository.getByIsbn("b");
}

@Test
public void test4() {
repository.getByIsbn("a");
}

@Test
public void test5() {
repository.refresh("a");
repository.getByIsbn("a");
}
}
결과
1
2
3
4
5
6
소요시간: 3215ms
소요시간: 10ms
소요시간: 3006ms
소요시간: 8ms
cache clear => a
소요시간: 3017ms

참고

SpringBootSample / SpringBootCache