TIL - 20260623

juni·2026년 6월 23일

TIL

목록 보기
385/468

0623 백엔드 실무 심화 (3/N): Redis 캐싱과 성능 최적화


✅ 1. 캐싱이란 무엇인가?

  • 캐싱(Caching)은 자주 사용되는 데이터를 더 빠르게 가져오기 위해 임시 저장소에 보관하는 방식입니다.
  • 매번 DB를 조회하거나 외부 API를 호출하지 않고, 한 번 가져온 결과를 일정 시간 동안 저장해두고 재사용합니다.
  • 웹서비스에서는 상품 목록, 배너 목록, 카테고리, 공지사항, 설정값, 메인 페이지 데이터, 통계 결과처럼 자주 조회되는 데이터를 캐싱할 수 있습니다.

➕ 1-1. 캐싱이 필요한 이유

  • 응답 속도 개선

    • DB 조회보다 메모리 기반 캐시 조회가 훨씬 빠릅니다.
  • DB 부하 감소

    • 자주 조회되는 데이터를 캐시에서 처리하면 DB 요청 수가 줄어듭니다.
  • 외부 API 비용 절감

    • 외부 API 호출 결과를 캐싱하면 호출 횟수와 비용을 줄일 수 있습니다.
  • 트래픽 증가 대응

    • 광고 유입, 이벤트 페이지, 사전예약 페이지처럼 순간 트래픽이 몰릴 때 캐시가 큰 도움이 됩니다.
캐시 없음:
사용자 1,000명 접속
  ↓
DB 조회 1,000번

캐시 있음:
첫 사용자만 DB 조회
  ↓
나머지 사용자는 캐시 조회

✅ 2. Redis란 무엇인가?

  • Redis는 메모리 기반의 Key-Value 저장소입니다.
  • 데이터베이스처럼 디스크 중심으로 저장하는 것이 아니라, 주로 메모리에 데이터를 저장하기 때문에 매우 빠릅니다.
  • 백엔드 실무에서는 캐시, 세션 저장소, Queue, Rate Limiting, 분산 Lock 등에 자주 사용됩니다.

➕ 2-1. Redis를 사용하는 대표 목적

용도설명
캐싱자주 조회되는 데이터를 임시 저장
세션 저장로그인 세션 정보를 저장
QueueBullMQ 같은 작업 대기열 저장소
Rate LimitingAPI 요청 횟수 제한
분산 Lock동시에 하나의 작업만 처리하도록 제어
임시 데이터 저장인증번호, 토큰, 일회성 값 저장

✅ 3. 캐싱이 적합한 데이터

  • 모든 데이터를 캐싱하는 것이 좋은 것은 아닙니다.
  • 자주 조회되고, 자주 바뀌지 않으며, 약간 오래된 데이터가 보여도 큰 문제가 없는 데이터가 캐싱에 적합합니다.

➕ 3-1. 캐싱하기 좋은 데이터

  • 메인 페이지 배너 목록
  • 상품 카테고리 목록
  • 인기 상품 목록
  • 이벤트 페이지 설정
  • FAQ 목록
  • 공지사항 목록
  • 요금제 목록
  • 통신사 정책 요약
  • 광고 랜딩 페이지 데이터
  • 관리자 대시보드 통계 일부
  • 외부 API 조회 결과
예시:
메인 배너 목록은 자주 조회되지만 자주 바뀌지 않음
  ↓
캐싱하기 좋음

➕ 3-2. 캐싱에 조심해야 하는 데이터

  • 결제 상태
  • 주문 상태
  • 실시간 재고
  • 사용자 포인트
  • 관리자 권한
  • 개인정보
  • 인증번호
  • 로그인 토큰
  • 사전예약 선착순 카운트
주의:
정확성이 매우 중요한 데이터는 캐싱을 신중하게 적용해야 함
  • 캐시된 오래된 데이터가 사용자에게 보이면 문제가 되는 데이터는 캐시 만료, 무효화, DB 검증을 함께 설계해야 합니다.

✅ 4. 캐시의 기본 흐름

➕ 4-1. Cache Aside 패턴

  • 실무에서 가장 흔히 사용하는 캐싱 패턴입니다.
  • 먼저 캐시를 확인하고, 캐시에 없으면 DB를 조회한 뒤 캐시에 저장합니다.
1. 요청 들어옴
2. Redis 캐시 확인
3. 캐시에 있으면 캐시 데이터 반환
4. 캐시에 없으면 DB 조회
5. DB 결과를 Redis에 저장
6. 응답 반환

➕ 4-2. 흐름 예시

