1. 캐시란 무엇인가?
캐시는 자주 사용하는 데이터를 더 빠른 저장 공간에 임시로 보관하는 것이다.
일반적인 데이터 조회 흐름은 다음과 같다.
클라이언트 요청
↓
Spring Boot 서버
↓
데이터베이스 연결
↓
SQL 실행
↓
조회 결과 반환
캐시를 적용하면 먼저 캐시에 데이터가 있는지 확인한다.
클라이언트 요청
↓
캐시 조회
├─ 데이터가 있음 → 캐시 데이터 반환
└─ 데이터가 없음 → DB 조회 → 캐시에 저장 → 결과 반환
캐시에 데이터가 있는 경우를 Cache Hit, 없는 경우를 Cache Miss라고 한다.
Cache Hit
요청 → 캐시 조회 성공 → 바로 응답
DB를 조회하지 않기 때문에 응답 속도가 빠르고 DB 부하도 줄어든다.
Cache Miss
요청 → 캐시에 데이터 없음 → DB 조회 → 캐시 저장 → 응답
첫 번째 요청에서는 DB를 조회하지만, 이후 동일한 요청부터는 캐시를 사용할 수 있다.

2. 캐시가 필요한 이유
2.1 응답 속도를 줄일 수 있다
Redis 같은 인메모리 저장소는 데이터를 주로 메모리에서 처리한다.
DB에서 SQL을 실행하고 결과를 만드는 것보다 메모리에서 이미 준비된 값을 조회하는 것이 일반적으로 빠르다.
결과가 자주 바뀌지 않는다면 매번 SQL을 실행하는 것보다 결과를 캐시에 보관하는 편이 효율적이다.
2.2 데이터베이스 부하를 줄인다
사용자가 많아지면 동일한 SELECT 쿼리가 반복해서 실행될 수 있다.
사용자 A → 상품 1번 조회
사용자 B → 상품 1번 조회
사용자 C → 상품 1번 조회
사용자 D → 상품 1번 조회
캐시가 없다면 네 요청 모두 DB에 접근한다.
캐시가 있다면 첫 번째 요청만 DB에 접근하고, 나머지 요청은 캐시에서 처리할 수 있다.
2.3 반복 계산을 줄인다
캐시는 DB 조회 결과뿐만 아니라 계산 결과에도 사용할 수 있다.
- 통계 데이터
- 실시간 인기 게시글
- 상품별 평균 평점
- 사용자별 추천 결과
- 외부 API 호출 결과
- 복잡한 집계 결과
계산 비용이 큰 결과를 일정 시간 캐싱하면 동일한 작업을 반복하지 않아도 된다.
3. 모든 데이터에 캐시를 적용하면 좋을까?
그렇지 않다.
캐시는 성능을 높여주지만, 원본 데이터와 캐시 데이터가 서로 달라질 수 있다는 새로운 문제를 만든다.
다음과 같은 데이터가 캐시에 적합하다.
- 조회 횟수가 많은 데이터
- 수정 빈도가 낮은 데이터
- 조회 또는 계산 비용이 큰 데이터
- 잠시 오래된 값을 보여줘도 문제가 없는 데이터
- 여러 사용자가 공통으로 조회하는 데이터
반대로 다음과 같은 데이터는 신중하게 적용해야 한다.
- 실시간 정확성이 중요한 재고
- 결제 상태
- 계좌 잔액
- 사용자의 현재 포인트
- 수정이 매우 자주 발생하는 데이터
- 한 번만 조회되고 다시 사용되지 않는 데이터
캐시는 원본 데이터가 아니다. 성능을 위해 만들어 둔 임시 복사본이다.
따라서 데이터의 정확성이 성능보다 중요하다면 DB를 직접 조회하는 것이 더 안전할 수 있다.
4. 캐시의 유형
| 구분 | 설명 | 예시 |
| 로컬 캐시(Local Cache) | 애플리케이션 내부 메모리에 저장 | ConcurrentHashMap, Caffeine |
| 분산 캐시(Distributed Cache) | 외부 서버에 저장해 여러 인스턴스가 공유 | Redis, Memcached |
| 구분 | 로컬 캐시 | Redis |
| 저장 위치 | Spring Boot 서버 메모리 | 별도의 Redis 서버 |
| 네트워크 통신 | 없음 | 있음 |
| 서버 종료 | 캐시 삭제 | Redis가 살아 있으면 유지 |
| 여러 서버 공유 | 불가능 | 가능 |
| 운영 복잡도 | 낮음 | Redis 운영 필요 |
5. Spring Cache와 Redis 의존성 추가
Gradle 프로젝트라면 다음 의존성을 추가한다.

