Dev 달팽이 @_''

[Redis] 캐시(Cache) 설계 전략 본문

기타

[Redis] 캐시(Cache) 설계 전략

다본죽 2023. 7. 24. 17:10

Redis 캐시(Cache) 설계 전략

 

본 포스트에 앞서..

지난 포스트에서 Redis에 대한 정리를 하는 이유를 다시 복기하자면, 

 

`유저가 많아진 게시판 웹페이지의 첫페이지가 항상 느린데 이유가 무엇인가? 성능 개선 방법은 무엇인가?`

 

에 대한 대답을 찾기 위해서였다.

 

이 포스트에서 Redis 설계 전략을 알아보고 저 질문에 대한 내 생각을 적어보려고 한다.

 

Redis - 캐시(Cache) 전략

캐싱 전략은 웹 서비스 환경에서 시스템 성능 향상을 기대할 수 있는 중요한 기술이다.

캐싱 전략을 구현할 때 고려해야 할 점 중 하나는 캐시 데이터의 수명이다.

 

캐시는 메모리를 사용하기 때문에 RDM보다 훨씬 빠르다. 하지만 용량이 작아  모든 데이터를 지우지 않고 캐시 저장소에 저장하면 용량 부족 현상으로 인해 시스템이 다운될 수 있다.

 

그러므로 캐시 만료 정책을 적절하게 설정하고 사용할 것인지가 중요하다.

 

캐싱 전략 패턴 종류

캐시를 이용하게 되면 반드시 닥쳐오는 문제점이 바로 데이터 정합성이다.

데이터 정합성 문제란, 캐시 저장소(Redis)에 있는 데이터와 DB에 있는 데이터가 다른 경우이다.

 

캐시를 사용하기 전에는 DB에서만 데이터 조회/수정을 했기 때문에 데이터 정합성 문제가 나타나지 않지만, 캐시를 사용하면 또 다른 데이터 저장소가 생기기 때문에 주 저장소에 저장된 값이 서로 다를 수 있는 현상이 일어날수 밖에 없다.

 

이를 위해 적절한 캐시 일기 전략(Read Cache Strategy)과 캐시 쓰기 전략(Write Cache Strategy)을 통해, 캐시와 DB 간의 데이터 불일치 문제를 극복하면서도 빠른 성능을 잃지 않도록 해야한다.

 

캐시 읽기 전략(Read Cache Strategy)

Look Aside 패턴

  • Cache Aside 패턴이라고도 불림.
  • 데이터를 찾을 때 우선 캐시에 저장된 데이터가 있는지 우선적으로 확인하는 전략.
  • 캐시가 없다면 DB에서 조회.
  • 반복적인 읽기가 많을 때 적합.
  • 캐시와 DB가 분리되어 가용.
    • 원하는 데이터만 별도 구성하여 캐시에 저장.
    • 캐시 서버가 장애나도 DB에서 데이터를 가져올 수 있어 캐시 장애 대비 구성이 되어있음.
    • 단, 캐시에 붙어있는 Connection이 많았다면 캐시 서버 장애 시 다운된 순간 DB로 몰려 부하 발생.

1. Client에서 서버에 데이터 요청
2. Server는 Cache에 해당 데이터가 있는지 확인
3. 데이터가 존재하면 서버에 전달 (Cache Hit)
4. 데이터가 없으면 DB에서 조회 (Cache Miss)
6. Cache에 DB에서 조회해 온 데이터 업데이트
7. Client에게 데이터 응답
  • Look Aside 패턴은 애플리케이션에서 일반적으로 사용되는 기본적인 캐시 전략.
  • Cache Store와 DB 간의 정합성 유지가 발생 할 수 있으며, 초기 조회 시 무조건 DB를 호출해야 하므로 단건 호출 빈도가 높은 서비스에서는 적합하지 않음.
  • 대신, 반복적인 동일 쿼리를 수행하는 서비스에 적합한 아키텍쳐.
  • 이런 경우, DB에서 캐시로 데이터를 미리 넣어주는 작업을 하기도 하는데 이를 Cache Warming이라고 함.
