Cache는 일종의 임시 data이다. DB의 내용이 변경된다면 cache도 따라서 최신화되어야 한다. Cache에 오래된 data가 남아있으면 사용자에게 잘못된 정보를 제공할 수도 있다. 따라서, 유효하지 않은 cache data를 무효화하는 것이 중요하다. 다양한 캐시 무효화 전략을 살펴보자.
Cache invalidation 전략
Cache invalidation이란 무엇인가?
캐시 무효화(cache invalidation)는 문자 그대로 cache data 중 유효하지 않은 데이터를 감지하고, 이를 사용할 수 없도록 무효로 처리해주는 방법을 의미한다. Cache invalidation 전략에는 대표적으로 세 가지 정도가 있다.
- Expiration time
- Freshness caching verification
- Active application invalidation
세 가지 캐시 무효화 전략
Expiration time
일반적으로 많이 사용하는 방식은 cache에 data를 저장할 때 만료되는 시간(이하 TTL)을 명시하는 것이다. Cache data는 해당 시간 내에서만 유효하고, TTL이 경과하면 유효하지 않은 data로 처리한다. TTL을 짧게 정하면 cache data가 최신화되는 주기가 빨라지게 된다.
TTL을 활용한 방식은 무엇보다 구현이 간단하다는 장점이 있다. TTL 값만 설정하면 알아서 캐시가 무효화되기 때문에 크게 신경쓸 것이 없다. TTL 값으로 인해 특정 data가 지나치게 cache에 오래 유지되는 것이 방지된다는 점도 expiration time 방식의 강점이다.
아이러니하게도 단점은 적절한 TTL 값을 설정하는 것이 까다롭다는 것이다. 잘 관리할 자신이 없다면 TTL을 짧게 정하는 것이 좋다. 하지만, 그만큼 cache 사용으로 인한 큰 성능 향상은 기대하기 어렵다는 문제가 생긴다. 또한, 데이터의 최신성이 TTL 기간동안 보장되지 않는다는 치명적인 단점도 있다. 실시간으로 최신성이 보장되어야 하는 경우에는 적절하지 않은 방법이다.
Freshness caching verification
Freshness caching verification은 별도의 검증 절차를 통해 cache가 유효한지 확인하는 방법이다. 보통 클라이언트에서 서버에 data를 요청하는 상황에서 불필요한 중복 데이터 반환을 방지하기 위해 사용된다. 예를 들어, 서버의 원본 data가 수정된 시간과 cache data가 생성된 시간을 클라이언트가 비교해서 해당 cache data의 유효성을 검증하고 서버에 실제 요청을 보낼지 여부를 결정한다. 만약 cache data가 유효하다면 서버에 요청을 보내지 않는 것이다.
이 전략은 expiration time 방식과 달리 필요할 때만 검증 및 캐시 업데이트를 진행하기 때문에 불필요한 캐시 무효화를 방지할 수 있다는 장점이 있다. 이에 따라 cache hit ratio가 늘어나서 읽기 성능이 향상되는 효과가 있다. 그리고 직접 cache와 DB를 확인하는 과정 덕분에 클라이언트 입장에서 데이터의 최신성이 보장된다는 장점도 있다. 이때 검증 과정에서 작은 양의 메타데이터만 주고받기 때문에 큰 부담은 없다.
단점은 cache가 사용될 때마다 어쨌든 추가적인 확인 절차를 수행하기 때문에 오버헤드가 발생한다는 것이다. 클라이언트에 읽기 요청이 올 때마다 서버와 통신을 한 번씩 해야 한다. 결국 읽기 성능이 서버의 응답 속도에 좌지우지된다는 얘기가 된다. 그리고 클라이언트가 검증 요청을 보내야만 무효화가 진행된다는 것도 단점이다. 유효하지 않은 불필요한 데이터가 캐시에 오래 남아서 무의미하게 용량을 차지하는 문제가 생긴다.
Active application invalidation
Active application invalidation은 data가 수정되는 코드가 실행될 때마다 서버에서 연관된 cache data를 전부 직접 무효화하는 방법이다. 예를 들어, 서버에 저장된 data에 변경이 발생하면 곧바로 해당 데이터를 cache에서 제거하거나 갱신하는 방식이다. Cache를 세밀하게 관리할 수 있기 때문에 이상적으로 설계되었을 때 가장 효율성이 좋다.
우선 이 방식은 데이터의 최신성이 항상 보장된다는 점이 큰 장점이다. 그리고 freshness caching verification 방식과 달리 클라이언트가 읽기 작업에서 추가적인 검증 작업을 위해 비용을 소모하지 않아도 되기 때문에 불필요한 오버헤드로부터 자유롭다는 장점도 있다.
단점은 별도의 코드를 통해 cache를 직접 컨트롤하기 때문에 에러가 발생하기 쉽고 관리하기 까다롭다는 것이다. 여러 분산 서버가 하나의 캐시를 공유하는 경우 data의 일관성을 유지하면서 캐시 업데이트를 하는 것이 다소 복잡해진다. 만약 변경해야 하는 데이터가 relation으로 복잡하게 얽혀있는 경우라면 여러 데이터를 무효화해야 할 수도 있기 때문에 더욱 구현이 복잡해진다. Aggregation을 통해 통계값을 캐싱하는 경우에도 마찬가지다. 특정 data 뿐만 아니라 해당 data가 사용된 통계치도 무효화해야 한다. 그리고 데이터가 자주 변경되는 상황에서는 캐시 무효화가 자주 발생하기 때문에 cache hit ratio가 낮아져서 읽기 성능이 저하되는 문제도 있다.
전략 선택 가이드
앞서 살펴본 세 가지 캐시 무효화 전략들의 장단점을 살펴보면 데이터의 최신성과 읽기 성능의 trade-off에 대한 고민들을 엿볼 수 있다. App의 특성에 맞게 적절한 전략을 선택하면 된다. 그런데, 사실 세 가지 전략 중 하나를 선택하는 것이 아니라 여러 전략을 조합하여 사용하는 것이 좋다. 예를 들어, freshness caching verification 방식의 경우 클라이언트의 요청같은 캐시 무효화를 위한 트리거가 없을 경우 cache data가 영원히 남게 되는 문제가 있다. 이때, TTL을 적용하면 이 문제를 완화할 수 있다.
- Expiration time: 데이터 최신성을 보장할 필요가 없는 경우
- Freshness caching verification: 데이터 변경이 적고 최신성을 보장해야 하는 경우
- Active application invalidation: 데이터 변경이 적고 최신성을 보장해야 하는 경우
결국 적절한 전략을 선택할 때는 최신성을 보장해야 하는지에 초점을 맞추면 도움이 된다. 금융같은 특수한 도메인을 제외하면 대부분의 서비스는 데이터의 최신성을 반드시 보장하지 않아도 된다. 이러한 경우 간단하게 TTL 방식을 사용하면 편하다. 보통 redis같은 캐시 도구들이 TTL 기능을 기본적으로 제공하기 때문에 별도로 코드를 작성할 필요도 없으면서 준수한 읽기 성능을 확보할 수 있다. 사용자의 불편을 최소화할 수 있을 정도의 TTL 값만 잘 설정하면 된다.
데이터의 최신성이 중요한 경우 TTL을 활용한 방식을 제외한 나머지 두 방법을 모두 사용할 수 있다. 그런데, 두 가지 방법 모두 최신성을 정확하게 유지하려면 서버에서 캐시 무효화 관련 로직을 복잡하게 구현해야 한다. (Freshness caching verification의 경우에도 서버에서 "수정된 시간"을 기록하는 방식을 구현해야 한다.)
데이터의 변경이 자주 발생하면서 최신성을 보장해야 한다면 오히려 해당 data 종류에 대해서는 캐시를 적용하지 않는 것이 좋을지도 모른다. 구현만 복잡할 뿐 캐시 적용 자체로 큰 성능 향상을 기대하기 어렵기 때문이다.
따라서, 두 가지 방법을 적용하는 경우는 데이터 변경이 자주 발생하지 않으면서 최신성을 보장해야 하는 시나리오다. 데이터 객체에 얽힌 다른 객체들을 왜래키 등을 활용해 연쇄적으로 무효화하는 코드를 모듈화해두거나, 특정 데이터를 활용해 산출한 통계치들의 목록을 조회하고 무효화해주는 코드를 미리 작성해두면 그나마 편리할 것이다.
More Posts
Next.js 정적 사이트 효율적으로 배포하기 - Deploying Next.js static exports with bash
콘텐츠가 많다면 정적 사이트를 웹 서버에 배포할 때 지나치게 오랜 시간이 걸릴 수도 있다. 배포 과정이 길어지면 여러가지 문제가 발생할 수 있다. 제한된 환경에서 추가적인 도구없이 딱 필요한 파일들만 파악하여 웹 서버에 전송해주는 bash script를 작성해보자.
Argo workflow에 kubernetes resource request와 limit을 설정하는 방법
Kubernetes 환경에서 argo workflow를 통해 파이프라인을 실행할 때는 자원 할당에 대한 고민이 필요하다. 그런데, 일반적인 방식으로는 의도대로 자원 할당이 되지 않는다. Kubernetes 환경의 안정적인 운영을 위해 argo workflow에 자원 설정을 하는 방법을 알아보자.
Python FastAPI로 파일 업로드 및 다운로드 가능한 web server 개발하기
FastAPI를 활용하면 파일을 업로드하거나 다운로드할 수 있는 web server를 매우 간단하게 구현할 수 있다. 예제 코드와 함께 최대한 간단하게 파일 업로드 및 다운로드 API를 구현하고 테스트하는 방법을 알아보자.
Comments