다음과 같은 상황을 가정해보자. 상품 판매 서비스를 개발하고 있다.
상품 조회 API를 개발하고 있는데, 상품의 정보는 거의 바뀌지 않지만, 매 요청마다 DBMS에 쿼리를 날려 다시 조회하고 있다.
이 상황의 문제점은 다음과 같다.
변경 빈도가 매우 낮은 데이터를 얻기 위해 매번 DB에 쿼리를 보내 애플리케이션의 성능이 떨어지고 있다!
이것이 왜 문제일까?
1. DBMS는 기본적으로 Disk에 데이터를 저장한다. Disk는 용량은 크지만, 접근 시간이 느리다는 단점이 있다.
2. 상품 데이터가 100만개라고 가정하면, 매 조회마다 Disk에 접근해야 한다.
3. 매번 DBMS에 쿼리를 날리지만, 응답 데이터는 거의 항상 똑같다.
이 비효율성을 해결하기 위해 똑똑한 사람들이 한 가지 방법을 생각해냈다.
변경이 적지만, 자주 읽히는 데이터를 어딘가에 미리 가지고 있다가 요청이 들어오면 그 어딘가에서 먼저 데이터를 찾으면 어떨까?
이 방법을 사용한다면, 매번 DB를 뒤지는 대신, 어딘가를 뒤지면 된다. 물론, 그 어딘가는 Disk보다 접근 속도가 빨라야 할 것이다.
이 "어딘가"를 구현하기 위해 일반적으로 Redis 라는 In memory DBMS를 사용한다. Redis는 Disk가 아닌 Memory에 데이터를 저장한다. Key - Value 형태의 일종의 In memory NOSQL DBMS인 것이다.
자주 조회되는 데이터를 Redis에 저장해두었다가 해당 데이터에 대한 요청이 들어왔을 때, Redis에서 찾아 반환하면 데이터 조회 속도를 크게 개선시킬 수 있다.
물론, 이 방식이 완벽한 것은 아니다. 변경이 적은 데이터를 Redis에 저장한다고 해도, 변경이 아예 없을 수는 없다.
상품 설명에 문의 이메일이 변경되었다고 가정하자.
문의 이메일을 변경한다면, DB에 해당 변경 사항이 반영된다.
만약 Redis에 해당 상품 설명 데이터가 존재한다면, Redis와 DB의 정보의 불일치가 발생한다.
해당 데이터가 Redis에 남아 있는 이상, 사용자들은 변경 전 데이터만 볼 수 있게 되는 것이다.
이 문제를 해결하기 위해 TTL(캐시 데이터 만료 시간) 설정, 갱신 시점 캐시 업데이트, 캐시 무효화 등의 전략이 있다.
캐싱 전략에는 읽기 전략, 쓰기 전략이 있다. 이번 포스팅에서는 캐시의 읽기 전략만 다루도록 하겠다.
사전 지식
후에 나오는 내용을 위해선, 사전 지식이 필요하다. 캐시를 읽을 때에는 다음 두 가지 결과만이 존재한다.
캐시 히트(Cache Hit)
캐시 저장소(Redis)에 해당 데이터가 존재해서 캐시 저장소(Redis)에서 데이터를 얻음.
캐시 미스(Cache Miss)
캐시 저장소(Redis)에 해당 데이터가 존재하지 않아 DB에 가서 데이터를 얻음.
당연한 말이지만, Cache Miss가 생기면, DB 접근 시간 + Redis 접근 시간 만큼의 비용이 발생하고, 이는 캐시를 사용하지 않는 상황보다 더 비효율적이다. 캐시를 사용하지 않으면 DB 접근 시간 의 비용만 발생하기 때문이다. 물론, Redis 접근 시간은 매우 짧을 것이므로, 큰 차이는 없을 것이다.
Look Aside 전략
데이터를 가져올 때, 캐시를 우선적으로 찾고, 만약 캐시에 데이터가 없다면, DB에서 데이터를 가져온 후, 캐시에 데이터를 저장한다. 읽기 요청이 많을 때 적합하고, 읽기 전략으로 가장 많이 사용된다.
흐름은 다음과 같다.
1. 데이터를 우선, Redis에서 읽어 온다. 만약 데이터가 Redis에 존재한다면, 그대로 반환한다.
2. 데이터가 Redis에 존재하지 않는다면, 애플리케이션에서 "직접" DB를 조회해 데이터를 얻고, 이를 Redis에 삽입한 후, 해당 데이터를 반환한다.
장점
- 캐시와 DB가 분리되어 있으므로, 캐시에서 장애 발생 시, 서비스를 계속 이용할 수 있다.
- 캐싱 로직을 애플리케이션 레벨에서 코드로 제어할 수 있기 때문에, 유연성이 높다.
단점
- 캐싱 로직을 애플리케이션 레벨에서 코드로 제어해야 하기 때문에, 코드 복잡도가 올라간다.
Read Through 전략
처음에 Read Through 전략을 이해하는 데에 시간이 많이 들었다. Look Aside 전략과 비슷하지만, Cache Miss 발생 시, 데이터베이스에서 데이터를 읽어 오는 주체가 애플리케이션이 아니다 캐시 제공자(Redis) 이다. 일반적으로, Read Through 전략은 데이터 정합성을 보장하기 위해 적절한 캐시 쓰기 전략과 함께 사용된다.
흐름은 다음과 같다.
1. 데이터를 우선, Redis에서 읽어 온다. 만약 데이터가 Redis에 존재한다면, 그대로 반환한다.
2. 데이터가 Redis에 존재하지 않는다면, Redis에서 "직접" DB를 조회해 데이터를 얻고, 이를 자제척으로 적재한 후, 해당 데이터를 반환한다.
장점
- 캐싱 로직을 애플리케이션 레벨에서 제어할 필요가 없다. 따라서, 애플리케이션 레벨의 로직이 단순해진다.
단점
- 캐시를 통해서만 조회하게 되므로, 캐시 장애 시 서비스 장애로 이어질 수 있다.
- 캐싱을 전적으로 캐시 제공자(Redis)에게 위임하므로, 세부 설정이나 문제 대응이 어려울 수 있다.
Redis에서 이 기능을 제공하고 있다는 것을 발견했다.
Write-behind, write-through, and read-through caching
Write-behind, write-through, and read-through caching between Redis and other databases (SQL or NoSQL).
redis.io