[Cache Warming]
Cache Warming은 미리 Cache로 데이터를 넣어두는 작업을 의미한다.
이 작업을 수행하지 않으면 서비스 초기 트래픽 급증시 대량의 cache miss가 발생하여 DB의 부하가 급증할 수 있다(Thundering Herd).
다만, 캐시는 메모리를 사용하여 용량이 매우 작으므로 데이터를 무한정으로 가지고 있을 수 없어 일정시간이 지나면 expire가 되어 다시 Thundergin Herd가 발생 할 수 있다.
따라서, TTL을 잘 조정해야 할 필요가 있다.(뒤에서 설명)

Thundering Herd는 모든 지점에서 발생하는 것은 아니고, 서비스 첫 페이지와 같은 대부분 조회가 몰리는 지점에서 주로 발생한다.

 

Read Through 패턴

  • 캐시에서만 데이터를 읽어오는 전략(inline cache).
  • Look Aside와 비슷하지만 데이터 동기화를 라이브러리 또는 캐시 제공자에게 위임하는 방식이라는 차이가 있음.
  • 따라서 데이터 조회에 있어 전체적으로 속도가 느림.
  • 데이터 조회를 전적으로 캐시에만 의존하므로, 캐시 서버가 죽으면 서비스에 문제가 생길 수 있음.
  • 캐시와 DB 간 데이터 동기화가 지속적으로 이루어지므로 데이터 정합성 문제에서는 벗어날 수 있음.
  • 읽기가 많은 워크로드에 적합

1. Client에서 서버에 데이터 요청
2. Server는 Cache에 해당 데이터가 있는지 확인, 데이터가 있으면 서버에 데이터 전달(Cache Hit)
3,4. 데이터가 없으면 캐시에서 DB에 조회 후 자체 업데이트(Cache Miss)
5. 데이터를 서버에 전달
6. Client에게 데이터 응답
  • Look Aside 패턴과 차이점은 캐시에 저장하는 주체가 서버가 아닌 DB 자체임.
  • 직접적인 DB 접근은 최소화하고 Read에 대한 소모되는 자원을 최소화 할 수 있음.
  • 캐시 서버가 죽으면 서비스 전체가 영향이 있으므로 Replication 또는 Cluster로 구성하여 가용성을 높여야 함.
  • 이 방식 또한, Cache Warming을 해주는 것이 좋음.

캐시 쓰기 전략(Write Cache Strategy)

Write Back 패턴

  • Write behind 패턴이라고도 불림.
  • 캐시와 DB 동기화를 비동기하기 때문에 동기화 과정이 생략.
  • 데이터를 저장할 때 DB에 바로 쿼리하지 않고, 캐시에 모아서 일정 주기로 배치 작업을 통해 DB에 반영.
  • 캐시에 모아놨다가 DB에 쓰기 때문에 쓰기 쿼리 회수 비용과 부하를 줄일 수 있음.
  • Write가 빈번하면서 Read를 하는데 많은 양의 Resource가 소모되는 서비스에 적합
  • 데이터 정합성 확보
  • 자주 사용되지 않는 불필요한 리소스 저장.
  • 캐시에서 오류가 발생하면 데이터 영구 손실.

1. Client가 데이터 업데이트 요청을 한다.
2. 모든 데이터를 Cache에 저장한다.
3. 일정 시간 뒤 DB에 모두 업데이트한다.
  • Write Back 방식은 데이터를 저장할 때, DB가 아닌 먼저 캐시에 저장
  • 캐시에 모인 데이터를 특정 시점마다 DB로 쓰는 방식
  • Cache가 일종의 Queue 역할.
  • 캐시에 데이터를 모았다가 한 번에 DB에 저장하기 때문에 DB 쓰기 횟수 비용과 부하를 줄일 수 있음.
  • 데이터를 옮기기 전 Cache 서버가 죽으면 데이터가 유실되지만 반대로 DB가 죽어도 지속적인 서비스 제공 가능.
  • 캐시에 Replication이나 Cluster 구조를 적용함으로써 캐시 서비스의 가용성을 높이는 것이 좋으며, 캐시 읽기 전략인 Read-Through와 결합하면 가장 최근에 업데이트된 데이터를 항상 캐시에서 사용할 수 있는 혼합 워크로드에 적합.

