DB 캐싱
파레토 법칙과 캐싱
시스템 결과 중 80%가 전체 원인의 20%에서 비롯된다는 개념에 기초하여, 파레토 법칙은 전체 데이터 중 극소수의 데이터가 대부분의 요청을 차지함을 강조합니다.
캐싱은 이 원리를 활용하여, 전체 데이터 중 빈번하게 요청되는 약 20%의 데이터를 우선적으로 캐싱함으로써 전체 시스템의 효율성을 높이고 응답 속도를 향상시키는 전략입니다.
캐시
데이터 원본보다 더 빠르고 효율적으로 액세스할 수 있는 임시 데이터 저장소를 캐시라고 합니다.
캐시를 효과적으로 활용하려면, 캐시 대상 데이터를 신중하게 선정하는 것이 중요합니다. 특히, 다음 두 가지 유형의 데이터는 캐시 도입 시 성능 향상을 기대할 수 있습니다.
-
자주 조회되면서 변경 빈도가 낮은 데이터:
예를 들어, 제품 정보, 카테고리 목록 등은 조회 빈도가 높고 업데이트가 드물어 DB와 캐시 간의 불일치 가능성이 낮습니다.
-
최신성이 엄격하게 요구되지 않는 데이터:
실시간으로 최신 업데이트가 반영되지 않아도 되는 경우, 캐시를 사용하더라도 큰 문제가 발생하지 않습니다.
데이터베이스와 캐시
데이터 베이스에서 캐시를 활용하는 방법은 크게 두가지로 구분 됩니다.
-
외부 캐시 서버 도입
Redis 같은 별도의 캐시 서버를 사용하여, 애플리케이션과 데이터베이스 사이에 빠른 임시 데이터 저장소를 활용하는 방법입니다.
-
DB 내부 캐시
각 데이터베이스는 자체적으로 버퍼 풀 기능을 제공합니다. 자주 사용되는 데이터를 메모리에 저장하고 빠르게 액세스할 수 있도록 지원합니다. 이 방식은 추가 인프라 없이 데이터베이스 내부에서 자동으로 관리됩니다.
DB 내부 캐시 : 버퍼풀
버퍼풀(Buffer Pool)
대부분의 데이터베이스 엔진마다 버퍼풀이 존재합니다. 예를 들어 MySQL에는InnoDB 버퍼 풀, PostgreSQL에는 shared_buffers이 존재합니다.
데이터베이스 내부에는 디스크에서 읽어온 데이터 페이지를 캐시하고, 새로 작성하거나 변경된 데이터 페이지를 임시로 보관하는 공간인 버퍼풀이 있습니다.
버퍼 풀은 디스크에서 읽어온 데이터를 이 페이지 단위로 캐싱합니다. 예를 들어, 데이터베이스에서는 개별 행만 캐싱하는 것이 아니라, 그 행이 포함된 전체 페이지(데이터 블록)를 캐싱합니다. 한 번의 디스크 I/O로 여러 관련 데이터를 빠르게 읽어올 수 있도록 최적화 되어있습니다.
공간적 지역성(Spatial Locality) 데이터베이스는 공간적 지역성 원칙을 기반으로 설계되어있습니다. 한 행을 조회할 때 그 행이 속한 페이지의 다른 행들도 곧 필요할 가능성이 높다고 가정합니다.
데이터베이스는 데이터 조회시 버퍼 풀을 확인하여 작업을 처리합니다. 요약하면 다음과 같습니다:
-
버퍼 풀 확인:
데이터 읽기/쓰기 작업 전에, 데이터베이스는 메모리에 있는 버퍼 풀에서 요청된 데이터 페이지를 찾습니다.
-
Cache Hit (캐시 히트):
요청한 데이터 페이지가 버퍼 풀에 있으면, 디스크 접근 없이 바로 해당 페이지를 사용하여 빠르게 작업을 수행합니다.
-
Cache Miss (캐시 미스):
요청한 데이터 페이지가 버퍼 풀에 없으면, 디스크에서 해당 페이지를 읽어와 버퍼 풀에 로드한 후 작업을 진행합니다.
데이터베이스는 기본적으로 버퍼풀, 실행 계획 캐시, 메타데이터 캐시, 트랜잭션 로그 버퍼 등 내부 캐시 메커니즘을 통해 성능을 최적화합니다.그러나 이러한 내부 캐시는 데이터 베이스 자원을 직접적으로 이용하기 때문에, 용량 제한, 동시성 처리로 인한 부하와 관련된 한계가 있습니다.
애플리케이션 전반에서 자주 사용되는 데이터나 쿼리 결과를 별도로 캐싱하여 DB 부하를 줄이고 응답 속도를 개선하기 위해서는 Redis 같은 외부 캐시 서버 도입을 고려하는 것이 좋습니다.
외부 캐시 서버 도입(Remote Cache) : Redis
Redis 같은 별도의 캐시 서버를 사용하여, 애플리케이션과 데이터베이스 사이에 빠른 임시 데이터 저장소를 활용하는 방법입니다.
외부 캐시 서버를 도입하면 다음과 같은 장점을 얻을 수 있습니다.
- 데이터베이스 부하 감소
- 반복적인 데이터 요청을 외부 캐시가 처리함으로써, 데이터베이스에 대한 직접적인 요청 횟수를 줄이고 부하를 낮출 수 있습니다.
- 확장성 및 분산 처리
- 여러 애플리케이션 인스턴스가 동일한 캐시 데이터를 공유할 수 있어, 분산 환경에서의 확장성이 좋아집니다.
- 유연한 캐시 관리
- TTL(Time-To-Live) 설정, 다양한 데이터 구조 지원 등으로 캐시 정책을 세밀하게 조정할 수 있습니다.
캐싱 전략
캐시를 사용하면 데이터 정합성 문제에 직면할 수 있습니다.
데이터 정합성이란, 동일한 데이터가 캐시(Cache Store)와 데이터베이스(Data Store) 두 곳에 저장될 때, 두 저장소 간의 데이터 값이 일치하지 않는 현상을 말합니다.
예를 들어, 어떤 게시글의 좋아요 개수가 캐시에는 10으로 저장되어 있지만, 데이터베이스에는 7로 저장될 수 있습니다. 이는 데이터 조회 시, 캐시에 저장된 값을 우선 활용하기 때문입니다.
캐시를 사용하지 않으면 모든 데이터 조회와 저장이 데이터베이스에서 이루어져 정합성 문제가 발생하지 않지만, 외부 캐시를 도입하면 동일한 데이터라도 두 저장소에서 다른 값을 가질 가능성이 있습니다.
따라서, 적절한 캐시 읽기 및 쓰기 전략을 수립하여 데이터 불일치 문제를 최소화하면서도 빠른 성능을 확보하는 것이 매우 중요합니다
캐시 읽기 전략(Read Cache Strategy)
- Look Aside 패턴
- 데이터를 찾을 때 우선 캐시에 저장된 데이터가 있는지 우선적으로 확인하는 전략입니다.
- 반복 적인 읽기가 많은 호출에 적합합니다.
- 캐시 서버가 다운된되어도 DB에서 데이터를 가져올 수 있어 서비스 장애를 막을 수 있습니다.
- Read Through 패턴
- 캐시에서만 데이터를 읽어오는 전략
- 데이터 조회를 전적으로 캐시에만 의존하므로, 캐시 서버가 다운되는 경우 서비스 이용에 차질이 생길 수 있다.
캐시 쓰기 전략(Write Cache Strategy)
- Write Back 패턴
- 캐시와 DB의 데이터 동기화를 비동기로 처리하는 패턴입니다.
- 데이터 저장시 DB에 바로 저장하지 않고, 캐시에 모아서 일정 주기 배치 작업을 통해 DB에 반영합니다.
- 캐시에 모아놨다가 DB에 반영하기 때문에 쓰기 쿼리 비용과 비용을 줄일 수 있습니다.
- 하지만, 캐시에서 오류나 장애가 발생하면 아직 DB에 반영되지 않은 데이터가 손실될 위험이 있습니다.
- Write Through 패턴
- DB와 Cache에 동시에 데이터를 저장하는 전략입니다.
- 데이터를 저장할 때 먼저 캐시에 저장한 다음 바로 DB에 저장합니다.
- 항상 동기화가 되어있어 항상 최신정보를 보장합니다.
- 매 요청마다 2단계의 write가 발생하므로 상대적으로 느리고, 빈번한 생성 및 수정이 발생하는 경우 성능 이슈 발생할 수 있습니다.
- Write Around 패턴
- 모든 쓰기 작업은 직접 데이터베이스에 수행되며, 캐시는 쓰기 작업 시에는 사용되지 않습니다.
- 캐시에는 오직 Cache Miss가 발생했을 때, 읽기 요청을 처리하기 위해 데이터가 저장됩니다.
캐시 읽기 + 쓰기 전략 조합
읽기 전략과 쓰기 전략은 독립적으로 선택하는 것이 아니라, 서로 보완하는 형태로 함께 채택하여 최적의 성능과 데이터 일관성을 유지합니다.
-
Look Aside + Write Around 조합
- 가장 일반적으로 자주 쓰이는 조합
- 읽기(Read)
- 먼저 캐시를 확인하여(캐시 히트) 데이터가 있으면 캐시에서 반환합니다.
- 캐시에 없으면(캐시 미스) DB에서 데이터를 읽어오고, 그 데이터를 캐시에 저장하여 이후 요청 시 활용합니다.
- 쓰기(Write)
- 쓰기 작업은 데이터베이스에 직접 반영됩니다.
- 캐시는 업데이트하지 않으므로, 이후 조회 시에는 최신 데이터가 캐시에 반영되기 전까지 DB의 값을 조회하게 됩니다.
-
Read Through + Write Around 조합
- 읽기(Read)
- 데이터 조회 요청 시, 무조건 캐시에서 결과를 반환합니다.
- 캐시에 데이터가 없는 경우, 캐시가 대신 DB에 요청하여 데이터를 가져온 후 캐시에 저장하고, 그 값을 반환합니다.
- 쓰기(Write)
- 쓰기 작업은 DB에 바로 반영됩니다.
- 캐시는 별도로 갱신되지 않으므로, 쓰기 후 즉시 조회하면 캐시에는 기존 데이터가 남아 있을 수 있으며, 이후 조회 시 캐시 미스가 발생하면 최신 데이터가 DB에서 읽혀져 캐시가 갱신됩니다.
- 읽기(Read)
-
Read Through + Write Through 조합
- 읽기(Read)
- 데이터 조회 요청 시, 무조건 캐시에서 결과를 반환합니다.
- 캐시에 데이터가 없는 경우, 캐시가 대신 DB에 요청하여 데이터를 가져온 후 캐시에 저장하고, 그 값을 반환합니다.
- 쓰기(Write)
- 쓰기 작업은 우선 캐시에 데이터를 저장합니다.
- 동시에 캐시가 DB로 해당 데이터를 전달하여, DB에도 반영되도록 합니다.
- 읽기(Read)