캐싱과 API 성능 최적화 이해
캐싱과 Redis 개념, Cache Hit/Miss, TTL, 캐시 무효화 전략, API 성능 최적화를 위한 캐싱 고려사항
For the English version of this post, see here.
오늘 학습한 개념
캐싱 (Cache)
웹 서비스에서는 사용자가 요청할 때마다 항상 데이터베이스에 접근하는 방식이 기본적이다. 하지만 모든 요청이 매번 DB까지 도달하면 요청 수가 많아질수록 DB에 큰 부담이 생긴다. 특히 공지사항, 인기 상품 목록, 메인 페이지 배너, 자주 조회되는 게시글처럼 여러 사용자가 반복적으로 조회하는 데이터는 매번 DB에서 가져오는 것보다 더 빠른 저장소에 잠시 저장해두고 재사용하는 것이 효율적이다.
이때 사용하는 방식이 캐싱이다. 캐싱은 자주 사용되는 데이터를 더 빠르게 접근할 수 있는 공간에 저장해두고, 다음 요청부터는 원본 저장소의 DB가 아니라 캐시에서 데이터를 가져오는 전략이다.
캐시를 사용하는 이유
캐시를 사용하는 가장 큰 이유는 응답 속도 개선과 DB 부하 감소이다.
예를 들어 많은 사용자가 같은 공지사항을 조회한다고 가정해보면, 캐시가 없다면 모든 요청이 DB로 전달된다. 사용자가 1,000명이면 DB 조회도 1,000번 발생할 수 있다. 하지만 첫 번째 요청에서 DB 데이터를 조회한 뒤 그 결과를 Redis 같은 캐시에 저장해두면, 이후 요청들은 DB가 아니라 캐시에서 데이터를 가져올 수 있다.
이렇게 하면 DB는 반복 조회 요청을 덜 받게 되고, 사용자는 더 빠른 응답을 받을 수 있다.
즉, 캐시는 단순히 ‘데이터를 저장하는 곳’이라기보다, 자주 조회되는 데이터를 빠르게 제공하기 위한 성능 최적화 계층이라고 볼 수 있다.
Cache Hit와 Cache Miss
문제를 풀면서 중요하게 정리한 개념은 Cache Hit와 Cache Miss이다.
Cache Hit는 요청한 데이터가 캐시에 존재하는 경우이다.
이 경우에는 DB까지 가지 않고 캐시에서 바로 데이터를 가져올 수 있기 때문에 응답 속도가 빠르다.
반대로 Cache Miss는 요청한 데이터가 캐시에 없는 경우이다.
이때는 DB에서 데이터를 조회한 뒤, 조회한 결과를 캐시에 저장하고 응답한다. 이후 같은 데이터 요청이 들어오면 Cache Hit가 발생할 수 있다.
사용자가 데이터를 요청한다.
먼저 캐시에 데이터가 있는지 확인한다.
캐시에 있으면 캐시에서 바로 응답한다.
캐시에 없으면 DB에서 조회한다.
DB 조회 결과를 캐시에 저장한다.
사용자에게 응답한다.
이 흐름을 이해하면 캐싱이 왜 DB 접근 횟수를 줄이는지 알 수 있다.
Redis를 캐시로 사용하는 이유
캐시 저장소로는 Redis가 자주 사용된다.
Redis는 데이터를 디스크가 아니라 주로 메모리 기반으로 저장하기 때문에 DB보다 훨씬 빠르게 데이터를 읽고 쓸 수 있다. 그래서 자주 조회되는 데이터를 Redis에 저장해두면 API 응답 속도를 개선할 수 있다.
물론 애플리케이션 내부 메모리에 데이터를 저장하는 방식도 가능하다. 이 방식은 매우 빠르지만, 애플리케이션이 재시작되면 데이터가 사라질 수 있고, 서버가 여러 대일 때 각 서버마다 캐시 상태가 달라질 수 있다. 반면 Redis를 별도의 캐시 저장소로 두면 여러 애플리케이션 서버가 같은 캐시를 공유할 수 있다는 장점이 있다.
따라서 대규모 서비스에서는 Redis 같은 외부 인메모리 저장소를 캐시 계층으로 두는 경우가 많다.
TTL과 캐시 만료 정책
캐시는 빠르지만, 항상 최신 데이터를 보장하지는 않는다.
그래서 캐시에 저장된 데이터가 언제까지 유효한지 정하는 정책이 필요하다. 이때 사용하는 개념이 TTL(Time To Live)이다.
TTL은 캐시에 저장된 데이터의 유효 시간이다. 예를 들어 어떤 데이터를 10분 동안만 캐시에 저장하도록 설정하면, 10분이 지난 뒤에는 캐시에서 자동으로 사라질 수 있다.
데이터가 자주 바뀌지 않는다면 TTL을 길게 설정해도 된다. 예를 들어 공지사항 목록이나 카테고리 목록처럼 변경 빈도가 낮은 데이터는 오래 캐싱해도 문제가 적다.
반대로 재고 수량, 주문 상태, 결제 상태처럼 자주 변경도거나 정확성이 중요한 데이터는 TTL을 짧게 설정하거나 캐싱 자체를 신중하게 적용해야 한다.
오늘 문제를 통해 캐싱에서는 단순히 ‘저장하면 빠르다’가 아니라, 얼마나 오래 저장할 것인지, 언제 갱신할 것인지, 최신성이 얼마나 중요한지를 함께 고려해야 한다는 점을 정리할 수 있었다.
캐시 무효화와 데이터 일관성
캐싱에서 가장 조심해야 하는 부분은 데이터 일관성이다.
DB의 데이터가 변경되었는데 캐시에 예전 데이터가 남아있다면, 사용자는 오래된 정보를 보게 될 수 있다. 예를 들어 상품 가격이 DB에서는 변경되었는데 Redis에는 이전 가격이 남아 있다면 잘못된 가격이 사용자에게 노출될 수 있다.
이 문제를 해결하기 위해 데이터가 변경될 때 캐시를 함께 처리해야 한다.
대표적인 방법은 두 가지이다.
캐시 삭제
캐시 갱신
캐시 삭제는 DB 데이터가 변경될 때 관련 캐시를 지워버리는 방식이다. 이후 사용자가 다시 조회하면 Cache Miss가 발생하고, DB에서 최신 데이터를 가져와 다시 캐시에 저장한다.
캐시 갱신은 DB 데이터가 변경될 때 캐시의 값도 함께 최신 값으로 바꾸는 방식이다.
두 방식 모두 장단점이 있다. 캐시 삭제는 구현이 비교적 단순하지만, 다음 조회 시 DB 접근이 다시 발생한다. 캐시 갱신은 최신 데이터를 바로 유지할 수 있지만, 갱신 로직이 복잡해질 수 있다.
결국 캐시 전략은 서비스 특성과 데이터 성격에 따라 다르게 설계해야 한다.
API 성능 최적화 관점에서 본 캐싱
대부분의 웹 API는 DB 조회, 외부 API 호출, 파일 I/O 같은 작업에서 시간이 많이 걸린다. 따라서 성능을 개선하려면 병목이 어디에서 발생하는지 파악해야 한다.
캐싱은 특히 읽기 요청이 많고, 데이터 변경이 적은 경우에 효과적이다.
예를 들어 다음과 같은 데이터는 캐싱 대상으로 적합하다.
자주 조회되는 공지사항
메인 페이지 배너
카테고리 목록
인기 게시글 목록
변경 빈도가 낮은 설정값
여러 사용자가 공통으로 조회하는 데이터
반대로 다음과 같은 데이터는 캐싱에 신중해야 한다.
실시간 재고
결제 상태
사용자별 민감 정보
권한에 따라 응답이 달라지는 데이터
자주 변경되는 데이터
캐싱은 모든 API에 무조건 적용하는 것이 아니라, 읽기 빈도와 변경 빈도를 기준으로 적용 여부를 판단해야 한다는 점이 중요하다고 느꼈다.
느낀 점
오늘 캐싱과 API 성능 최적화 문제를 풀면서, 캐싱은 단순히 “빠르게 만들기 위한 기술”이 아니라는 것을 알게 되었다.
처음에는 Redis에 데이터를 저장하면 무조건 성능이 좋아질 것이라고 생각했지만, 실제로는 어떤 데이터를 캐싱할지, 얼마 동안 유지할지, 데이터가 변경되었을 때 캐시를 어떻게 처리할지를 함께 고민해야 했다.
특히 캐시가 있으면 DB 부하는 줄어들 수 있지만, 잘못 관리하면 사용자에게 오래된 데이터를 보여줄 수 있다는 점이 중요했다. 그래서 캐싱은 성능과 데이터 정확성 사이의 균형을 잡는 작업이라고 느꼈다.
API 성능 최적화 관점에서도, 모든 요청을 무조건 DB로 보내는 구조는 요청량이 많아질수록 한계가 생길 수 있다. 자주 조회되는 데이터는 캐시를 활용하고, 정확성이 중요한 데이터는 캐싱을 신중하게 적용하는 식으로 데이터 특성에 따라 전략을 다르게 가져가야 한다.
댓글
궁금한 점, 피드백, 오류 제보를 남겨 주세요.