| 의존성 | 필요한 이유 |
| spring-boot-starter-cache | Spring의 캐시 애너테이션과 CacheManager를 사용하기 위해 필요 |
| caffeine | 캐시 데이터를 현재 Spring Boot 서버의 메모리에 저장하기 위해 필요 |
| spring-boot-starter-data-redis | Redis 연결과 RedisTemplate, RedisCacheManager 사용을 위해 필요 |
| jackson-datatype-jsr310 | LocalDateTime 같은 Java 날짜 타입을 JSON으로 변환하기 위해 필요 |
6. Docker로 Redis 실행하기
로컬에 Redis를 직접 설치하지 않고 Docker로 실행할 수 있다.
docker run --name redis-cache \
-p 6379:6379 \
-d redis:7
각 옵션의 의미는 다음과 같다.
| 옵션 | 의미 |
| --name redis-cache | 컨테이너 이름을 redis-cache로 지정 |
| -p 6379:6379 | 로컬 6379 포트와 컨테이너 6379 포트 연결 |
| -d | 백그라운드 실행 |
| redis:7 | Redis 7 이미지 사용 |
실행 중인 컨테이너는 다음 명령어로 확인한다.
docker ps
Redis CLI에 접속하려면 다음과 같이 입력한다.
docker exec -it redis-cache redis-cli
정상적으로 연결되었는지 확인한다.

7. Spring Boot에서 Redis 연결하기

application.yml에 Redis 연결 정보를 작성한다.
Spring Boot도 Docker 컨테이너에서 실행하고 Redis와 동일한 Docker 네트워크를 사용한다면 localhost가 아니라 Redis 서비스 이름을 사용해야 한다.
spring:
data:
redis:
host: redis
port: 6379
컨테이너 내부에서 localhost는 호스트 컴퓨터가 아니라 현재 컨테이너 자신을 의미한다.
8. Spring Cache 활성화하기
Spring Cache를 사용하려면 설정 클래스에 @EnableCaching을 추가한다.
@Configuration
@EnableCaching
public class CacheConfig {
}
이 애너테이션이 있어야 Spring이 @Cacheable, @CacheEvict 등의 애너테이션을 인식하고 캐시 기능을 적용한다.
9. RedisCacheManager와 RedisTemplate 설정하기
Redis를 Spring Cache 저장소로 사용하려면 RedisCacheManager를 설정할 수 있다.

설정의 의미
.serializeKeysWith(...)
Redis Key를 문자열로 저장한다.
.serializeValuesWith(...)
Java 객체를 JSON 형태로 변환해 저장한다.
.entryTtl(Duration.ofMinutes(10))
캐시 데이터를 10분 후 자동으로 만료시킨다.
.disableCachingNullValues()
조회 결과가 null인 경우 캐시에 저장하지 않는다.
.transactionAware()
트랜잭션이 완료되는 시점에 맞춰 캐시 작업을 처리하도록 한다.
10. @Cacheable: 캐시 조회 및 저장
가장 많이 사용하는 애너테이션은 @Cacheable이다.

