본문 바로가기

Dot Database/Concept

[DB] 캐싱과 캐싱 전략에 대해 알아보자

    Cache

    Cache는 데이터나 값을 저장하는 임시 저장소로, 데이터를 더 빠르고 효율적으로 액세스할 수 있게 해준다.

    • 원본 데이터 접근보다 빠르다.
    • 같은 데이터를 반복적으로 접근하는 상황에서 사용하기에 알맞다.
    • 인증 세션 값과 같은 잘 변하지 않는 데이터일수록 더 효율적이다.

     

    컴퓨터 메모리 계층 구조

    레이어별로 캐시를 보면 용량은 위로 갈수록 커지고 속도로 밑으로 내려갈수록 빨라진다. 보통 우리가 사용하는 Redis는 Memory층에 존재한다고 보면 된다. 

    • Disk가 제일 느리고 L3 < L2 < L1 순으로 속도가 빠르다.
    • Disk 접근 속도가 SSD를 쓰고있지만 Memory와 비하면 속도가 굉장히 차이난다. 그렇기 때문에 Memory에 올려놓고 쓰는 게 Disk에서 읽어오는 것보다 훨씬 빠르다. 그 대신 용량은 당연히 Disk가 훨씬 크다.

    memory hierarchy

     

    추상적인 웹 서비스 구조 (클라이언트 - 웹 서버 - DB)

    클라이언트가 웹 서버에 접속하고 요청을 보내면 웹 서버가 DB에 데이터를 읽거나 쓰기 작업을 요청한다. DB도 사실 내부용 캐시가 있지만 캐시 용량이 Memory 사이즈보다 커지면 당연히 Disk를 사용해야 한다. 쿼리 결과를 내부적 캐시에 담고 있는데 여러가지 요청을 처리하다 보면 기존에 있던 캐시를 날리고 디스크에서 새로 읽어야 하는 순간이 온다. 그래서 디스크에 접근할 때마다 속도가 굉장히 느려질 수 있다. 

     

    Cache 구조 및 전략

    Redis로 캐시로 사용할 때 어떻게 배치할 것이냐는 캐싱 전략이 필요하다. 이에 따라 성능에 영향을 끼치기 때문에 상황(데이터 유형, 데이터 액세스 패턴)에 맞게 적절한 전략을 사용해줘야 한다.

    • 시스템이 많이 작성하는데 덜 자주 읽습니까? (ex. 시간 기반 로그)
    • 데이터를 한번 쓰고 여러 번 읽습니까? (ex. 사용자 프로필)
    • 반환되는 데이터는 항상 고유합니까? (ex. 검색어)

     

    모바일 게임용 Top-10 리더보드 시스템의 캐싱 전략과 사용자 프로필을 집계하고 반환하는 서비스의 캐싱 전략은 많이 다르다. 올바른 캐싱 전략을 선택하는 것이 성능 향상의 핵심이다. 이 글에서 알아볼 캐시 전략은 다음 총 5가지이다.

    1. Look-Aside(Cache-Aside) 읽기 전략
    2. Read-Through 읽기 전략
    3. Write-Around 쓰기 전략
    4. Write-Through 쓰기 전략
    5. Write-Back 쓰기 전략

     

    1. Look-Aside 읽기 전략 (Lazy Loading)

    Look-Aside는 앱에서 데이터를 읽는 전략이 많을 때 사용하는 전략이다. 이 구조는 Redis를 캐시로 쓸 때 가장 일반적으로 사용하는 방법이다. 캐시는 찾는 데이터가 없을 때 DB에 직접 조회해서 입력되기 때문에 Lazy Loading이라고 한다.

    • 이 방식은 가장 범용적이며 읽기 요청이 많은 경우에 적합하다. 이 구조는 Redis가 다운되더라도 바로 장애로 이어지지 않고 DB에서 데이터를 가져올 수 있다는 장점이 있다. 
    • 그런데 만약 캐시에 많은 커넥션이 붙어있는 상태에서 다운이 발생하면 동시에 DB로 그 커넥션이 다 붙기 때문에 갑자기 DB 부하가 많이 몰릴 수 있다.

    Look-Aside 전략을 사용할 때 가장 일반적인 쓰기 전략은 DB에 직접 쓰는 Write-Around 쓰기 전략이다. 이 경우 캐시와 DB의 데이터가 일치하지 않을 수 있다. 이를 해결하기 위해 TTL을 사용하고 TTL이 만료될 때 까지는 변경되지 않은 캐시 데이터를 계속 제공한다. 데이터 최신성을 보장해야 하는 경우에는 캐시 entry를 무효화하거나 더 적절한 쓰기 전략(Cache를 거친 쓰기 전략)을 이용해야 한다.

    과정

    1. 앱은 데이터를 찾을 때 캐시를 먼저 확인한다.
    2. 캐시에 데이터가 있으면 해당 데이터를 읽어오는 작업을 반복한다.
    3. 만약 Redis에 해당 키가 존재하지 않는다면 (Cache miss) 앱은 DB에 접근해서 데이터를 직접 가지고 온 뒤 다시 Redis에 저장하는 과정을 거친다. 그래서 동일 데이터에 대한 후속 읽기 결과 Cache Hit이 된다.

    Look-Aside 읽기 전략 (Lazy Loading)

     

    2. Read-Through 읽기 전략

    Read-Through 전략은 데이터를 읽을 때 캐시로만 데이터를 읽어온다. 만약 Cache Miss가 발생하면 DB에서 해당 데이터를 캐시에 바로 저장한다. 즉 캐시는 앱과 DB 중간에 위치해 앱은 캐시만 바라보게 되고 DB는 캐시만 바라보게 된다. 

    • 뉴스 기사 읽기와 같이 동일한 데이터가 여러 번 읽기 요청이 되는 경우에 적합하다. 디스크 읽기 비용을 절약할 수 있다. 
    • 첫 요청은 항상 Cache Miss가 발생하여 데이터가 로딩된다.
    • Look-Aside와 다른 점은 읽기에 대한 앱의 관점이다. Look-Aside의 경우 Cache Miss가 나면 앱이 직접 DB에 데이터를 조회한 반면, Read-Through는 캐시에서 DB에 데이터를 직접 조회하여 로드한다. 그리고 Read-Through는 캐시의 데이터 모델과 DB 데이터 모델이 다를 수 없다.

    과정

    1. 앱은 모든 데이터를 캐시로만 읽어온다.
    2. Cache Miss가 발생한 경우 캐시에서 직접 DB 데이터를 읽어온다. 

    Read-Through 읽기 전략

     

    Cache Warming - 데이터 미리 캐싱해두기

    Look-Aside에서 캐시가 다운되면 다시 새로운 캐시를 투입해야 하고 DB에 새로운 데이터를 넣으면 커넥션은 자연스레 DB로 몰리게 된다. 이러한 경우 초기에 Cache Miss가 엄청 발생해서 성능 저하가 올 수 있다. 매번 첫 요청에 Cache Miss가 발생하는 Read-Through도 마찬가지이다.

     

    이럴 때는 캐시로 미리 데이터를 밀어넣어주는 Cache Warming 작업을 해주면 된다. 실제로 실무에서는 특정 이벤트 상품 오픈 전 해당 상품의 조회수가 몰릴 것을 대비해 상품 정보를 미리 DB에서 캐시로 올려주는 작업을 처리한다고 한다. 

    Cache Warming

     

    3. Write-Through 쓰기 전략

    이는 Read-Through와 같다. Write-Through 전략은 DB에 데이터를 저장할 때 먼저 캐시에 기록된 다음 DB에 저장되는 방식이다.

    • 캐시는 항상 최신 정보를 가지고 있고 DB와 동기화되어 데이터 일관성을 보장한다는 장점이 있지만
    • 저장할 때 마다 두 단계 스텝(앱 → Redis → DB)을 거쳐야 하기 때문에 추가 쓰기 시간이 발생하여 상대적으로 느리다는 단점이 있다. 그리고 저장하는 데이터가 재사용하지 않을 수도 있는데 무조건 캐시에 넣는 것은 리소스 낭비이다.

    따라서 이렇게 데이터를 저장할 때에는 얼마 동안만 캐시에 보관하고 있겠다는 TTL을 설정해주는 것이 좋다. 

    과정

    1. 앱은 모든 데이터를 캐시에 저장한 후 DB에 저장한다.

    Write-Through 쓰기 전략

    Read-Through 캐시와 함께 사용하면 Read-Through의 모든 이점을 얻을 수 있고 데이터 일관성도 보장되어 캐시 무효화 기술을 사용하지 않아도 된다.

    • AWS의 DynamoDB Accelerator(DAX)가  Read-Through와 Write-Through를 함께 사용한 좋은 예이다. 아는 DynamoDB와 앱이 인라인으로 배치된다. 그래서 DynamoDB에 대한 읽기 쓰기는 DAX를 통해 수행할 수 있다.

     

    그런데 만약 캐시 전략을 잘못 조합하면 성능이 안좋아진다. 예를 들어 실시간 로그를 기록하는 시스템에서 Read-Through / Write-Through 전략을 사용한다면 자주 읽지도 않는 데이터가 캐시에 적재되기 때문에 리소스 낭비가 발생하게 된다. 이러한 경우에는 Read-Through / Write-Around 전략을 사용하는 게 적합하다. 

     

    4. Write-Around 쓰기 전략

    Write-Around 전략은 DB에만 데이터를 저장하고 읽은 데이터만 캐시에 저장하는 방식이다. 일단 모든 데이터는 DB에 저장되고 Cache Miss가 발생한 경우에만 DB에서 캐시로 데이터를 끌어오게 된다.

    • 자주 읽히지 않는 데이터는 캐시에 로드되지 않으니 리소스를 절약할 수 있다.
    • 그러나 캐시 내의 데이터와 DB내의 데이터가 다를 수 있다는 단점이 있다. (ex. 캐시에 이미 로드된 데이터를 수정할 경우)

    과정

    1. 앱은 모든 데이터를 DB에 저장한다.
    2. Cache Miss가 발생한 경우에만 DB에서 캐시로 데이터를 저장한다.

    Write-Around 쓰기 전략

    그래서 Write-Around는 데이터가 한 번 쓰여지고 덜 자주 읽히거나 읽지 않는 상황에서 좋은 성능을 제공할 수 있다. 이 경우 읽기 전략은 크게 상관없다. (Read-Through나 Look-Aside와 모두 결합 가능)

    • 실시간 로그
    • 채팅방 메시지

     

    5. Write-Back 쓰기 전략

    Write-Back 전략은 쓰기 작업을 캐시에 먼저 저장했다가 특정 시점마다 DB에 저장하는 방식이다. 디스크 기반 DB로 치면 데이터를 굉장히 많이 써야되기 때문에 무조건 디스크에 저장해야 하는 상황에서 주로 사용한다. 매번 디스크 쓰기를 실행하면 시간이 오래 걸리기 때문에 캐시에 저장해두었다가 묶음으로 디스크 쓰기 작업을 실행해주는 것이다.

    • 쓰기가 많은 경우에 적합하다. DB 디스크 쓰기 비용을 절약할 수 있다. 그리고 특정 시점에만 DB에 쓰기 요청을 하기 때문에 DB 오류에도 탄력적이다.
    • 바꿔서 생각해보면 데이터를 캐시에 모두 모아뒀다가 DB에 옮기기 때문에 도중 캐시에 장애가 발생하면 데이터 유실이 크게 발생할 수 있다.

    대부분의 RDB 스토리지 엔진 내부에는 Write-Back 캐시 기능을 갖고 있다. 쿼리는 먼저 메모리에 기록되다가 특정 시점에 한 번에 Disk에 flush된다. 

    과정

    1. 앱은 모든 데이터를 캐시에 저장한다.
    2. 특정 시점이 되면 캐시에 저장된 데이터를 DB에 저장한다.
    3. 그렇게 DB에 저장된 데이터는 캐시에서 삭제해준다.

    Write-Back 쓰기 전략

     

    Write-Back은 Read-Through와 결합하면 가장 최근 업데이트되고 액세스 된 데이터를 항상 캐시에서 사용할 수 있다.

     


    참고

    https://meetup.toast.com/posts/225

    https://youtu.be/92NizoBL4uA

    https://youtu.be/mPB2CZiAkKM

    https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/