목차

  • 캐싱이란
  • 캐싱 유형
    • Local Caches
    • Remote Caches
  • 캐싱 전략
    • Cache Aside(lazy loading)
    • Write Through
    • Write Back
  • 데이터 제거 방식
    • expiration
    • eviction

 

캐싱이란

캐싱이란 자주 사용하는 데이터를 보다 빠르게 액세스 하기 위해, 캐시와 같이 가까운 장소에 임시로 저장해 두는 것을 말한다. cpu 코어와 메모리, 웹 클라이언트와 서버, 서버와 db 등 다양한 곳에서 캐싱 기술이 사용되고 있다.

 

만약 모든 데이터가 동일한 사용 빈도를 띈다면 캐싱이라는 기술이 큰 의미를 갖기 힘들었을 것이다. 하지만 소수의 데이터가 자주 사용되는 경향이 있기 때문에 캐싱이 큰 힘을 발휘하고 있다. 이를 캐시의 지역성이라고 하며 크게 시간 지역성과 공간 지역성으로 나뉜다.

  • 시간 지역성 : 최근에 접근한 데이터에 다시 접근하는 경향. ex) for문의 인덱스
  • 공간 지역성 : 최근에 접근한 데이터의 주변 공간에 다시 접근하는 경향. ex) 배열

실생활에서도 지역성을 쉽게 발견할 수 있다. 인스타그램을 예로 들면, 보통 소수의 사용자들이 많은 팔로워를 보유하고 있다. 또한 예전 게시물보다는 최근 게시물을 소비하는 빈도가 더욱 높기 때문에, 일부 게시물의 사용 빈도가 높다는 사실을 알 수 있다.

 

캐싱 유형

Local caches

로컬 캐시란 애플리케이션 내에 자주 사용되는 데이터를 저장하는 것을 말한다.

 

장점

  • 데이터 검색과 관련된 네트워크 트래픽이 제거된다.

단점

  • 각 서버 간의 로컬 캐시(db 행 데이터, 웹 콘텐츠, 세션 테이터 등)가 공유되지 않는다. 따라서 분산 환경에서 공유 데이터에 대해 문제를 야기할 수 있다.
  • 서버가 중단되면 캐시 데이터가 손실된다.

원격 캐시를 사용하면 로컬 캐시의 단점을 극복할 수 있다.

Remote caches

원격 캐시란 별도의 전용 인스턴스에 캐시 데이터를 저장하는 것을 말한다. 일반적으로 Redis나 Memcached와 같은 key/value 저장소로 구축한다.

 

장점

  • 요청의 평균 latency는 밀리초 단위 미만으로, 디스크 기반 db보다 훨씬 빠르다.
  • 서버 간에 캐시 공유가 가능하기 때문에 분산 환경에 적합하다.

딘점

  • 로컬 캐시보다 느리다.

네트워크 latency가 문제가 되는 경우에는 로컬 캐시와 함께 사용하는 방법을 적용하면 된다. 하지만 복잡하기 때문에 꼭 필요한 경우에만 사용을 고려한다.

 

캐싱 전략

Cache-Aside(Lazy Loading)

가장 일반적으로 사용하는 방식이며, 절차는 다음과 같다.

  1. db를 읽어야 하는 요청이 들어오는 경우, 가장 먼저 캐시에 데이터가 있는지 확인한다.
  2. 이용 가능한 데이터가 존재하면(cache hit) 캐시 데이터를 반환한다.
  3. 이용 가능한 데이터가 존재하지 않으면(cache miss) db를 통해 데이터를 조회한다. 그다음 캐시에 데이터를 저장하고, 데이터를 응답으로 반환한다.

 

장점

  • 애플리케이션이 실제로 요청하는 데이터만 캐싱하기 때문에, 비용 효율적으로 캐시를 활용할 수 있다.
  • 단순한 구현으로 즉각적인 성능 향상을 기대할 수 있다.