처음 postId가 1인 게시글을 조회하면 다음 순서로 실행된다.
1. postCache에서 Key가 1인 데이터를 조회한다.
2. 캐시에 데이터가 없으므로 메서드를 실행한다.
3. DB에서 게시글을 조회한다.
4. 반환된 PostDto를 캐시에 저장한다.
5. PostDto를 사용자에게 반환한다.
같은 게시글을 다시 조회하면 다음과 같이 동작한다.
1. postCache에서 Key가 1인 데이터를 조회한다.
2. 캐시에 PostDto가 존재한다.
3. 메서드를 실행하지 않는다.
4. DB도 조회하지 않는다.
5. 캐시의 PostDto를 바로 반환한다.
여기서 가장 중요한 부분은 다음과 같다.
@Cacheable(value = "postCache", key = "#postId")
- postCache: 캐시 공간의 이름
- #postId: 캐시에서 데이터를 구분하는 Key
- 메서드 반환값 PostDto: 실제로 캐시에 저장되는 Value
즉, postId만 저장되는 것이 아니다.
Key = postId
Value = 메서드가 반환한 PostDto 전체
개념적으로는 다음과 같은 형태다.
postCache::1 → PostDto(id=1, title="Redis 정리", content="...")
postCache::2 → PostDto(id=2, title="JPA 정리", content="...")
11. @CachePut: 캐시 갱신
@CachePut은 캐시에 데이터가 있더라도 항상 메서드를 실행한 다음 반환값을 캐시에 저장한다.

동작은 다음과 같다.
게시글 수정 요청
↓
DB 데이터 수정
↓
수정된 PostDto 반환
↓
기존 캐시를 새로운 PostDto로 교체
| 애너테이션 | 실행 여부 | 목적 |
| @Cacheable | 캐시가 있으면 실행하지 않음 | 조회 최적화 |
| @CachePut | 항상 실행함 | 캐시 갱신 |
12. @CacheEvict: 캐시 삭제
데이터가 수정되거나 삭제되면 기존 캐시가 오래된 데이터가 될 수 있다.
이때 @CacheEvict를 사용해 캐시를 삭제한다.