Write Through 패턴

  • 데이터베이스와 Cache에 동시에 데이터를 저장하는 전략.
  • 데이터를 저장할 때 먼저 캐시에 저장한 다음 DB에 저장(모아놓았다 저장하는 것이 아닌 바로 저장).
  • Read Through와 마찬가지로 DB 동기화 작업을 Cache에 위임.
  • DB와 캐시가 항상 동기화 되어있어, 캐시의 데이터는 항상 최신의 상태로 유지.
  • 캐시와 백업 저장소에 업데이트를 같이 하여 데이터 일관성을 유지할 수 있어서 안정적.
  • 데이터 유실이 발생하면 안되는 상황에 적합.
  • 자주 사용되지 않은 불필요한 리소스 저장.
  • 매 요청마다 두번의 Write가 발생하게 됨으로써 빈번한 생성/수정이 발생하는 서비스에서는 성능 이슈 발생.
  • 기억장치 속도가 느릴 경우, 데이터를 기록할 때 CPU가 대기하는 시간이 필요하기 때문에 성능 감소.

  • Write Through 패턴은 Cache에도 반영하고 DB에도 동시에 반영하는 방식.
  • 그래서 항상 동기화되어 있는 최신의 버전을 가지고 있음.
  • 하지만, 결국 2단계 과정을 거쳐야하기 때문에 상대적으로 느리고, 무조건 일단 캐시에 저장하기 때문에 캐시에 넣은 데이터를 저장만하고 사용하지 않을 가능성이 있어 리소스 낭비 가능성이 있음.
Write Through와 Write Back 패턴 모두 둘 다 자주 사용되지 않은 데이터가 저장되어 리소스 낭비가 발생하는 문제점이 있다. 이를 해결하기 위해 TTL을 꼭 사용하여 사용되지 않는 데이터는 반드시 삭제해줘야 한다.

Write Around 패턴

  • Write Through보다 훨씬 빠름.
  • 모든 데이터는 DB에 저장(캐시를 갱신하지 않음).
  • Cache Miss가 발생하는 경우에만 DB와 캐시에도 데이터를 저장.
  • 따라서 캐시와 DB 내의 데이터가 다를 수 있음(데이터 불일치).

1. Client에서 데이터 업데이트를 요청한다.
2. 모든 데이터는 DB에 저장한다.
  • Write Around 패턴은 속도가 빠르지만, Cache Miss가 발생하기 전 DB에 저장된 데이터가 수정되었을 경우, 사용자가 조회하는 캐시와 DB 간의 데이터 불일치가 발생
  • 따라서, 저장된 데이터가 수정/삭제될 때 마다, 캐시 또한 삭제하거나 변경해야 하며, 캐시의 expire를 짧게 조정하는 식으로 대처해야 함.
  • Write Around 패턴은 주로 Look Aside, Read Through와 결해서 사용하며, 데이터가 한 번 쓰여지고, 덜 자주 읽히거나 읽지 않는 상황에서 좋은 성능을 제공함.

캐시 읽기  +  쓰기 전략 조합

Look Aside + Write Around 조합

  • 가장 일반적으로 자주 쓰이는 조합

Read Through + Write Around 조합

  • 항상 DB에 쓰고, 캐시에서 읽을 때 항상 DB에서 먼저 읽어오므로 데이터 정합성 이슈에 대한 완벽한 안전 장치를 구성할 수 있음

Read Through + Write Through 조합

  • 데이터를 쓸 때 항상 캐시에 먼저 쓰므로, 읽어올 때 최신 캐시 데이터 보장
  • 데이터를 쓸 때 항상 캐시에서 DB로 보내므로, 데이터 정합성 보장

TTL(Time To Live)

TTL은 Key가 자연스럽게 만료되어 없어지게 하는 시간(Redis의 경우 Integer 당 1ms)이다. 각 전략에 TTL을 추가하면 이득을 취할 수 있다.

  • Look Aside : 자연스럽게 값이 삭제되어 캐시 미스가 발생하고 최신의 값을 유지 가능.
  • Write Through : 업데이트가 되지 않은 데이터를 자동 삭제하여 메모리 이득을 볼 수 있음.

Cache Stampede 현상

