캐시
캐시는 코스트가 높은 연산 결과나 자주 참조되는 데이터를 메모리 안에 두고 뒤이은 요청이 빨리 처리될 수 있도록 하는 저장소다.
애플리케이션의 성능은 DB 호출이 얼마나 많냐에 따라서 영향을 많이 받는다. 캐시는 DB 호출로 인한 애플리케이션 성능 저하에 대한 해결책이 될 수 있다.
캐시 계층
캐시 계층은 데이터가 잠시 보관되는 곳으로 DB보다 훨씬 빠르다. 언급했듯 메모리로 데이터를 관리한다. 그렇기에 훨씬 빠른 것이다. 별도의 캐시 계층을 두면 성능이 개선될 뿐 아니라 데이터베이스의 부하를 줄일 수 있고, 캐시 계층의 규모를 독립적으로 확장시키는 것도 가능하다.
이 캐시 계층을 끼고 데이터를 읽거나 쓸 때, 다양한 전략을 이용할 수 있는데, 이를 캐싱 전략(caching strategy)이라 한다. 개발자는 캐시할 데이터 종류, 크기, 액세스 패턴에 맞는 캐시 전략을 선택하면 된다.
캐싱 전략: 읽기
Look Aside
- 데이터를 찾을 때 캐시에 저장된 데이터가 있는지 먼저 확인하는 전략이다.
- 만약 캐시에 없다면, DB로 넘어가서 데이터를 조회한다.
- 그 후 DB에서 가져온 데이터를 서버가 캐시에 업데이트 시킨다.
- 가장 일반적인 방식이다.
- 특정 데이터가 반복적으로 많이 호출되어야한다면 이 전략은 상당히 유용할 것이다.
- 만약 캐시가 다운되더라도 일단은 DB를 통해 조회할 수 있으니, 장애에 대한 리스크가 좀 덜한 편이다.
- 리스크가 좀 덜하지 안전한건 아니다.
- 캐시에 요청이 어마어마하게 몰려있었는데 캐시가 죽으면 이 요청들은 어디로 가야할까?
- DB로 향해야 할테니 DB로 부하가 한번에 많이 쏠릴 수도 있다.
- DB도 죽을 가능성이 있다는 것이다.
- 반복적으로 동일 쿼리를 날리는 경우에는 적합하다.
- 구조상 Cache Warming이 권장된다.
- Cache Warming은 초기 서비스 오픈시 급증되는 트래픽을 감당하기 위해서 쓰는 기법 중 하나이다.
- 오픈 전에 미리 DB에 있는 데이터들을 Cache로 옮겨 놓는 것을 의미한다.
Read Through
- Look Aside와 상당히 유사한 전략이다.
- 캐시 조회 -> (없다면 캐시가 DB에서 데이터를 조회 -> 캐시가 직접 데이터를 업데이트 시킨다.) 데이터 가져옴.
- 위의 플로우다. 그냥 이렇게만 보면 Look Aside와의 차이를 느끼기 어려울 수 있다.
- Look Aside에서 db의 데이터를 업데이트 하는 건 서버다.
- 캐시에 데이터가 없다면 웹 서버가 DB를 조회하고 이를 캐시에 업데이트한다.
- 반면 Read Through에서 DB의 데이터를 업데이트 하는 건 Data Store 그 자체다.
- 캐시에 데이터가 없다면 캐시 저장소가 DB를 조회한 후 이를 직접 업데이트한다.
- 웹 서버는 데이터 조회를 위해서는 무조건 캐시를 볼 수 밖에 없는 구조다.
- DB에 대한 직접적인 접근을 최소화하고 Read 자원을 최소화할 수 있다고 한다.
- 이에는 위험도 따르는데, 웹 서버는 캐시를 통해서만 데이터에 액세스할 수 있기 때문에 캐시가 터지면 데이터 조회를 할 수가 없다.
- Look Aside보다 더욱 더 읽기 자원을 최소화 해야할 때 유용하다.
- 구조상 역시 Cache Warming이 권장된다.
캐싱 전략: 쓰기
Write Back
모든 데이터를 캐시에 저장 -> 스케줄링 과정을 통해 DB에 저장
하는 방식이다.
- 스케줄링 덕분에 캐시에 있는 걸 한 번에 저장할 수 있다.
- DB에 관해서 불필요한 커넥션을 줄일 수 있다. 이는 부하를 줄일 수 있음을 이야기하기도 한다.
- 캐시가 일종의 queue 역할도 하게 된다.
- 쓰기가 빈번하면서 읽는데 많은 리소스가 든다면 이 전략을 사용하기에 적합한 상황이다.
- 데이터를 캐시에 먼저 저장하므로 DB에 문제가 생겼을 때라도 일단은 서비스는 돌아갈 수 있다.
- 근데 자주 쓰지 않을 수도 있는 데이터도 캐시에 저장할 수 있어서 리소스 낭비일 수 있다.
- 스케줄러가 돌기 전에 캐시가 터지면 데이터가 소실된다.
- 캐시에 Replication이나 Cluster 구조를 가지게 함으로써 안정성을 높일 수 있긴 하다.
Write Through
모든 데이터를 캐시에 저장 -> 바로 DB에 저장
하는 방식이다.
- 캐시 데이터가 항상 최신이다.
- 또한 데이터도 DB와 캐시 둘 모두 일관적이다.
- 따라서 캐싱을 하는데 데이터 유실이 되면 절대 안 되는 상황에서 유리하다.
- 마찬가지로 자주 안 쓸 데이터도 저장하게 된다.
- 매 요청마다 쓰기가 두번 들어간다. 생성/수정이 상당히 빈번한 경우 성능 이슈가 생길 수도 있다.
Write Around
모든 데이터는 DB에 일단 저장 -> Cache miss가 발생하는 경우에만 DB, 캐시에 데이터 저장
하는 방식이다.
- DB와 캐시 사이에 정합성 문제가 있을 수 있다.
캐시 사용 시 유의할 점
-
데이터 갱신은 자주 일어나지 않지만 참조가 빈번히 일어난다면 고려해볼만 함.
-
영속적으로 보관할 데이터를 캐시에 두는 건 사실 바람직하지 않다.
- 캐시는 데이터를 휘발성 메모리에 두기 때문.
- 캐시 서버가 재시작되면 캐시 내 모든 데이터는 사라진다.
-
데이터가 언제 어떻게 만료될지에 대한 정책을 마련해두는 것이 좋다.
- 만료 정책이 없으면 데이터는 캐시에 계속 남을 것이다.
- 적절한 만료 정책과 기한을 설정하는 것이 중요하다.
-
캐시와 DB 사이의 일관성을 어떻게 유지할 것인지 고민을 하자.
- DB 데이터를 갱신하는 연산과 캐시를 갱신하는 연산이 단일 트랜잭션으로 처리되지 않는 경우 이 일관성이 깨질 수 있다.
-
단일 장애 지점이 되지 않도록 조심하자.
- 캐시 서버를 한 대만 두는 경우 이 캐시 서버는 단일 장애 지점이 되어버릴 수 있다.
- 단일 장애 지점: 장애가 전체 시스템의 동작을 중단시켜버릴 수 있는 특정 지점.
- 이걸 방지하기 위해선 여러지역에 걸쳐 캐시 서버를 분산 시켜놔야한다.
-
캐시 메모리는 얼마나 크게 잡아야할지 생각해보자.
- 메모리가 너무 작으면 데이터가 자주 캐시에서 밀려 캐싱 자체에 대한 의미가 퇴색될 수 있다.
- 캐시 메모리를 과할당하는 방법으로 캐시에 보관될 데이터가 갑자기 늘어도 버틸 수 있다.
-
데이터 방출 정책은 무엇이어야할까 생각해보자.
- 캐시가 꽉 찼을 때는 당연히 캐시에 데이터를 넣을 떄 기존 데이터를 방출시켜야한다.
- 가장 널리 쓰이는 건 마지막으로 사용된 시점이 가장 오래된 걸 내보내는 정책이다.
- 일명 LRU(Last Recently Used)
- 다른 정책으로는 사용빈도가 가장 적은 데이터를 내보내는 정책이 있다.
- 일명 LFU(Least Frequently Used)
- 또 FIFO로 적용할 수도 있다.
Ref.