개발 기록
> 캐싱에 대해 알아보자! 캐시는 언제 써야 할까? 본문

●
1. 캐싱이란?
캐싱(caching) 은 자주 사용되는 데이터나 계산된 결과값을 미리 저장해 두고, 이후 요청 시에 이를 재사용하는 기법이다. 이를 통해 반복적인 데이터 접근이나 계산을 피하고, 시스템의 응답 속도를 향상 시킬 수 있다.
※ 캐시의 종류와 그 예시
① CPU 캐시: L1, L2, L3 캐시
② 운영체제 캐시: 페이지 캐시, 디스크 캐시
③ 데이터베이스 캐시: MemCached, Redis
ex. 자주 조회되는 상품 목록을 캐시에 저장하고, 쿼리 최적화와 DBMS 성능 튜닝을 통해 캐시 적중률을 높인다.
④ API 캐시
⑤ HTTP 캐시: CDN, 브라우저 캐시
ex. CDN: CDN 서버는 자주 요청되는 웹 콘텐츠( CSS, JavaScript 파일 등)를 캐시하여 사용자에게 가까운 서버에서 제공
ex. 브라우저 캐시: 이미지가 브라우저 캐시에 저장되어 재방문 시 빠르게 로드
⑥ DNS 캐시: 사용자가 자주 방문하는 웹사이트의 도메인 이름을 캐시하여 재방문 시 빠르게 IP 주소를 해석
⑦ 마이크로서비스 캐시
캐싱은 데이터 접근 속도를 크게 향상시킬 수 있는 유용한 도구이지만, 모든 상황에서 적합하지는 않는다. 캐싱하기 적합한 데이터를 선택하고, 실시간성이 요구되는 데이터는 캐싱하지 않는 것이 중요하다. 시스템 특성에 맞게 캐시 전략을 설계 해야한다!!
●
2. 캐싱은 언제 사용하는게 좋을까?
(1) 캐싱하기 적합한 데이터
① 반복적이고 동일한 결과가 나오는 기능의 결과값 (ex. 자주 조회되는 사용자 프로필 정보, 인기 상품 목록 등)
② 자주 조회되면서 업데이트가 자주 발생하지 않는 데이터 (ex. 날짜별 날씨 정보, 뉴스 기사 등)
③ 입력값과 출력값이 일정한 데이터 (ex. 특정 수학적 계산 결과, 고정된 데이터 조회)
④ 작업 시간이 오래 걸리거나 서버에 부담을 주는 작업 (ex. 외부 API 호출 결과, 복잡한 데이터베이스 쿼리 결과 등)
⑤ Hit 확률이 높을 경우 (ex. 특정 키 값이 자주 호출되는 데이터, 예를 들어 인기 검색어 순위 등)
(2) 캐싱하기 적합하지 않은 경우
① 실시간으로 변화하고 민감한 데이터 (ex. 결제 상태, 쿠폰 적용, 주식 거래 데이터 등)
② 캐시 서버가 따로 없을 경우 캐싱을 자주 하는 것이 오히려 애플리케이션 서버에 부담을 줘서 서버 다운이나 서비스 중단을 일으킬 수 있다.
③ 데이터가 자주 변경되거나 실시간성이 중요한 경우, 긴 캐싱 시간은 데이터의 신뢰성을 떨어뜨릴 수 있다.
●
3. 캐시 사용 시 고려해야 할 주요 사항
▶ Cache TTL (Time To Live, 캐시에 저장된 데이터의 유효기간)
- 각 캐시 항목에 만료일자를 설정하여 일정 기간 동안만 데이터를 유지하고, 기간이 지나면 삭제되도록 해야한다. 또한 TTL이 만료된 데이터는 다시 요청될 때 재캐싱되도록 한다. 이를 통해 데이터가 지속적으로 최신 상태를 유지할 수 있다.
* 데이터를 무기한 저장하는 것은 메모리 자원 낭비 이다. 특히 미사용 데이터나 유효하지 않은 데이터가 계속해서 메모리를 차지하면, 중요한 데이터가 캐시에 저장되지 못할 수 있다.
▶ 메모리 관리
- 캐시는 메모리 내에 데이터를 저장하기 때문에, 메모리 관리가 중요하다.
- 캐시에 데이터가 메모리에 가득차게 되면, 정의한 삭제 알고리즘(ex. LRU, LFU, FIFO)에 따라 데이터를 삭제하여 새로운 데이터를 저장할 공간을 만든다.
- Java GC(Garbage Colletion)가 동작할 때 캐시된 데이터가 너무 많이 쌓이면 GC가 메모리 해제를 제대로 수행하지 못해 제거 대상이 되는 캐시 데이터를 제거하지 못한다. 이는 즉 메모리 부족상황을 초래하여 OOM(Out Of Memory Error) 오류가 발생할 수 있다. 그러므로 캐시가 사용하는 메모리와 GC가 해제할 수 있는 여유 메모리를 충분히 고려하여, 캐시가 메모리를 효율적으로 사용하고, GC가 메모리를 효과적으로 관리하도록 설계 해야한다.
▼ 캐시 삭제 전략 (아래)
※ 참고(삭제 알고리즘)
LRU (Least Recently Used): 가장 오랫동안 사용되지 않은 데이터를 삭제
LFU (Least Frequently Used): 사용 빈도가 가장 낮은 데이터를 삭제
FIFO (First In, First Out): 가장 먼저 들어온 데이터를 가장 먼저 삭제
▼ 캐시와 메모리 간의 데이터 관리방법 (아래)
1. Look-Aside (Lazy Loading): 데이터를 요청 했을 때 데이터를 캐시에 로드하는 캐싱 전략
(캐시에서 우선 조회 -> 조회 실패시 : DB에서 조회 후 그 결과를 캐시한 후에 데이터를 내보냄)
장점: 캐시의 적중률을 높일 수 있으며, 시스템 전체적인 성능을 향상시킬 수 있다.
단점: 초기에는 캐시에 데이터가 없을 수 있고, 이로 인해 일시적인 지연이 발생할 수 있다.
2. Write-Back: 캐시 메모리에서의 데이터 수정을 메인 메모리에 즉시 반영하지 않고, 캐시 내부에 변경된 데이터를 유지하는 방식
(ex. 데이터를 캐싱해뒀다가 특정 시점마다 한번씩 캐시 내 데이터를 DB에 insert 및 update 하는 방법)
장점: 쓰기 연산의 성능을 향상시킬 수 있다.
단점: 데이터를 일정 기간 유지하고 있는 동안 서버 장애 상황에서 데이터가 손실될 수 있다. 또한 메모리와 캐시 간의 데이터 일관성을 관리하기 위한 별도의 제어 로직이 필요하다.
▶ 실제 데이터와 캐시 데이터와의 정합성
- 캐시된 데이터가 실제 데이터와 일치하지 않을 때 발생하는 문제
(ex. 데이터베이스에서 특정 링크를 삭제했지만 캐시에는 여전히 저장되어 있다면, 사용자가 이 링크에 접근할 때 HTTP 404 오류 발생)
- 해결방법: 캐시 무효화, Write-through 또는 Write-behind(변경 사항 반영시 캐시 동시 업데이트)
▶ 인풋과 아웃풋의 의존 관계
- 요청에 필요한 모든 데이터가 캐시에 있어야 한다. 일부 데이터만 캐시에 있고 나머지는 없으면, 불완전한 요청 처리, 성능저하, 일관성 문제가 발생한다.
- 해결 방법: 요청에 필요한 전체 데이터 캐싱, 업데이트 관리, 프리로드(preload)
●
4. 캐시 히트율과 캐싱 전략
캐시 히트 확률은 캐시에 저장된 데이터에 대한 요청이 얼마나 자주 성공적으로 이루어지는지를 나타내는 비율을 말한다. 쉽게 말해, 캐시 히트 확률은 캐시에서 데이터를 찾을 수 있는 빈도를 측정하는 지표이다.