단점

  • cache miss가 발생해야 캐시에 데이터를 적재한다. 따라서 최초 요청에 대해 오버헤드가 존재한다.

 

Write-Through

cache miss가 발생한 뒤에 캐시에 적재하는 lazy loading과 달리, DB 변경이 발생하는 경우에 캐시도 함께 최신화하는 방식이다. 하지만 캐시 데이터가 없거나 만료되었을 수도 있기 때문에, 보통 lazy loading 전략과 함께 사용한다.

 

장점

  • 캐시와 db가 동기화되어 있기 때문에, 전반적인 애플리케이션 성능과 사용자 경험이 향상된다.
  • cache-aside에 비해 db 쿼리가 더 적게 수행된다.

단점

  • 자주 요청하지 않는 데이터도 캐싱된다. 따라서 캐시가 더 크고 비용이 많이 든다.

캐싱 전략을 효과적으로 사용하기 위해서는, write-through와 lazy loading 전략을 적절하게 사용해야 한다. 그리고 데이터에 만료 설정을 걸어주어야 한다.

 

Write-Back

데이터를 캐시에만 쓰고, 캐시의 데이터를 일정 주기로 DB에 업데이트하는 방식이다.

 

장점

  • 쓰기가 많은 경우 DB 부하를 줄일 수 있다.

단점

  • 캐시가 DB에 쓰기 전에 장애가 생기면 데이터 유실될 수 있다.

 

데이터 제거 방식(with redis)

expiration

데이터에 ttl을 설정하여 시간 단위로 캐시 데이터의 수명을 제어할 수 있다. 제한 시간을 초과하면 캐시에서 데이터를 삭제하고, 원본 데이터에서 데이터를 새로 가져온다.

참고로 레디스에서는 논리적으로 만료되더라도 메모리에 남아있는 경우가 있다. 다만 존재하지 않는 것으로 간주되며, 해당 메모리는 결국 회수되니 문제 될 건 없다. (참고 링크)

 

TTL을 적용하기 위해서는 다음의 두 가지를 고려한다. 몇 분, 몇 초 단위로 ttl을 적용하더라도, 적절한 ttl은 성능과 사용자 경험에 큰 이점을 준다.

  1. 기반 데이터가 변경되는 정도.
  2. 오래된 데이터가 반환될 수 있는 리스크.

eviction

eviction은 캐시 메모리가 꽉 차거나 maxmemory 설정보다 클 때 발생한다. 가장 마지막에 사용한 키 제거와 같이, 제거 정책을 설정할 수 있다. 절차는 다음과 같다.

  1. 클라이언트가 데이터를 추가하는 command를 실행한다.
  2. redis는 메모리 사용량을 확인하고, maxmemory 제한보다 크다면 정책에 따라 키를 제거한다.
  3. 새로운 command가 실행된다.

 

일반적으로 lru 정책을 가장 많이 사용하며, 정책의 종류는 다음과 같다.

  • noeviction: 메모리 제한에 도달하면 새로운 값들이 저장되지 않는다. 레플리케이션을 사용하는 경우 기본값이다.
  • allkeys-lru: 가장 마지막에 사용한 키를 제거한다.
  • allkeys-lfu: 가장 적게 사용한 키를 제거한다.
  • volatile-lru: expire가 걸려있는 키 중에서, 가장 마지막에 사용한 키를 제거한다.
  • volatile-lfu: expire가 걸려있는 키 중에서, 가장 적게 사용한 키를 제거한다.
  • allkeys-random: 키를 랜덤으로 제거한다.
  • volatile-random: expire가 걸려있는 키 중에서, 랜덤으로 제거한다.
  • volatile-ttl: expire가 걸려있는 키 중에서, ttl이 가장 짧은 키를 제거한다.

 

제거가 발생하는 경우는 일반적으로 확장이 필요하다는 신호이다. 하지만 의도적으로 정책에 따라 키를 관리하고 있다면 무시해도 좋다.

 

 

 

참고 자료

+ Recent posts