메서드가 정상적으로 완료되면 다음 캐시가 삭제된다.
postCache::1
이후 1번 게시글을 다시 조회하면 Cache Miss가 발생하므로 DB를 다시 조회한다.
캐시 전체 삭제
@CacheEvict(
value = "postCache",
allEntries = true
)
public void clearPostCache() {
}
allEntries = true를 사용하면 postCache에 들어 있는 모든 데이터를 삭제한다.
전체 삭제는 비용이 클 수 있으므로 필요한 상황에서만 사용해야 한다.
13. Spring Cache 사용 시 주의할 점
13.1 같은 클래스 내부 호출 문제
Spring Cache는 일반적으로 프록시 방식으로 작동한다.
외부 객체가 캐시 애너테이션이 붙은 메서드를 호출해야 프록시가 해당 요청을 가로채 캐시 기능을 적용할 수 있다.
다음과 같은 내부 호출에서는 캐시가 적용되지 않을 수 있다.
@Service
public class PostService {
public void process(Long postId) {
getPost(postId);
}
@Cacheable(value = "postCache", key = "#postId")
public PostDto getPost(Long postId) {
// DB 조회
}
}
process()가 같은 객체 안에서 getPost()를 직접 호출하기 때문에 Spring 프록시를 거치지 않는다.
필요하다면 캐시 조회 책임을 별도의 서비스로 분리하는 편이 좋다.
@Service
@RequiredArgsConstructor
public class PostFacade {
private final PostCacheService postCacheService;
public void process(Long postId) {
PostDto post = postCacheService.getPost(postId);
}
}
13.2 동일한 캐시 이름에 서로 다른 타입 저장 금지
다음 두 메서드가 같은 캐시 이름과 같은 Key를 사용하면 문제가 발생할 수 있다.
@Cacheable(value = "postCache", key = "#postId")
public PostDto getPost(Long postId) {
// ...
}
@Cacheable(value = "postCache", key = "#postId")
public PostSummaryDto getPostSummary(Long postId) {
// ...
}
두 메서드 모두 다음 Key를 사용할 수 있다.
postCache::1
하지만 저장되는 Value 타입은 서로 다르다.
PostDto
PostSummaryDto
역직렬화 오류나 잘못된 데이터 반환이 발생할 수 있으므로 캐시 이름을 구분해야 한다.
@Cacheable(value = "postDetailCache", key = "#postId")
@Cacheable(value = "postSummaryCache", key = "#postId")
14. 캐시 Key 네이밍 규칙
⚙️ 캐시의 Key는 “데이터 식별자이자 캐시를 구분하는 핵심 요소”다.
잘못 설계하면 캐시 충돌, 갱신 누락, 모니터링 불편이 발생한다.
기본 규칙
- 고유하고 일관된 형식으로 구성 (도메인:식별자)
- 복합 조건은 파라미터 포함 (search:{keyword}:{page})
- Redis에서는 prefix로 분류 관리 (user:*, post:*)
실무 팁
- Key를 명확히 구분하면 모니터링, 삭제, 장애 복구가 쉬워짐
- 구분자는 : 또는 :: (Spring 기본 cacheName::key)
- 운영 환경에서는 Key 길이를 줄이되 의미는 명확히
15. Redis란 무엇인가?
Redis는 데이터를 메모리 중심으로 처리하는 Key-Value 기반 데이터 저장소다.
관계형 데이터베이스처럼 테이블과 행으로 데이터를 관리하지 않는다.
Redis에서는 다음과 같이 Key와 Value로 저장한다.
Key Value
member:1:username smith
member:1:email smith@gmail.com
Redis는 단순 문자열뿐만 아니라 여러 자료구조를 지원한다.
| 자료구조 | 특징 | 활용 사례 |
| String | 하나의 Key에 하나의 값 | 캐시, 인증번호, 토큰 |
| Hash | 하나의 Key 안에 여러 필드 | 회원 정보, 상품 정보 |
| List | 값의 순서를 유지 | 작업 대기열, 최근 기록 |
| Set | 중복 없는 값 집합 | 태그, 좋아요 사용자 |
| Sorted Set | 점수를 기준으로 정렬 | 랭킹, 인기 게시글 |
| Stream | 이벤트를 순서대로 기록 | 이벤트 처리, 메시지 전달 |
16. Redis 기본 명령어
String 저장
SET username "smith"
조회한다.
GET username
결과는 다음과 같다.
"smith"
같은 Key에 새로운 값을 저장하면 기존 값이 교체된다.
SET username "ravi"
SET username "Alex"
GET username
결과는 가장 최근에 저장한 값이다.
"Alex"
Redis의 String은 기본적으로 하나의 Key에 하나의 Value를 저장하기 때문이다.
username → ravi
여기에 viva를 다시 저장하면 새로운 데이터가 추가되는 것이 아니라 기존 값이 교체된다.
username → Alex
만료 시간 설정
SET verification:email:1 "839201" EX 300
EX 300은 해당 데이터를 300초 후 삭제하라는 의미다.
남은 만료 시간을 확인한다.
TTL verification:email:1
Key 삭제
DEL username
Key 존재 확인
EXISTS username
17. RedisTemplate 사용하기
Spring Cache는 애너테이션을 이용해 메서드 반환값을 캐싱할 때 편리하다.
반면 Redis의 자료구조를 직접 사용하고 싶다면 RedisTemplate을 사용한다.

각 설정의 역할
template.setConnectionFactory(connectionFactory);
Redis 연결 정보를 RedisTemplate에 전달한다.
template.setKeySerializer(new StringRedisSerializer());
Redis Key를 사람이 읽을 수 있는 문자열로 저장한다.
template.setHashKeySerializer(new StringRedisSerializer());
Hash 내부의 필드 이름을 문자열로 저장한다.
template.setValueSerializer(
new GenericJackson2JsonRedisSerializer()
);
일반 Value를 JSON 형태로 변환해 저장한다.
template.setHashValueSerializer(
new GenericJackson2JsonRedisSerializer()
);
Hash 내부 Value를 JSON 형태로 변환해 저장한다.
template.afterPropertiesSet();
설정이 완료된 RedisTemplate을 초기화한다.
18. RedisTemplate으로 값 저장하기
String 형태로 저장