- 캐시 히트(Cache Hit): 요청된 데이터가 캐시에 이미 저장되어 있어, 캐시에서 직접 데이터를 반환할 수 있는 경우.
- 캐시 미스(Cache Miss): 요청된 데이터가 캐시에 존재하지 않아, 원래 데이터 소스(DB, API 등)에서 데이터를 가져와야 하는 경우.
(1) 캐시 히트 확률을 높이는 방법
- 적절한 데이터 선택: 자주 접근하는 데이터나 변경 빈도가 낮은 데이터를 캐싱한다.
- 적절한 캐시 크기 설정: 충분한 메모리를 할당하여 자주 사용되는 데이터가 캐시에 남아 있도록 한다.
- 너무 작은 캐시는 캐시 미스가 자주 발생하게 하지만, 너무 큰 캐시는 적중률을 높이지만 비용 문제가 발생할 수 있다.
- 효과적인 캐시 전략: LRU(Least Recently Used), LFU(Least Frequently Used) 등의 전략을 사용하여 불필요한 데이터를 제거하고 중요한 데이터를 유지한다.
- 캐시 최적화 기법 사용: 동적 캐시 크기 조정, 다중 레벨 캐시 등
- 적절한 캐시 매핑 방식 선택: 캐시 충돌을 최소화하고 캐시 히트를 증가시킨다
- 세트 연관 방식은 캐시 충돌 가능성이 줄어들어 적중률을 높일 수 있고, 직접 매핑은 간단하고 저렴하지만 같은 세트에 다수의 데이터가 매핑될 경우 충돌 가능성이 높고 캐시 미스가 발생할 수 있다.
* 참고
- Set Associative (세트 연관): 캐시를 여러 개의 세트로 나누고, 각 세트는 몇 개의 블록을 저장할 수 있다.
- Direct Mapping (직접 매핑): 메모리의 주소를 특정 세트에 직접 매핑하여 그 세트 안에서 태그를 사용하여 데이터 블록을 식별하는 방식
캐시를 언제, 어떻게 사용하면 좋을 지 알아봤다. 그렇다면 어떤 캐시 저장소를 선택해야 할까?
●
5. 로컬 캐시 vs 글로벌 캐시 vs 분산 캐시