GET /api/banners/main
  ↓
Redis key: banners:main 확인
  ↓
있음: Redis 데이터 반환
없음: DB에서 배너 목록 조회
  ↓
Redis에 5분 저장
  ↓
응답 반환

✅ 5. Cache Hit과 Cache Miss

➕ 5-1. Cache Hit

  • 요청한 데이터가 캐시에 있는 상태입니다.
  • DB를 조회하지 않고 빠르게 응답할 수 있습니다.
Redis에 banners:main 있음
  ↓
DB 조회 없이 바로 반환

➕ 5-2. Cache Miss

  • 요청한 데이터가 캐시에 없는 상태입니다.
  • DB를 조회한 뒤 결과를 캐시에 저장합니다.
Redis에 banners:main 없음
  ↓
DB 조회
  ↓
Redis에 저장
  ↓
응답 반환

➕ 5-3. 캐시 적중률

  • 캐시 적중률(Cache Hit Rate)은 전체 요청 중 캐시에서 처리된 요청 비율입니다.
전체 요청 1000개
캐시 Hit 800개
  ↓
캐시 적중률 80%
  • 캐시 적중률이 높으면 DB 부하가 줄고 응답 속도가 좋아집니다.
  • 하지만 무조건 높다고 좋은 것은 아닙니다. 오래된 데이터를 보여주는 캐시라면 문제가 될 수 있습니다.

✅ 6. TTL

  • TTL(Time To Live)은 캐시 데이터가 얼마나 오래 유지될지 정하는 시간입니다.
  • TTL이 지나면 Redis에서 해당 데이터가 만료됩니다.

➕ 6-1. TTL 예시

메인 배너 목록:
5분 캐싱

카테고리 목록:
30분 캐싱

공지사항 목록:
10분 캐싱

외부 API 결과:
1분 캐싱

➕ 6-2. TTL을 짧게 잡아야 하는 데이터

  • 자주 바뀌는 데이터
  • 이벤트 상태
  • 가격/지원금 정보
  • 정책 정보
  • 관리자에서 수정 직후 반영되어야 하는 데이터

➕ 6-3. TTL을 길게 잡아도 되는 데이터

  • 변경이 거의 없는 카테고리
  • 고정 FAQ
  • 정적 설정값
  • 코드 테이블
  • 공개 이미지 URL 목록
TTL이 너무 짧으면:
캐시 효과가 작음

TTL이 너무 길면:
오래된 데이터가 계속 보일 수 있음

✅ 7. Redis Key 설계

  • Redis는 Key-Value 구조이기 때문에 Key 이름 설계가 중요합니다.
  • Key 이름이 엉망이면 나중에 삭제, 조회, 디버깅이 어려워집니다.

➕ 7-1. 좋은 Key 예시

banners:main
products:list:category:iphone
products:detail:123
faqs:list
settings:public
admin:dashboard:summary:2026-06-23

➕ 7-2. Key 설계 기준

  1. 서비스 영역을 앞에 붙인다.
  2. 데이터 종류를 구분한다.
  3. 파라미터 값을 포함한다.
  4. 너무 길거나 복잡하게 만들지 않는다.
  5. 개인정보를 key에 직접 넣지 않는다.
  6. 운영/개발 환경을 분리한다.
좋은 예시:
prod:products:detail:123

나쁜 예시:
01012345678_user_private_data

✅ 8. NestJS에서 Redis 캐시 사용 흐름

  • NestJS에서는 Redis 클라이언트를 직접 사용하거나, CacheModule을 사용할 수 있습니다.
  • 실무에서는 복잡한 캐시 무효화나 key 관리가 필요하면 Redis 클라이언트를 직접 감싼 CacheService를 만드는 방식이 관리하기 좋습니다.

➕ 8-1. CacheService 예시

@Injectable()
export class CacheService {
  constructor(private readonly redis: Redis) {}

  async get<T>(key: string): Promise<T | null> {
    const value = await this.redis.get(key);

    if (!value) {
      return null;
    }

    return JSON.parse(value) as T;
  }

  async set<T>(key: string, value: T, ttlSeconds: number) {
    await this.redis.set(key, JSON.stringify(value), 'EX', ttlSeconds);
  }

  async del(key: string) {
    await this.redis.del(key);
  }
}
  • get은 Redis에서 값을 가져옵니다.
  • set은 JSON 문자열로 변환해서 TTL과 함께 저장합니다.
  • del은 특정 key를 삭제합니다.

✅ 9. 상품 목록 캐싱 예시

➕ 9-1. 캐시 적용 전