대규모 트래픽 환경에서 TTL 값이 너무 작게 설정되면 Cache Stampede 현상이 발생할 가능성이 있다.

Look-aside 패턴에서 redis에 데이터가 없다는 응답을 받은 서버(Cache Miss)가 직접 DB로 데이터 요청한 뒤, 다시 redis에 저장하는 과정을 거친다.

그런데 위 상황에서 key가 만료되는 순간 많은 서버에서 이 key를 같이 보고 있었다면 모든 어플리케이션 서버에서 DB로 가서 찾게 되는 duplicate read 가 발생한다.

또 읽어온 값을 각 각 redis에 쓰는 duplicate write 도 발생되어, 처리량도 다 같이 느려질 뿐 아니라 불필요한 작업이 굉장히 늘어나 요청양 폭주로 장애로 이어질 가능성 도 있다.

캐시 공유 방식 지침

캐시는 어플리케이션의 여러 인스턴스에서 공유하도록 설계 된다.

그래서 각 어플리케이션 인스턴스가 캐시에서 데이터를 읽고 수정할 수 있다. 

따라서 한 어플리케이션이 캐시에 보유하고 있는 데이터를 수정하는 경우, 다른 어플리케이션의 인스턴스가 변경을 덮어쓰지 않도록 해야한다. 그렇지 않으면 데이터 정합성 문제가 발생한다.

데이터의 충돌을 방지하기 위해 다음과 같은 어플리케이션 개발 방식을 취해야 한다.

  • 캐시 데이터를 변경하기 직전에 데이터가 검색된 이후 변경되지 않았는지 일일히 확인하는 방법
    • 변경하지 않았다면 즉시 업데이트
    • 변경되었다면 업데이트 여부를 어플리케이션 레벨에서 결정
    • 업데이트가 드믈고 충돌이 발생하지 않는 상황에서 적용하기 용이
  • 캐시 데이터를 업데이트 하기 전 Lock을 잡는 방법
    • 조회성 업무를 처리하는 서비스에 Lock으로 인한 대기현상이 발생.
    • 데이터의 사이즈가 작아 빠르게 업데이트가 가능한 업무와 빈번한 업데이트가 발생하는 상황에 적용하기 용이

 

마치며..

이제 캐시에 대한 정리를 마치며 마지막으로 

 

`유저가 많아진 게시판 웹페이지의 첫페이지가 항상 느린데 이유가 무엇인가? 성능 개선 방법은 무엇인가?` 

 

에 대한 나의 대답을 정리한 Redis, 캐시 개념을 가지고 내려보면 다음과 같다.

 

먼저, 유저가 많아진 게시판 웹페이지의 '첫'페이지가 항상 느린 이유에 대한 대답은 Thundering Herd 때문이다. Thundering Herd는 대부분의 조회가 이루어지는 서비스 첫 페이지 같은 곳에서 발생한다. 이를 해결하기 위해 Cache Warming을 해주어야 한다. Cache Warming이란, 초기에 미리 DB에 있는 데이터를 캐시에 넣어주는 것이다. 

 

추가로 빈번하게 업데이트가 되는 게시물의 '좋아요' 등은 캐시 쓰기 전략의 Write Back 패턴을 사용하여 캐시에 데이터를 저장해놓았다가  일정 시간이 되면 한번에 DB에 저장하는 방식을 사용하면 성능 개선을 할 수 있다.

 

물론, 이 질문의 대한 나의 대답이 틀릴 수도 있다. 혹은 성능 개선에 대한 더 좋은 방법이 있을 수도 있다..

향후에 내가 실제로 실무에서 Redis를 사용하게 되어 같은 상황을 마주하고 다른 방식으로 해결한다면 그 때 어떻게 해결했는지 포스팅을 해봐야겠다.

 

참고

https://inpa.tistory.com/entry/

https://velog.io/@jinmin2216/

https://sabarada.tistory.com/

'기타' 카테고리의 다른 글

[Redis] Redis Cluster  (0) 2023.07.27
[배포] 무중단 배포와 배포 전략  (0) 2023.07.25
[Redis] Redis란?  (0) 2023.07.20
[AWS] Amaz S3 구축 및 이미지 업로드(Springboot)  (0) 2022.09.19
도커 입문  (0) 2021.12.27