조회한다.

삭제한다.

Sorted Set으로 랭킹 저장

상위 N개 게시글을 조회한다.

게시글 조회수를 증가시킬 때마다 해당 게시글의 랭킹 점수를 1씩 증가시킨다.

19. Spring Cache와 RedisTemplate의 차이
둘 다 Redis를 사용할 수 있지만 목적이 다르다.
| 구분 | Spring Cache | RedisTemplate |
| 사용 방식 | 애너테이션 중심 | 명령을 직접 실행 |
| 주요 목적 | 메서드 반환값 캐싱 | Redis 자료구조 직접 사용 |
| 난이도 | 비교적 간단 | 직접 설계해야 함 |
| TTL | CacheManager 설정 | 저장할 때 직접 지정 가능 |
| 자료구조 활용 | 제한적 | String, Hash, Set, ZSet 등 활용 |
| 사용 사례 | 상품 상세 조회 캐싱 | 랭킹, 인증번호, 분산 락 |
상품 상세 조회처럼 메서드 결과를 저장하고 싶다면 Spring Cache가 편리하다.
@Cacheable(value = "productCache", key = "#productId")
public ProductDto getProduct(Long productId) {
// ...
}
Sorted Set을 사용한 랭킹처럼 Redis 기능을 직접 활용하려면 RedisTemplate이 적합하다.
redisTemplate.opsForZSet()
.incrementScore("ranking:products", productId, 1);
20. Redis 캐시 설계 원칙
원칙 1. Key만 보고도 의미를 알 수 있게 만든다
다음과 같은 Key는 의미를 알기 어렵다.
1
product
data
cache1
도메인과 데이터의 종류를 포함해 작성하는 것이 좋다.
product:detail:1
member:profile:10
post:summary:27
ranking:posts
verification:email:smith@gmail.com
일반적인 Key 설계 형식은 다음과 같다.
서비스:도메인:데이터종류:식별자
운영 환경까지 구분하려면 다음처럼 만들 수 있다.
dev:shop:product:detail:1
prod:shop:product:detail:1
이렇게 하면 개발 Redis와 운영 Redis를 실수로 함께 사용할 때 Key가 충돌하는 문제를 줄일 수 있다.
원칙 2. 대부분의 캐시에 TTL을 설정한다
TTL이 없는 캐시는 직접 삭제하지 않는 이상 계속 남아 있을 수 있다.
redisTemplate.opsForValue()
.set(
"product:detail:1",
productDto,
Duration.ofMinutes(10)
);
데이터 특성에 따라 TTL을 다르게 설정해야 한다.
| 데이터 | TTL 예시 |
| 이메일 인증번호 | 3~5분 |
| 상품 상세 정보 | 5~30분 |
| 인기 게시글 목록 | 1~5분 |
| 사용자 프로필 | 10~60분 |
| 외부 API 응답 | API 특성에 따라 결정 |
TTL에는 절대적인 정답이 없다.
다음 두 가지를 기준으로 결정해야 한다.
- 데이터가 얼마나 자주 변경되는가?
- 오래된 데이터를 얼마나 오래 허용할 수 있는가?
원칙 3. 캐시 무효화 방법을 먼저 설계한다
캐시에 저장하는 것보다 어려운 작업은 언제 캐시를 삭제할지 정하는 것이다.
예를 들어 상품 가격이 다음과 같이 변경되었다고 가정한다면
DB 가격: 10,000원 → 8,000원
캐시 가격: 10,000원
DB만 수정하고 캐시를 그대로 두면 사용자는 계속 10,000원을 보게 된다.
가장 단순한 해결 방법은 데이터 수정 시 캐시를 삭제하는 것이다.
@Transactional
@CacheEvict(
value = "productCache",
key = "#productId"
)
public void updateProduct(
Long productId,
UpdateProductRequest request
) {
Product product = productRepository.findById(productId)
.orElseThrow();
product.update(request.name(), request.price());
}
이후 조회 요청이 들어오면 Cache Miss가 발생하고, 변경된 데이터를 DB에서 다시 조회한다.
DB 데이터 수정
↓
관련 캐시 삭제
↓
다음 조회에서 Cache Miss
↓
DB의 최신 데이터 조회
↓
최신 데이터를 캐시에 저장
이 방식은 캐시 값을 직접 갱신하는 것보다 구현이 단순하고 실수를 줄이기 쉽다.
원칙 4. 캐시는 없어도 서비스가 동작하도록 설계한다
Redis는 성능을 높이기 위한 보조 저장소다.
Redis에 장애가 발생했다고 해서 상품 조회 서비스 전체가 반드시 중단되어야 하는 것은 아니다.
이상적인 조회 흐름은 다음과 같다.
Redis 정상
→ 캐시 조회
→ 빠르게 응답
Redis 장애
→ Redis 조회 실패
→ DB 직접 조회
→ 사용자에게 정상 응답
물론 모든 상황에서 무조건 예외를 무시해야 하는 것은 아니다.
분산 락, 중복 요청 방지, 세션 저장처럼 Redis 데이터 자체가 서비스의 정확성에 영향을 주는 경우에는 장애 대응 정책을 별도로 설계해야 한다.
Redis가 단순 캐시인지, 서비스 동작에 필수적인 저장소인지 먼저 구분해야 한다.
원칙 5. 너무 큰 데이터를 하나의 Value로 저장하지 않는다
다음과 같은 데이터를 하나의 Key에 전부 저장하면 문제가 될 수 있다.
Key = products:all
Value = 상품 100만 개 전체 목록
하나의 Value가 너무 크면 다음 문제가 발생한다.
- 직렬화와 역직렬화 시간이 증가한다.
- 네트워크 전송 비용이 커진다.
- Redis 메모리를 많이 차지한다.
- Key 삭제 시 부하가 발생할 수 있다.
- 일부 데이터만 필요해도 전체 데이터를 가져와야 한다.
상품 전체가 아니라 필요한 단위로 나누는 편이 좋다.
product:detail:1
product:detail:2
product:detail:3
목록 캐시도 무한정 저장하지 말고 페이지나 조건을 포함해 제한적으로 구성해야 한다.
product:list:category:10:page:0
product:list:category:10:page:1
21. 캐시 관통 문제
존재하지 않는 데이터를 반복해서 요청하면 매번 Cache Miss가 발생한다.
존재하지 않는 상품 999999번 요청
↓
캐시에 없음
↓
DB에도 없음
↓
다시 같은 요청
↓
또 DB 조회
공격이나 잘못된 요청이 반복되면 DB에 부하가 집중될 수 있다. 이를 Cache Penetration, 즉 캐시 관통이라고 한다.
해결 방법으로는 다음이 있다.
- 잘못된 ID 요청 검증
- 존재하지 않는 결과도 짧은 시간 캐싱
- Rate Limit 적용
- Bloom Filter 사용
존재하지 않는 결과를 짧게 캐싱하는 예시는 다음과 같다.
product:detail:999999 → NOT_FOUND
TTL → 30초
다만 null을 캐싱하면 실제 데이터가 생성된 뒤에도 잠시 조회되지 않을 수 있으므로 TTL을 짧게 설정해야 한다.
22. 캐시 스탬피드 문제
인기 상품의 캐시가 만료되는 순간 수천 개의 요청이 동시에 들어올 수 있다.
인기 상품 캐시 만료
↓
요청 1 → Cache Miss → DB 조회
요청 2 → Cache Miss → DB 조회
요청 3 → Cache Miss → DB 조회
요청 4 → Cache Miss → DB 조회
...
모든 요청이 동시에 DB를 조회하면 캐시를 사용했음에도 순간적으로 DB 부하가 폭증한다.
이를 Cache Stampede라고 한다.
대표적인 대응 방법은 다음과 같다.
- 캐시 재생성 구간에 락 사용
- TTL에 임의의 시간 차이 추가
- 캐시 만료 전에 미리 갱신
- 오래된 캐시를 잠시 반환하면서 백그라운드 갱신
- 인기 데이터는 더 긴 TTL 사용
TTL을 완전히 동일하게 설정하면 많은 Key가 동시에 만료될 수 있다.
long randomSeconds = ThreadLocalRandom.current()
.nextLong(0, 60);
Duration ttl = Duration.ofMinutes(10)
.plusSeconds(randomSeconds);
이렇게 TTL에 임의의 시간을 추가하면 캐시 만료 시점을 분산할 수 있다.
23. 직렬화 방식은 처음부터 통일한다
Java 객체는 Redis에 그대로 저장되지 않는다. 저장 가능한 형태로 변환하는 직렬화 과정이 필요하다.
예를 들어 PostDto는 다음과 같은 JSON으로 저장될 수 있다.
{
"id": 1,
"title": "Redis 캐싱",
"content": "Redis 캐시에 대한 내용입니다."
}
애플리케이션이 이 값을 다시 사용할 때는 JSON을 PostDto로 변환하는 역직렬화 과정을 거친다.
직렬화 방식을 중간에 변경하면 기존 캐시를 읽지 못할 수 있다.
기존 캐시 → JDK 직렬화
새 코드 → JSON 역직렬화
결과 → 역직렬화 실패
변경이 필요하다면 다음과 같은 방법을 고려할 수 있다.
- 배포 전 기존 캐시 삭제
- Key에 버전 포함
- 이전 형식과 새로운 형식을 모두 읽을 수 있도록 점진적 전환
v1:product:detail:1
v2:product:detail:1
24. 민감한 정보는 함부로 캐싱하지 않는다
Redis에도 개인정보나 인증 정보가 저장될 수 있다.
다음 정보는 특히 주의해야 한다.
- 비밀번호
- 카드 정보
- 주민등록번호
- Access Token과 Refresh Token
- 개인 주소
- 전화번호
- 인증번호
민감한 데이터를 저장해야 한다면 다음 항목을 검토해야 한다.
- 반드시 필요한 데이터만 저장하는가?
- TTL이 설정되어 있는가?
- Redis 외부 접근이 차단되어 있는가?
- 네트워크 암호화가 적용되어 있는가?
- 로그에 Value가 노출되지 않는가?
- 운영 Redis에 인증이 설정되어 있는가?
캐시는 임시 데이터라고 해서 보안 책임이 사라지는 것이 아니다.
25. 캐시 도입 전 체크리스트
캐시를 적용하기 전에 다음 질문에 답할 수 있어야 한다.
데이터 선정
- 정말 자주 조회되는 데이터인가?
- DB 조회 비용이 큰가?
- 수정 횟수보다 조회 횟수가 훨씬 많은가?
- 잠시 오래된 데이터가 보여도 괜찮은가?
Key 설계
- Key만 보고 데이터의 의미를 알 수 있는가?
- 서로 다른 데이터가 같은 Key를 사용하지 않는가?
- 개발 환경과 운영 환경의 Key가 구분되는가?
- 사용자 입력을 그대로 Key에 사용하면서 충돌이 발생하지 않는가?
TTL 설계
- TTL이 설정되어 있는가?
- 데이터의 변경 주기에 맞는 TTL인가?
- 많은 Key가 동시에 만료되지 않는가?
- 존재하지 않는 데이터의 캐시 TTL은 충분히 짧은가?
데이터 정합성
- DB 데이터가 수정되면 어떤 캐시를 삭제해야 하는가?
- 관련 목록 캐시도 함께 삭제해야 하는가?
- 트랜잭션 실패 시 캐시가 먼저 변경될 가능성은 없는가?
- 캐시 삭제에 실패하면 어떻게 복구할 것인가?
장애 대응
- Redis가 중단되어도 DB 조회로 전환할 수 있는가?
- Redis 장애가 전체 서비스 장애로 이어지지 않는가?
- 타임아웃이 너무 길게 설정되어 있지 않은가?
- Redis 메모리가 부족할 때 제거 정책이 적절한가?