async getProducts(category: string) {
  return this.prisma.product.findMany({
    where: {
      category,
      isVisible: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  });
}
  • 사용자가 상품 목록을 볼 때마다 DB를 조회합니다.
  • 트래픽이 많으면 DB 부하가 커질 수 있습니다.

➕ 9-2. 캐시 적용 후

async getProducts(category: string) {
  const cacheKey = `products:list:category:${category}`;

  const cached = await this.cacheService.get(cacheKey);

  if (cached) {
    return cached;
  }

  const products = await this.prisma.product.findMany({
    where: {
      category,
      isVisible: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  });

  await this.cacheService.set(cacheKey, products, 60 * 5);

  return products;
}
  • 첫 요청은 DB를 조회합니다.
  • 이후 5분 동안은 Redis에서 데이터를 가져옵니다.
  • 상품 목록처럼 자주 조회되는 API에 적용하면 효과가 큽니다.

✅ 10. 캐시 무효화

  • 캐시 무효화(Cache Invalidation)는 오래된 캐시 데이터를 삭제하거나 갱신하는 작업입니다.
  • 캐싱에서 가장 어려운 부분 중 하나가 바로 무효화입니다.

➕ 10-1. 무효화가 필요한 상황

  • 상품 정보 수정
  • 배너 노출 여부 변경
  • 이벤트 상태 변경
  • FAQ 수정
  • 공지사항 등록
  • 관리자 설정 변경
  • 가격/지원금 정보 변경
상품 수정:
DB에는 새 데이터 저장됨
  ↓
Redis에는 이전 상품 데이터 남아 있음
  ↓
사용자에게 오래된 정보가 보임

➕ 10-2. 상품 수정 후 캐시 삭제 예시

async updateProduct(productId: number, dto: UpdateProductDto) {
  const product = await this.prisma.product.update({
    where: {
      id: productId,
    },
    data: dto,
  });

  await this.cacheService.del(`products:detail:${productId}`);
  await this.cacheService.del(`products:list:category:${product.category}`);

  return product;
}
  • 상품 상세 캐시와 상품 목록 캐시를 함께 삭제합니다.
  • 다음 요청이 들어오면 DB에서 최신 데이터를 조회하고 다시 캐싱합니다.

✅ 11. 캐시 무효화 전략

➕ 11-1. TTL 기반

  • 캐시가 일정 시간이 지나면 자동으로 만료되게 합니다.
장점:
구현이 단순함

단점:
TTL 동안 오래된 데이터가 보일 수 있음
  • 배너, 상품 목록, FAQ처럼 조금 늦게 반영되어도 괜찮은 데이터에 적합합니다.

➕ 11-2. 수동 삭제

  • 데이터가 수정될 때 관련 캐시를 직접 삭제합니다.
상품 수정
  ↓
상품 상세 캐시 삭제
상품 목록 캐시 삭제
  • 최신 데이터 반영이 빠릅니다.
  • 하지만 관련된 캐시 key를 빠뜨리면 오래된 데이터가 남을 수 있습니다.

➕ 11-3. 버전 기반 캐시

  • key에 버전 값을 포함해서 새 버전의 캐시를 사용하게 만드는 방식입니다.
products:list:v1
products:list:v2
  • 대규모 캐시 갱신이 필요할 때 유용합니다.
  • 오래된 key 정리 전략이 필요합니다.

✅ 12. Cache Stampede

  • Cache Stampede는 캐시가 만료된 순간 많은 요청이 동시에 DB로 몰리는 현상입니다.
  • 트래픽이 많은 서비스에서 자주 문제가 됩니다.

➕ 12-1. 문제 상황

메인 배너 캐시 만료
  ↓
동시에 사용자 1,000명 접속
  ↓
1,000개 요청이 모두 DB 조회
  ↓
DB 부하 급증

➕ 12-2. 완화 방법

  • TTL을 약간 랜덤하게 설정합니다.
  • Lock을 사용해 한 요청만 DB를 조회하게 합니다.
  • 백그라운드에서 미리 캐시를 갱신합니다.
  • 중요한 데이터는 TTL을 너무 짧게 잡지 않습니다.
예시:
TTL을 항상 300초로 두지 않고
270초 ~ 330초 사이로 랜덤 설정

✅ 13. 캐시와 개인정보

  • 캐시는 빠르지만, 개인정보를 잘못 저장하면 보안 문제가 됩니다.
  • 특히 공용 캐시에 사용자별 민감정보를 저장할 때는 매우 조심해야 합니다.

➕ 13-1. 캐싱에 조심해야 할 정보

  • 전화번호
  • 이름
  • 주민등록번호
  • 인증번호
  • Access Token
  • Refresh Token
  • 결제 정보
  • 관리자 권한 정보
  • 고객 상담 메모
나쁜 예시:
consult:01012345678
user:token:eyJhbGciOi...

➕ 13-2. 개인정보 캐싱 기준

  1. 꼭 필요한 경우에만 저장한다.
  2. TTL을 짧게 설정한다.
  3. key에 개인정보를 직접 넣지 않는다.
  4. 암호화나 마스킹을 고려한다.
  5. 접근 권한을 제한한다.
  6. 로그에 캐시 값이 출력되지 않게 한다.

✅ 14. Redis를 세션 저장소로 사용하기

  • 서버가 여러 대가 되면 메모리 세션을 서버 내부에 저장하는 방식이 문제가 됩니다.
  • 사용자가 A 서버에서 로그인했는데 다음 요청이 B 서버로 가면 로그인 상태를 찾지 못할 수 있습니다.
  • 이때 Redis를 세션 저장소로 사용할 수 있습니다.

➕ 14-1. 구조

사용자 로그인
  ↓
API 서버
  ↓
Redis에 세션 저장

다음 요청
  ↓
어떤 API 서버로 가도 Redis에서 세션 조회

➕ 14-2. Redis 세션의 장점

  • 서버 여러 대에서 세션 공유 가능

  • 세션 만료 시간 관리 가능

  • 강제 로그아웃 처리 가능

  • 서버 재시작 후에도 세션 유지 가능

  • 단, JWT 기반 인증을 사용한다면 Redis 세션이 반드시 필요한 것은 아닙니다.

  • Refresh Token 관리, 블랙리스트, 관리자 강제 로그아웃 등에 Redis를 사용할 수 있습니다.


✅ 15. Redis를 Rate Limiting에 사용하기

  • Rate Limiting은 특정 사용자나 IP가 일정 시간 안에 너무 많은 요청을 보내지 못하도록 제한하는 기능입니다.
  • 로그인 시도, 인증번호 발송, 상담 신청, 검색 API 등에 필요합니다.

➕ 15-1. 인증번호 발송 제한 예시

같은 전화번호로 인증번호 발송:
1분에 1회
하루 5회까지만 허용

➕ 15-2. Redis Key 예시

rate:sms:phone:hashValue
rate:login:ip:123.123.123.123
rate:consult:ip:123.123.123.123
  • 전화번호를 그대로 key에 넣기보다 해시값을 사용하는 것이 좋습니다.
  • Redis의 TTL을 사용하면 일정 시간 뒤 자동으로 제한 카운트를 초기화할 수 있습니다.

✅ 16. Redis 운영 시 주의점

➕ 16-1. Redis는 메모리 기반이다

  • Redis는 매우 빠르지만 메모리를 사용합니다.
  • 너무 많은 데이터를 무제한으로 넣으면 메모리가 부족해질 수 있습니다.
나쁜 예시:
모든 주문 목록을 무기한 Redis에 저장

좋은 예시:
자주 조회되는 공개 데이터만 TTL과 함께 저장

➕ 16-2. Redis 장애 대비

  • Redis가 장애 나면 캐시 조회가 실패할 수 있습니다.
  • 캐시 서버가 죽었다고 서비스 전체가 죽으면 안 됩니다.
캐시 조회 실패
  ↓
DB 조회로 fallback
  ↓
서비스는 계속 동작
  • 단, Redis를 Queue나 세션 저장소로 쓰고 있다면 장애 영향이 더 커질 수 있습니다.
  • 캐시 용도와 핵심 인프라 용도를 구분해서 생각해야 합니다.

➕ 16-3. 캐시에 의존한 비즈니스 로직 주의

  • 캐시는 보조 저장소입니다.
  • 중요한 원본 데이터는 DB에 있어야 합니다.
좋지 않은 구조:
사전예약 신청 수를 Redis에만 저장

더 안전한 구조:
DB에 원본 저장
Redis는 빠른 조회나 임시 카운트 용도로 사용

✅ 17. 실무 체크리스트

➕ 17-1. 캐시 적용 체크리스트

  1. 이 데이터가 자주 조회되는가?
  2. DB 조회 비용이 큰가?
  3. 약간 오래된 데이터가 보여도 괜찮은가?
  4. TTL을 몇 초로 둘지 정했는가?
  5. 데이터 수정 시 캐시 무효화가 가능한가?
  6. 캐시 key 규칙이 명확한가?
  7. 개인정보가 캐시에 들어가지 않는가?
  8. Redis 장애 시 DB fallback이 가능한가?

➕ 17-2. Redis 운영 체크리스트

  1. Redis 메모리 사용량을 확인하는가?
  2. TTL 없는 key가 무제한 쌓이지 않는가?
  3. 운영/개발 Redis가 분리되어 있는가?
  4. Redis 접속 권한이 제한되어 있는가?
  5. Queue와 Cache 용도를 같은 Redis에서 쓴다면 부하를 확인하는가?
  6. 중요한 데이터 원본을 Redis에만 저장하지 않는가?
  7. 장애 시 서비스가 어떻게 동작할지 정해져 있는가?

➕ 17-3. 캐시 무효화 체크리스트

  1. 상품 수정 시 상품 상세 캐시가 삭제되는가?
  2. 상품 목록 캐시도 함께 삭제되는가?
  3. 배너 수정 시 메인 배너 캐시가 삭제되는가?
  4. 관리자 설정 변경 시 공개 설정 캐시가 갱신되는가?
  5. TTL만 믿고 너무 오래된 데이터를 보여주지 않는가?
  6. 캐시 key를 빠뜨려서 일부 화면만 오래된 데이터가 보이지 않는가?

✅ 18. AI를 활용해 Redis 캐싱을 설계할 때 질문법

  • Redis 캐싱을 AI에게 물어볼 때는 어떤 데이터를 캐싱할지, 얼마나 자주 바뀌는지, 오래된 데이터 허용 여부를 같이 알려줘야 합니다.
  • 단순히 “Redis 붙여줘”라고 하면 잘못된 곳에 캐시를 넣을 수 있습니다.

➕ 18-1. 좋은 질문 예시

NestJS + Prisma + PostgreSQL 서비스에서 Redis 캐싱을 적용하고 싶어.

상황:
1. 고객 메인 페이지에서 배너 목록, 인기 상품, FAQ를 조회함
2. 배너와 FAQ는 관리자에서 가끔 수정됨
3. 인기 상품은 10분 단위로 바뀌어도 괜찮음
4. 상품 상세는 수정 직후 최대한 빨리 반영되어야 함
5. 개인정보가 포함된 상담 신청 데이터는 캐싱하고 싶지 않음
6. Redis 장애 시 DB 조회로 fallback하고 싶음

요청:
- 어떤 API에 캐시를 적용하면 좋은지
- Redis key 이름 설계
- TTL 추천
- 관리자 수정 시 캐시 무효화 방식
- NestJS CacheService 예시
- 주의해야 할 보안 이슈
를 설명해줘.

➕ 18-2. AI 답변 검증 기준

  1. 모든 데이터를 무조건 캐싱하라고 하지 않는가?
  2. 자주 조회되고 자주 바뀌지 않는 데이터 위주로 추천하는가?
  3. 개인정보 캐싱 주의점을 설명하는가?
  4. TTL과 수동 무효화를 함께 고려하는가?
  5. Redis 장애 시 fallback을 설명하는가?
  6. 캐시 key 규칙을 명확히 제안하는가?
  7. 오래된 데이터가 보여도 되는지 기준을 나누는가?

📌 요약

  • 캐싱은 자주 조회되는 데이터를 임시 저장소에 보관해 응답 속도를 높이고 DB 부하를 줄이는 전략입니다.
  • Redis는 메모리 기반 Key-Value 저장소로, 캐시, 세션, Queue, Rate Limiting, Lock 등에 사용됩니다.
  • 캐싱은 메인 배너, 카테고리, FAQ, 상품 목록, 공개 설정값처럼 자주 조회되고 자주 바뀌지 않는 데이터에 적합합니다.
  • 결제 상태, 주문 상태, 포인트, 개인정보, 인증 정보처럼 정확성과 보안이 중요한 데이터는 캐싱을 신중하게 적용해야 합니다.
  • 실무에서 자주 쓰는 방식은 먼저 Redis를 조회하고, 없으면 DB를 조회한 뒤 Redis에 저장하는 Cache Aside 패턴입니다.
  • 캐시에는 TTL이 필요하며, 데이터가 수정될 때 관련 캐시를 삭제하는 무효화 전략도 함께 설계해야 합니다.
  • Redis key는 서비스 영역, 데이터 종류, 파라미터를 기준으로 명확하게 설계해야 하며, key에 개인정보를 직접 넣으면 안 됩니다.
  • Redis 장애가 발생해도 DB 조회로 fallback할 수 있도록 설계해야 캐시 장애가 전체 서비스 장애로 번지는 것을 막을 수 있습니다.

0개의 댓글