| Local Cache | Global Cache | |
| 저장소 | 개별 시스템 또는 서비스 내에 존재하는 캐시 메모리 | 여러 시스템이 공유하는 중앙화된 캐시 메모리 |
| 목적 | 주로 개별 사용자 또는 프로세스의 성능 향상을 목적으로 함 | 여러 시스템이 공통으로 접근 가능하며, 전역적인 데이터 공유를 목적으로 함 |
| 장점 | - 데이터 접근 속도가 빠름 - 각 시스템에서 데이터 독립적으로 관리 가능 |
- 여러 시스템 간 데이터 공유 용이 - 데이터 일관성 유지 가능 |
| 단점 | - 다른 시스템과의 데이터 공유 어려움 - 시스템 간 데이터 불일치 문제 발생 가능 |
- 네트워크 트래픽을 사용해야 해서 로컬 캐시보다 느림 - 전역 캐시의 관리와 동기화가 복잡함 |
| ex | Ehcache, Caffeine 등 | Redis |
※ 보통 다른 서비스에 전혀 영향을 받지 않는 데이터이고 캐싱이 필요하다면 Local Cache를 사용하고 전체적인 서비스가 공유되어야 되는 데이터 이지만 캐싱이 필요하다고 하면 Global Cache를 사용한다.
| 분산 캐시 | |
| 저장소 | 여러 노드에 분산된 캐시 메모리 |
| 목적 | 캐시 데이터를 여러 노드에 분산하여 확장성을 높이고 성능을 개선하는 것을 목적으로 함(=부하 분산) |
| 특징 | - 데이터가 여러 노드에 분산되어 저장되며, 보통 각 노드는 일부 데이터의 캐시 복제본을 가지고 있음 - 캐시 데이터는 일관된 해싱에 의해서 각 분산 캐시 노드별로 분할되어 저장된다. 이 경우 각 노드는 작은 캐시 데이터을 가지고 있으며, 그런 다음 원본으로 이동하기 전에 다른 노드에 데이터 요청을 전송한다. |
| 장점 | - 높은 확장성과 성능 (요청 풀에 노드를 추가하는 것만으로 캐시 공간을 쉽게 늘릴 수 있음) - 부하 분산이 가능하며 단일 장애점이 없 |
| 단점 | - 노드 증가에 따른 네트워크 오버헤드와 복잡성 증가 가능성 - 데이터 일관성 유지 어려움 (두 노드에서 동시에 동일한 캐시의 동일한 데이터에 대한 변경을 수행할 경우, 두 노드 사이에 데이터 불일치가 발생할수 있음) - 누락된 캐시 노드 |
| ex | Memcached, Redis |
※ 분산 캐시와 글로벌 캐시는 비슷한 개념이지만 약간의 차이가 있다. 글로벌 캐시는 모든 노드가 동일한 단일 캐시 공간을 사용하고 분산 캐시의 각 노드는 캐시된 데이터의 일부를 저장하고 있다.
참고
'DIVE' 카테고리의 다른 글
| >함수형 프로그래밍은 무엇인가? (0) | 2024.02.26 |
|---|---|
| > [Spring] Spring 은 요청을 어떻게 처리 해주고 있는 걸까? (5) HttpMessageConvert (0) | 2023.12.12 |
| > [Spring] Spring 은 요청을 어떻게 처리 해주고 있는 걸까? (4) Convert (0) | 2023.12.08 |
| > [Spring] Spring 은 요청을 어떻게 처리 해주고 있는 걸까? (3) Request, Response Log (0) | 2023.12.08 |
| > [Spring] Spring 은 요청을 어떻게 처리 해주고 있는 걸까? (2) HandlerMethodArgumentResolver : Request Value Binding (0) | 2023.12.07 |