TIL - 20261004

juni·4일 전

TIL

목록 보기
470/473

1004 운영 자동화/AI 워크플로우 심화 (28/N): Cache Strategy, Invalidation, Stampede와 Consistency


✅ 1. Cache의 목적은 ‘무조건 빠르게’가 아니다

Cache를 도입하는 가장 흔한 이유는:

DB Query 감소

API Response 단축

외부 API 호출 감소

서버 부하 감소

다.

하지만 Cache를 잘못 사용하면:

오래된 상품 정보

잘못된 가격

이미 취소된 주문 상태

권한 변경 미반영

DB와 Cache 불일치

같은 문제가 생긴다.

즉:

Cache는 성능 최적화 도구이면서 동시에 새로운 상태 저장소다.


✅ 2. Cache가 생기면 Source of Truth가 하나 더 생긴 것처럼 보인다

원래:

PostgreSQL
=
Source of Truth

이었다.

Cache를 넣으면:

Redis
Local Memory
CDN
Browser Cache

에도 데이터가 존재한다.

하지만 중요한 원칙은:

Cache
≠
Source of Truth

이다.


✅ 3. Source of Truth는 명확하게 유지한다

예:

상품 가격
→ PostgreSQL

주문 상태
→ PostgreSQL

Feature Flag
→ DB / SSM

Cache는:

원본 데이터를 더 빠르게 읽기 위한 복제본

으로 본다.


✅ 4. Cache를 먼저 도입할 영역

모든 Query에 Cache를 붙이지 않는다.

좋은 후보:

읽기 빈도 높음

변경 빈도 낮음

계산 비용 큼

외부 API 호출 비용 큼

약간 오래된 값 허용 가능

이다.


✅ 5. 현재 프로젝트에서 Cache 후보

예:

상품 목록

상품 상세

FAQ

카테고리

공통 코드

배너

검색 조건 Metadata

통신사/요금제 기본 정보

등이다.


✅ 6. Cache에 신중해야 하는 데이터

예:

주문 현재 상태

재고

가격 확정 정보

관리자 권한

고객 개인정보

중복 신청 판단

결제/금전 상태

같은 데이터다.

오래된 값이 실제 업무 오류로 이어질 수 있다.


✅ 7. Cache는 데이터 특성별로 다르게 설계한다

다음 질문을 먼저 한다.

얼마나 자주 읽는가?

얼마나 자주 바뀌는가?

몇 초 오래돼도 괜찮은가?

잘못된 값이 보이면 어떤 피해가 있는가?

다시 계산하기 쉬운가?

✅ 8. Cache 전략의 핵심은 Freshness 요구사항이다

예:

FAQ
10분 오래돼도 큰 문제 없음

반면:

주문 상태
10분 오래되면 문제

다.

즉 Cache TTL은 기술 숫자가 아니라 업무 요구다.


✅ 9. TTL이란?

TTL:

Time To Live

Cache Entry가 얼마 동안 유효한지를 의미한다.

예:

product:123
TTL 300초

이면 5분 후 만료된다.


✅ 10. TTL을 너무 길게 잡으면

장점:

Cache Hit 증가

DB 부하 감소

하지만:

Stale Data 증가

한다.


✅ 11. TTL이 너무 짧으면

Cache Miss 증가

DB Query 증가

Redis 사용 의미 감소

할 수 있다.


✅ 12. 데이터마다 TTL을 다르게 둔다

예:

FAQ
30분

상품 상세
5분

카테고리
10분

통신사 공통 코드
1시간

등이다.

정확한 값은 실제 변경 빈도와 트래픽을 보고 정한다.


✅ 13. Cache를 쓴다고 반드시 Redis가 필요한 것은 아니다

Cache 종류는 여러 가지다.

Browser Cache

CDN Cache

Application Local Cache

Redis

DB Query Result Cache

각 위치마다 특성이 다르다.


✅ 14. Browser Cache

사용자 브라우저가 직접 보관한다.

예:

정적 JS

CSS

이미지

폰트

에 매우 적합하다.


✅ 15. CDN Cache

CloudFront 같은 CDN이 Edge에서 응답을 보관한다.

예:

상품 이미지

배너

정적 Asset

에 적합하다.


✅ 16. Application Local Cache

NestJS Process Memory에 저장하는 방식이다.

예:

Map

LRU Cache

같은 형태다.


✅ 17. Local Cache 장점

매우 빠름

추가 Network 없음

구현 단순

이다.


✅ 18. Local Cache 단점

서버가 여러 대면:

Server A Cache

Server B Cache

가 서로 다를 수 있다.


✅ 19. 서버 재시작 시 Cache가 모두 사라진다

하지만 Cache라면 이 자체는 문제가 아니다.

Source of Truth에서 다시 만들면 된다.


✅ 20. Redis Cache

여러 서버가 하나의 Cache를 공유할 수 있다.

Server A
   ↓
 Redis
   ↑
Server B

형태다.


✅ 21. Redis가 유용한 경우

여러 Instance

공유 Cache 필요

TTL 관리

Atomic Counter

Rate Limit

Distributed Lock

등이다.


✅ 22. 현재 규모에서 Redis를 무조건 추가할 필요는 없다

현재:

단일 또는 소규모 EC2

PostgreSQL

NestJS

라면 먼저:

Query 최적화

Index

TanStack Query Client Cache

CloudFront

만으로 해결 가능한지 본다.


✅ 23. Cache는 느린 Query를 숨기는 용도로만 쓰면 안 된다

예:

SELECT
매번 8초

걸리는 Query에 Cache를 붙여:

Cache Hit
20ms

로 만들었다고 끝이 아니다.

Cache Miss 때 여전히 8초다.


✅ 24. 먼저 원본 Query를 최적화한다

순서:

Query 분석

Index

Pagination

필요 Column만 Select

N+1 제거

그 다음 Cache

가 좋다.


✅ 25. Cache-Aside Pattern

가장 흔한 방식이다.

흐름:

Request

↓

Cache 조회

Hit
→ 반환

Miss
→ DB 조회
→ Cache 저장
→ 반환

이다.


✅ 26. 코드 개념

const cached =
  await cache.get(key);

if (cached) {
  return cached;
}

const result =
  await repository.find(...);

await cache.set(
  key,
  result,
  ttl,
);

return result;

✅ 27. Cache-Aside 장점

단순함

필요한 데이터만 Cache

Cache 장애 시 DB fallback 가능

이다.


✅ 28. 단점

첫 Miss에는 DB Query가 발생한다.

그리고 여러 요청이 동시에 Miss하면 문제가 생긴다.


✅ 29. Cache Stampede

예:

product:list
TTL 만료

되는 순간 고객 요청 1,000개가 동시에 들어온다.

모두:

Cache Miss

를 본다.

결과:

DB Query 1,000개

가 동시에 실행될 수 있다.

이걸 Cache Stampede 또는 Thundering Herd라고 볼 수 있다.


✅ 30. Cache가 오히려 DB 장애를 만들 수 있다

평소에는:

Redis가 99% 요청 처리

해서 DB가 조용하다.

그런데 Cache가 한꺼번에 만료되면:

DB로 전체 Traffic 이동

한다.


✅ 31. 첫 번째 해결책: TTL Jitter

Cache마다 TTL을 조금 다르게 한다.

예:

기본 TTL
300초

실제 TTL
270~330초

처럼 만든다.


✅ 32. Jitter의 목적

많은 Cache Key가:

정각 10:00

에 동시에 생성됐다면:

10:05

에 모두 동시에 만료될 수 있다.

Jitter를 주면 만료 시점이 분산된다.


✅ 33. 정확한 TTL이 중요한 데이터에는 Jitter를 조심한다

예:

정확히 5분 후 반드시 만료

라는 Security/Business 요구가 있으면 Random TTL을 함부로 쓰지 않는다.


✅ 34. 두 번째 해결책: Single Flight

Cache Miss가 발생했을 때:

첫 요청만 DB 조회

하고 나머지는 결과를 기다리게 한다.


✅ 35. Single Flight 개념

Request A
Cache Miss
→ DB Query

Request B
Cache Miss
→ A의 Query 대기

Request C
Cache Miss
→ A의 Query 대기

결과:

DB Query 1회

만 실행된다.


✅ 36. Local Process에서는 Promise 공유로도 가능하다

예:

Map<cacheKey, Promise<Result>>

를 이용할 수 있다.


✅ 37. 여러 서버에서는 Distributed Lock이 필요할 수 있다

Server A

Server B

가 동시에 Miss를 보면 각 서버에서 하나씩 Query할 수 있다.

Redis Lock 등을 활용할 수 있다.


✅ 38. 하지만 Cache Lock을 너무 복잡하게 만들지 않는다

DB Query가 충분히 싸다면:

두 서버가 각각 한 번 Query

정도는 문제가 아닐 수 있다.

트래픽 규모에 맞춘다.


✅ 39. 세 번째 방법: Stale-While-Revalidate

Cache가 만료됐더라도:

조금 오래된 값

을 먼저 반환한다.

뒤에서 최신 값을 다시 가져온다.


✅ 40. 흐름

Fresh Cache
→ 바로 반환

Stale Cache
→ 기존 값 반환
→ Background Refresh

완전 만료
→ DB Query

이다.


✅ 41. SWR이 적합한 데이터

예:

FAQ

상품 설명

배너

카테고리

통계 요약

처럼 몇 초~몇 분 오래된 데이터가 큰 문제가 아닌 경우다.


✅ 42. SWR이 부적합한 데이터

주문 상태

권한

가격 확정

재고

인증 상태

처럼 최신성이 중요한 데이터다.


✅ 43. Fresh TTL과 Stale TTL

예:

Fresh
5분

Stale 허용
10분

으로 나눌 수 있다.


✅ 44. Cache Entry에 Timestamp 저장

예:

{
  "data": {},
  "cachedAt": "2026-10-04T00:00:00Z"
}

처럼 저장한다.

현재 시간이 Fresh Window를 넘었는지 판단한다.


✅ 45. Cache Invalidation

Cache에서 가장 어려운 문제 중 하나다.

DB 값이 바뀌었는데 Cache는 이전 값을 가지고 있다.


✅ 46. 예

기존:

product:iphone18
price = 1,500,000

관리자가:

1,450,000

으로 변경했다.

DB는 새 값인데 Cache는 5분 동안 옛 가격을 보여줄 수 있다.


✅ 47. Invalidation 전략 1: TTL만 믿는다

DB 변경 후 Cache는 건드리지 않는다.

TTL이 지나면 최신 값이 들어간다.


✅ 48. 장점

구현 매우 단순

이다.


✅ 49. 단점

TTL 동안 Stale Data가 존재한다.

따라서 오래된 값이 허용 가능한 데이터에 적합하다.


✅ 50. Invalidation 전략 2: Write 후 Cache 삭제

예:

상품 Update

↓

DB Commit

↓

cache.del(productKey)

한다.

다음 조회에서 최신 값을 다시 Cache한다.


✅ 51. 이 방식이 흔히 사용된다

이를 Cache-Aside의 Write 전략으로 볼 수 있다.


✅ 52. 하지만 또 Dual Write 문제가 생긴다

DB Update
SUCCESS

↓

Cache Delete 전에
Server Crash

하면 Cache에 오래된 값이 남는다.


✅ 53. TTL은 최종 방어선으로 남겨야 한다

Cache Invalidation을 하더라도:

TTL = 무한대

로 두면 위험하다.

Delete Event가 유실될 수 있기 때문이다.


✅ 54. 즉 전략은

Write 후 Invalidate

+

TTL

이다.

Invalidate 실패해도 TTL이 최종적으로 복구한다.


✅ 55. Cache Invalidation도 Outbox와 연결할 수 있다

1001의 Outbox를 이용해:

Product Update

+

CACHE_INVALIDATE_REQUESTED

COMMIT

후 Consumer가 Cache를 삭제할 수 있다.


✅ 56. 하지만 Cache Invalidation까지 무조건 Outbox로 할 필요는 없다

Cache Stale이 몇 분 허용되고 TTL이 있다면 단순:

DB Update
→ best-effort cache.delete

만으로도 충분할 수 있다.


✅ 57. 업무 위험도에 맞춘다

예:

FAQ
Invalidate 실패
→ 큰 문제 없음

하지만:

상품 가격
Invalidate 실패
→ 고객 잘못된 가격 노출 가능

이면 더 강한 구조가 필요하다.


✅ 58. Price Cache는 더 짧은 TTL을 사용할 수도 있다

예:

Product Description
10분

Product Price
30초

처럼 하나의 Product DTO 전체를 같은 TTL로 Cache하지 않을 수도 있다.


✅ 59. Cache Granularity

Cache 단위를 결정한다.

예:

product:{id}

또는:

product-list:{category}:{page}

이다.


✅ 60. 너무 큰 객체 하나에 모든 것을 Cache하면

예:

all-products

하나로 10,000개 상품을 넣으면:

한 상품 변경
→ 전체 Cache 삭제

가 필요하다.


✅ 61. 너무 잘게 나누면

Key 수 폭증

Network 호출 증가

Invalidation 복잡

이 생긴다.


✅ 62. 보통 조회 단위와 비슷하게 Cache Key를 설계한다

예:

product:{id}

category:{id}:products:{page}

faq:list

처럼 한다.


✅ 63. Admin List Cache는 특히 신중

관리자 주문 목록은:

필터

검색

페이지

정렬

상태

조합이 많다.


✅ 64. Cache Key가 폭발할 수 있다

예:

status=WAITING
page=1

status=WAITING
page=2

search=홍길동
page=1

carrier=LGU
status=DONE
...

조합이 끝없이 생긴다.


✅ 65. 이런 동적 관리자 Query는 Cache보다 DB 최적화가 우선

예:

Index

Pagination

Search 구조 개선

필요 Column Select

이 더 중요하다.


✅ 66. Cache Cardinality

Cache Key 종류가 과도하게 많아지는 문제다.

예:

search:{query}

에서 모든 검색어를 Cache하면 Key가 끝없이 늘어난다.


✅ 67. 사용자 입력 전체를 Cache Key에 넣는 것을 조심한다

공격자가:

random query 100만 개

를 날려 Redis Memory를 소비하게 할 수도 있다.


✅ 68. Cache Admission Policy

모든 Miss 결과를 Cache하지 않고:

자주 조회되는 Query만

Cache할 수 있다.

현재 규모에서는 복잡한 Admission Algorithm까지 필요 없다.


✅ 69. 빈 결과도 Cache할 수 있다

예:

product:999999
→ 없음

매번 DB를 조회할 필요가 없다.


✅ 70. Negative Cache

존재하지 않는 결과를 짧게 Cache하는 방식이다.

예:

NOT_FOUND
TTL 30초

이다.


✅ 71. Cache Penetration 방지

공격/오류로 존재하지 않는 ID가 계속 조회되면:

Cache Miss
→ DB Query

가 반복된다.

Negative Cache가 도움이 된다.


✅ 72. Negative Cache TTL은 짧게

왜냐하면:

없던 Product가 방금 생성

될 수 있기 때문이다.


✅ 73. Null과 Cache Miss를 구분해야 한다

예:

const result = await cache.get(key);

if (!result) {

라고만 하면 Cache된 null과 Miss를 구분하기 어렵다.


✅ 74. 명시적 Wrapper

예:

{
  "found": false
}

같이 저장할 수 있다.


✅ 75. Cache Key Naming Convention

예:

{service}:{domain}:{version}:{identifier}

형태가 좋다.

예:

togethermall:product:v2:123

✅ 76. Version을 넣는 이유

1002의 Schema/API Versioning과 연결된다.

Response 구조가 바뀌었는데 기존 Cache가 남아 있을 수 있다.


✅ 77. Cache Key Versioning

기존:

product:v1:123

새 버전:

product:v2:123

을 사용한다.


✅ 78. 이 방식은 전체 Cache 삭제보다 안전하다

새 Application은 v2 Key만 사용한다.

v1은 TTL 이후 자연스럽게 사라진다.


✅ 79. Cache Flush All은 위험하다

예:

FLUSHALL

을 Production에서 실행하면 모든 Cache가 한꺼번에 사라진다.

결과:

DB Traffic 급증

할 수 있다.


✅ 80. Cache Warm-up이 필요할 수 있다

대규모 Cache를 비운 뒤:

인기 상품

공통 코드

메인 페이지

같은 데이터를 미리 채울 수 있다.


✅ 81. 하지만 모든 Cache를 미리 채울 필요는 없다

실제 접근되는 데이터만 Cache하는 Lazy 방식이 단순하다.


✅ 82. Warm Cache vs Cold Cache

Warm
주요 Entry가 존재

Cold
Cache가 비어 있음

이다.


✅ 83. 배포 직후 Cache Cold Start

Application Release나 Redis 재시작 이후 Cache가 비어 있을 수 있다.

평소보다 DB Load가 증가할 수 있다.


✅ 84. Release Monitoring에서 Cache Hit Rate를 같이 본다

예:

Cache Hit Rate
95%
→
20%

로 떨어졌다면 DB 부하 증가 원인이 될 수 있다.


✅ 85. Cache Hit Rate

hits
/
(hits + misses)

이다.

높다고 무조건 좋은 것은 아니다.


✅ 86. 99% Hit Rate인데 데이터가 30분씩 오래됐다면 나쁜 Cache다

성능 Metric만 보면 안 된다.

Freshness도 같이 본다.


✅ 87. Cache Metrics

예:

cache_hit_total

cache_miss_total

cache_error_total

cache_set_total

cache_eviction_total

cache_latency

cache_entry_age

정도를 볼 수 있다.


✅ 88. Redis Metrics

추가로:

Memory Usage

Connections

Evictions

Command Latency

Key Count

등을 본다.


✅ 89. Eviction

Redis Memory가 부족해 Cache Entry를 자동으로 밀어낼 수 있다.


✅ 90. Cache는 Eviction돼도 Business가 깨지면 안 된다

Cache가 없어도 Source of Truth에서 다시 조회할 수 있어야 한다.


✅ 91. Cache Eviction이 장애가 되는 구조는 위험하다

예:

Session 상태를 Cache에만 저장

원본 없음

이면 Cache가 단순 Cache가 아니라 State Store다.

역할을 명확히 구분한다.


✅ 92. Session Store와 Cache는 같은 Redis를 쓰더라도 다르다

Cache
없어져도 재생성 가능
Session
없어지면 로그인 상태 소실

이다.

운영 정책도 달라진다.


✅ 93. Cache 장애 시 정책

Redis가 다운됐다고 하자.

다음 선택이 있다.

DB fallback

Fail request

Stale local cache

기능 제한

업무에 따라 정한다.


✅ 94. Cache는 가능하면 Fail Open

예:

상품 Cache Redis 장애

↓

DB 직접 조회

하면 서비스는 조금 느려지지만 계속 동작한다.


✅ 95. 하지만 모든 요청이 DB로 몰리면 더 큰 장애가 될 수 있다

Redis 장애:

100% Cache Miss

↓

DB Traffic 20배

가 될 수 있다.


✅ 96. Cache Failure에도 Bulkhead가 필요할 수 있다

예:

DB fallback 동시 요청 제한

을 둔다.


✅ 97. Degraded Mode

Cache 장애 시:

인기 상품만 제공

일부 무거운 추천 기능 OFF

검색 제한

같은 방식도 가능하다.


✅ 98. Circuit Breaker와 Cache

Redis Timeout이 발생할 때마다:

매 Request
2초 Redis Timeout
+
DB Query

하면 응답이 매우 느려진다.


✅ 99. Redis 장애 감지 후 잠시 Cache를 건너뛸 수 있다

예:

Cache Circuit Open

↓

바로 DB fallback

한다.


✅ 100. 일정 시간 후 Cache Health를 재확인

Half Open

↓

Redis Ping

↓

정상
→ Cache 복구

같은 구조다.


✅ 101. 단 Cache Circuit Breaker까지 초기에 필요하지 않을 수 있다

현재 규모에서는:

짧은 Redis Timeout

빠른 Fallback

만으로 충분할 수 있다.


✅ 102. Redis Timeout은 짧게

Cache는 성능을 위한 시스템인데:

Redis 5초 기다림

은 목적과 반대다.

보통 Cache 호출은 빠르게 실패하고 원본으로 넘어가는 편이 낫다.


✅ 103. Cache Timeout과 DB Timeout은 별도로 본다

Cache
수십~수백 ms 수준

DB
더 긴 시간

처럼 업무/Infra에 맞게 조절한다.


✅ 104. Cache Serialization

Redis에는 보통 JSON/String 형태로 저장한다.

예:

{
  "id": "product_123",
  "name": "..."
}

이다.


✅ 105. Serialization Format도 Contract다

DTO 구조가 바뀌면 기존 Cache Entry가 깨질 수 있다.

1002의 Versioning과 같은 문제다.


✅ 106. 그래서 Cache Key에 Version을 넣는 것이 단순하다

product:v3:123

처럼 사용한다.


✅ 107. Cache Payload에 PII를 넣는 것도 신중해야 한다

Redis도 데이터 저장소다.

예:

이름

전화번호

주소

를 그대로 Cache하면 데이터 사본이 하나 더 생긴다.


✅ 108. 개인정보 Cache는 필요성을 따진다

관리자 주문 Detail을 Cache해서 몇 ms 줄이는 것보다:

PII 복제 감소

가 더 중요할 수 있다.


✅ 109. Cache Retention은 TTL이므로 PII가 자연스럽게 사라질 수 있다

하지만:

TTL 24시간

이면 그동안 사본이 유지된다.

정책을 고려한다.


✅ 110. Cache Log에 Value를 출력하지 않는다

예:

logger.info({
  cacheKey,
  cachedValue,
});

는 개인정보가 로그에 복제될 수 있다.


✅ 111. 로그에는 Metadata만

key

hit/miss

latency

ttl

정도면 충분하다.


✅ 112. Cache Key 자체에도 개인정보를 넣지 않는다

나쁜 예:

user:01012345678:orders

Redis Key 목록만 봐도 개인정보가 노출된다.


✅ 113. 내부 ID 사용

user:u_123:orders

처럼 사용한다.


✅ 114. Authorization Result Cache

권한 계산이 복잡하면 Cache하고 싶을 수 있다.

하지만 권한 변경 반영 지연이 위험하다.


✅ 115. 예

관리자 권한을 제거했는데:

permission cache TTL
1시간

이면 1시간 동안 기존 권한을 사용할 수 있다.


✅ 116. Security 관련 Cache는 짧은 TTL 또는 즉시 Invalidate

예:

RBAC 변경

↓

permission:user:123
삭제

한다.


✅ 117. 권한 Cache는 Fail Closed를 고려

Cache 조회 실패 시:

기존 권한 허용

보다 DB 재조회가 안전하다.


✅ 118. 인증 Token 검증 Cache도 신중

Logout/Revocation과의 Consistency를 고려해야 한다.


✅ 119. Feature Flag Cache

0928에서 다룬 Dynamic Config도 Cache가 필요하다.

예:

Feature Flag TTL
30초

이다.


✅ 120. Kill Switch는 짧은 TTL

예:

AI Production Action Kill Switch
5초

처럼 한다.

Cache 때문에 Kill Switch가 늦게 반영되면 위험하다.


✅ 121. Config Cache에는 Last Known Good Value를 사용할 수 있다

SSM 일시 장애 시:

마지막 정상값

을 잠깐 유지할 수 있다.


✅ 122. 하지만 Security Policy는 Last Known Good가 위험할 수 있다

예:

AI_PROD_ACCESS=false

로 껐는데 Cache가 과거:

true

를 유지하면 안 된다.


✅ 123. 데이터별 Failure Policy가 다르다

예:

FAQ Cache 장애
→ DB fallback

Kill Switch Cache 장애
→ Fail Closed

상품 Cache 장애
→ DB fallback

처럼 정책을 나눈다.


✅ 124. Cache Consistency Level

개념적으로 다음처럼 분류할 수 있다.

STRONG-ish

BOUNDED_STALENESS

BEST_EFFORT

✅ 125. Bounded Staleness

예:

상품 설명
최대 5분 오래된 값 허용

처럼 오래될 수 있는 최대 시간을 정의한다.


✅ 126. Best Effort

예:

인기 검색어

내부 Dashboard 일부

처럼 약간의 지연이나 누락이 큰 문제가 없는 데이터다.


✅ 127. Cache SLA/SLO도 만들 수 있다

예:

상품 Cache
99% 요청에서 100ms 이하

또는:

Stale Age
5분 이하

같은 목표다.


✅ 128. Invalidation 대상 파악

예:

상품이 변경되면:

product:{id}

product-list:{category}

search:{keyword}

등 여러 Cache가 영향을 받을 수 있다.


✅ 129. 이게 Cache Invalidation이 어려운 이유다

한 DB Row가 여러 Derived Cache에 포함될 수 있다.


✅ 130. Tag 기반 Invalidation

예:

product:123
tag=product:123

category-list
tag=category:iphone

같이 관련 Cache를 Group으로 관리할 수 있다.


✅ 131. 하지만 Redis에 직접 Tag 시스템을 만들면 복잡하다

현재 규모에서는 명시적 Key Pattern 정도가 더 단순할 수 있다.


✅ 132. Cache Key Registry

예:

const cacheKeys = {
  productDetail: (id: string) =>
    `product:v1:${id}`,

  categoryProducts: (
    categoryId: string,
    page: number,
  ) =>
    `category:${categoryId}:products:${page}`,
};

처럼 중앙화한다.


✅ 133. 문자열 Key를 코드 곳곳에서 직접 만들지 않는다

나쁜 예:

`product-${id}`
`products:${id}`

처럼 규칙이 제각각이면 Invalidation이 어려워진다.


✅ 134. Cache Service를 중앙화한다

예:

interface CacheService {
  get<T>(key: string): Promise<T | null>;

  set<T>(
    key: string,
    value: T,
    ttl: number,
  ): Promise<void>;

  delete(key: string): Promise<void>;
}

✅ 135. Domain Service에서 Redis Library를 직접 쓰지 않는다

기존 아키텍처 원칙과 같다.

Application

↓

Cache Abstraction

↓

Redis Infrastructure

로 분리한다.


✅ 136. Cache Decorator를 남발하지 않는다

예:

@Cacheable()

을 모든 Method에 붙이면 Invalidation이 어디서 필요한지 알기 어려워질 수 있다.


✅ 137. 중요한 Cache는 명시적으로 처리하는 편이 이해하기 쉽다

예:

getProduct()

invalidateProduct()

처럼 한다.


✅ 138. Write-through Cache

DB에 쓰는 동시에 Cache에도 새 값을 쓴다.

Write

↓

DB

↓

Cache Set

이다.


✅ 139. 장점

다음 Read가 Cache Hit다.


✅ 140. 단점

또 Dual Write 문제다.

DB SUCCESS

Cache SET FAILED

가능하다.

TTL/Invalidate 전략이 필요하다.


✅ 141. Write-behind Cache

Cache에 먼저 쓰고 나중에 DB에 반영한다.

성능은 좋지만 데이터 유실 위험이 높다.


✅ 142. 현재 프로젝트 핵심 Business Data에는 권장하지 않는다

주문/상담처럼 중요한 Write는:

DB 먼저

가 안전하다.


✅ 143. Read-through Cache

Application이 Cache API를 호출하면 Cache Layer가 Miss 시 DB Loader까지 호출한다.

Cache-Aside를 추상화한 형태다.


✅ 144. 구현은 가능하지만 너무 숨기면 흐름이 안 보일 수 있다

현재는 명시적 Cache-Aside가 더 이해하기 쉽다.


✅ 145. Refresh Ahead

TTL 만료 전에 인기 Cache를 미리 갱신한다.

예:

TTL 10분

8분 시점
Background Refresh

한다.


✅ 146. 적합한 경우

항상 많이 읽히는 메인 페이지 데이터

처럼 Cache Miss가 거의 발생하면 안 되는 경우다.


✅ 147. 현재는 SWR 정도로 충분할 수 있다

복잡한 Refresh Scheduler까지 먼저 만들 필요 없다.


✅ 148. List Cache Invalidation

예:

새 상품 등록

하면:

product-list:all

category:iphone

brand:apple

등 여러 List Cache가 Stale해질 수 있다.


✅ 149. 이럴 때 TTL 의존이 더 단순할 수도 있다

Detail은 즉시 Invalidate하고,

List는:

짧은 TTL

로 운영한다.


✅ 150. 모든 Derived Cache를 정확히 삭제하려고 하면 복잡도가 커진다

Cache Consistency 요구를 낮출 수 있는 데이터라면 TTL로 해결한다.


✅ 151. Cache Invalidation Rule 예

Product Update

Detail Cache
즉시 삭제

Category List
TTL 60초

Search Result
TTL 30초

처럼 중요도에 따라 나눈다.


✅ 152. 상품 가격 변경은 List까지 더 빨리 반영해야 할 수 있다

이 경우:

가격 Cache 전용

으로 분리하거나 관련 List Invalidate를 강화한다.


✅ 153. Cache Denormalization

하나의 Cache Entry에 여러 Source 데이터가 합쳐져 있을 수 있다.

예:

Product Detail

Product

Promotion

Review Summary

를 하나의 JSON으로 Cache한다.


✅ 154. 어느 하나가 바뀌어도 전체 Entry가 Stale

따라서 변화 빈도가 다른 데이터를 너무 많이 묶지 않는다.


✅ 155. Fragment Cache

예:

product:123:base

product:123:price

product:123:review-summary

처럼 나눌 수 있다.


✅ 156. 하지만 Read 시 Redis 호출 수가 늘어난다

Trade-off다.

현재는 실제 병목이 생길 때 분리한다.


✅ 157. Cache Consistency와 Eventual Consistency

1001과 비슷한 문제다.

DB Update 후 Cache Invalidation이 몇 초 늦게 적용되면:

DB
new

Cache
old

상태가 잠깐 존재한다.


✅ 158. 이것도 일종의 Eventual Consistency

따라서:

얼마나 오래 Stale해도 되는가?

를 정의해야 한다.


✅ 159. Cache Drift

예:

DB product price
1,450,000

Cache
1,500,000

불일치다.


✅ 160. 중요한 데이터는 Drift를 Sampling할 수 있다

예:

1000번 중 1번
Cache와 DB 동시 조회

값 비교

한다.


✅ 161. Shadow Validation

실제 응답은 Cache 값을 쓰되:

Background DB Compare

를 수행할 수 있다.


✅ 162. 모든 요청에서 하면 DB 부하가 늘어난다

샘플링한다.


✅ 163. Cache Mismatch Metric

예:

cache_consistency_mismatch_total

을 기록한다.


✅ 164. Cache 삭제 대신 Version Token을 사용할 수도 있다

예:

category:iphone:version=17

을 별도로 관리한다.

List Key:

category:iphone:v17:page:1

이다.


✅ 165. 데이터 변경 시 Version만 증가

17 → 18

하면 새 요청은 새 Cache Key를 사용한다.


✅ 166. 기존 Cache는 TTL로 자연스럽게 사라진다

Bulk Key Delete를 하지 않아도 된다.


✅ 167. 이 방식은 List Cache에 유용할 수 있다

하지만 Key 수가 늘어나므로 TTL이 반드시 있어야 한다.


✅ 168. Cache Busting

Frontend Asset에서 흔히 쓰는 방식이다.

예:

banner.jpg?v=123

또는 Hash 파일명.


✅ 169. CloudFront Invalidation

배너/이미지를 같은 URL로 교체했는데 CDN Cache가 남아 있을 수 있다.


✅ 170. 가장 단순한 방법은 파일명 변경

예:

banner-v1.webp

banner-v2.webp

또는 Content Hash를 사용한다.


✅ 171. 이것이 Invalidation 요청보다 안정적인 경우가 많다

새 URL은 자동으로 Cache Miss가 된다.


✅ 172. Static Asset은 Immutable 전략이 좋다

예:

Cache-Control:
max-age=31536000, immutable

같이 긴 Cache를 주고 파일명에 Hash를 넣는다.


✅ 173. 반면 HTML은 너무 오래 Cache하면 안 된다

HTML이 새 Asset을 가리켜야 하기 때문이다.


✅ 174. CDN Cache Layer와 Application Cache를 혼동하지 않는다

예:

CloudFront
→ Static Content

Redis
→ API Data

TanStack Query
→ Client Server State

역할이 다르다.


✅ 175. TanStack Query도 Cache다

Frontend에서는 이미:

Query Cache

를 사용하고 있을 수 있다.


✅ 176. staleTime

TanStack Query의:

staleTime

은 데이터가 얼마 동안 Fresh로 간주되는지를 결정한다.


✅ 177. gcTime

Query가 사용되지 않을 때 Cache를 얼마 동안 유지할지 결정한다.


✅ 178. 서버 Cache TTL과 Client staleTime을 같이 생각해야 한다

예:

Server Cache
5분

Frontend staleTime
10분

이면 실제 사용자는 최대 10분 이상 오래된 값을 볼 수 있다.


✅ 179. Cache Layer가 여러 개면 Staleness가 누적될 수 있다

DB
↓
Redis 5분
↓
Frontend 5분

이라고 단순히 최대 5분이라고 생각하면 안 된다.

상황에 따라 더 길게 느껴질 수 있다.


✅ 180. Freshness Budget

예:

상품 가격
전체 시스템 최대 Stale 30초

라고 정한다.

그 안에서:

Redis 10초

Frontend 5초

처럼 배분한다.


✅ 181. Client Mutation 후 Invalidate

예:

관리자가 주문 상태 변경

후 TanStack Query에서:

invalidateQueries(['orders'])

를 실행한다.


✅ 182. 이건 Client Cache Invalidation이다

Backend Redis Cache와는 별개다.


✅ 183. 관리자 화면에서 Optimistic Update

사용자 경험을 위해 서버 응답 전 UI를 먼저 바꿀 수도 있다.


✅ 184. 하지만 주문 상태 같은 중요한 업무는 Optimistic Update에 신중

서버에서 상태 전이가 거부될 수 있다.

예:

WAITING
→ COMPLETED

가 정책상 실패했는데 UI만 완료로 보이면 혼란이 생긴다.


✅ 185. 중요 상태 변경은 서버 결과 확인 후 반영

또는 Optimistic Update를 하더라도 실패 시 확실히 Rollback한다.


✅ 186. 상품 좋아요 같은 가벼운 UI는 Optimistic Update가 적합할 수 있다

업무 위험도에 따라 구분한다.


✅ 187. Cache Stampede 대응 우선순위

현재 규모에서는:

1. TTL Jitter

2. 짧고 적절한 TTL

3. 필요한 곳만 Single Flight

4. SWR

정도가 현실적이다.


✅ 188. Redis Distributed Lock을 처음부터 모든 Cache에 넣지 않는다

복잡도와 Lock 장애가 생긴다.


✅ 189. Hot Key

특정 Cache Key에 요청이 몰릴 수 있다.

예:

main-page

하나에 초당 수천 요청이 몰린다.


✅ 190. Redis 자체는 빠르지만 하나의 Key에 지나친 집중은 주의한다

현재 규모에서는 큰 문제가 아닐 가능성이 높지만 개념은 알아둔다.


✅ 191. Hot Key가 만료될 때 Stampede가 특히 크다

따라서:

SWR

Single Flight

가 효과적이다.


✅ 192. Cache Warming과 Deploy

새 Cache Version으로 배포:

product:v3

하면 v3 Cache는 모두 비어 있다.


✅ 193. 대규모 서비스라면 일부 Hot Key를 미리 생성

현재는 필요 시:

메인 페이지

인기 상품

정도만 Warm-up할 수 있다.


✅ 194. Cache Warm-up 실패 때문에 Deploy 전체를 실패시킬 필요는 없다

Cache는 보조 시스템이다.

필요하면 배포 후 Background로 Warm-up한다.


✅ 195. Cache Poisoning

잘못된 데이터가 Cache에 들어가 오랫동안 제공되는 문제다.

예:

DB Query Bug

↓

잘못된 값 Cache

↓

수천 요청에 동일 오류 노출

이다.


✅ 196. Cache가 Bug의 Blast Radius를 키울 수도 있다

따라서 Cache Set 전에:

Response Validation

이 중요할 수 있다.


✅ 197. 예

상품 가격:

-1원

같은 비정상 값은 Cache하기 전에 검증한다.


✅ 198. AI 결과 Cache도 가능하다

예:

동일 상품 설명 분석

동일 Git Commit 분석

동일 Prompt Context

을 반복 호출한다면 Cache할 수 있다.


✅ 199. AI Cache Key

예:

model

promptVersion

inputHash

policyVersion

을 포함한다.


✅ 200. 단 Prompt만 Hash하면 부족할 수 있다

Model이나 System Prompt가 달라지면 결과 의미가 바뀐다.


✅ 201. AI Run Fingerprint와 연결

0916에서 사용한:

task

commitSha

promptVersion

contextHash

model

을 Cache Key로 활용할 수 있다.


✅ 202. AI Cache 장점

Token/API 비용 감소

로컬 LLM 실행 시간 감소

같은 분석 반복 방지

다.


✅ 203. 하지만 Non-deterministic 결과를 Cache하는 의미를 이해해야 한다

Cache하면:

첫 번째 결과를 재사용

하는 것이다.

새 결과 생성 기회를 없앤다.


✅ 204. 따라서 AI Cache는 업무 특성에 맞춘다

예:

Commit Summary
Cache 적합

반면:

창의적 문구 생성

은 매번 다른 결과가 필요할 수 있다.


✅ 205. AI Cache에 민감 Context를 그대로 저장하지 않는다

Hash Key와 Artifact Reference 중심으로 한다.


✅ 206. AI Result Cache Retention

예:

Commit 기반 Report
장기 가능

임시 질문 응답
짧게

업무에 따라 다르다.


✅ 207. Cache와 Data Retention 연결

1003에서 다뤘듯 Cache도 데이터 사본이다.

TTL 자체가 Retention Policy 역할을 한다.


✅ 208. Cache Entry가 영구적으로 남지 않게 한다

가능하면 TTL을 둔다.

예외적으로 영구 Key가 필요하다면 명확한 관리 정책이 필요하다.


✅ 209. No-Expiry Cache를 피한다

특히 Dynamic Data에서:

TTL 없음

은 Stale Data가 영구화될 수 있다.


✅ 210. Redis Key Leak

새 Key는 계속 생기는데 TTL이 없으면 Memory가 계속 증가한다.


✅ 211. 모든 Cache Set에 TTL을 강제할 수 있다

CacheService에서:

set(
  key,
  value,
  ttl,
)

형태로 TTL 필수화한다.


✅ 212. Default TTL

예:

default
5분

을 둘 수 있지만 중요한 Cache는 명시적으로 설정한다.


✅ 213. TTL = 0의 의미를 명확히 한다

라이브러리마다:

즉시 만료

무제한

의미가 다를 수 있다.

Wrapper에서 정책을 명확히 한다.


✅ 214. Cache Namespace

환경별로 Key가 섞이지 않게 한다.

예:

prod:togethermall:product:v1:123

staging:togethermall:product:v1:123

이다.


✅ 215. 같은 Redis를 여러 환경에서 공유하는 경우 특히 중요하다

가능하면 Production은 별도 Resource가 더 안전할 수 있다.


✅ 216. Cache Key Collision

서로 다른 Domain이 같은 Key를 사용하지 않도록 Prefix를 둔다.


✅ 217. Cache Entry Size

큰 JSON을 Cache하면:

Network 비용

Serialization 비용

Memory 사용

이 커진다.


✅ 218. Cache한다고 항상 빠른 것은 아니다

예:

5MB JSON
Redis GET

보다 DB에서 필요한 20개 Column만 조회하는 편이 나을 수 있다.


✅ 219. 필요한 데이터만 Cache한다

DTO 전체를 무조건 넣지 않는다.


✅ 220. Compression

아주 큰 Cache Value를 압축할 수도 있다.

하지만 CPU 비용과 복잡도가 있다.

현재는 필요할 때만 고려한다.


✅ 221. Redis를 Cache 외에 Queue로도 쓴다면 Resource Competition 주의

예:

Cache Key 폭증

↓

Redis Memory 부족

↓

Queue/Job 영향

이 생길 수 있다.


✅ 222. Cache와 Queue를 같은 Redis에 둘 경우 Memory 정책이 중요하다

Queue 데이터가 Eviction되면 심각할 수 있다.


✅ 223. Cache Eviction Policy가 Queue에 영향을 주면 안 된다

가능하면 Namespace/Instance 분리 또는 Resource 정책을 명확히 한다.


✅ 224. 현재 Queue Infrastructure가 Redis 기반이라면 무작정 Cache를 추가하지 않는다

현재 Memory 사용량과 Eviction 정책을 먼저 확인한다.


✅ 225. Cache Memory Budget

예:

최대 512MB

등 제한을 둔다.


✅ 226. Memory가 Budget에 가까워지면

TTL 조정

큰 Entry 제거

불필요 Cache 제거

를 검토한다.


✅ 227. LRU / LFU

Memory 부족 시 어떤 Key를 제거할지 정책이다.

LRU

최근 사용되지 않은 데이터 제거.

LFU

사용 빈도가 낮은 데이터 제거.


✅ 228. Cache 용도라면 이런 Eviction 정책을 활용할 수 있다

하지만 Redis Instance가 Queue/Session을 같이 저장하면 신중해야 한다.


✅ 229. Cache Key Scan

Production에서:

KEYS *

같이 전체 Key를 한 번에 조회하는 명령은 큰 Redis에서 부담이 될 수 있다.


✅ 230. 운영 도구에서는 SCAN 방식 사용

점진적으로 조회한다.


✅ 231. Cache Admin Tool

굳이 전체 Redis Console을 관리자에게 열 필요는 없다.

예:

Cache 상태 조회

특정 Domain Invalidate

Hit Rate

정도만 제공한다.


✅ 232. 위험한 Cache 관리자 기능

Flush All

은 기본 UI에 두지 않는 편이 좋다.


✅ 233. 특정 Key Invalidate

예:

Product 123 Cache 삭제

정도는 운영상 유용할 수 있다.


✅ 234. Cache Invalidation Audit

관리자가 수동으로 Cache를 지웠다면:

actor

key/domain

reason

timestamp

정도를 남길 수 있다.


✅ 235. Cache Version Bump도 Audit

예:

product cache
v2 → v3

같은 변경은 Release와 연결할 수 있다.


✅ 236. Cache Deployment Strategy

DTO 변경 시:

Release A
v1 읽기 + v2 쓰기

같이 복잡하게 갈 필요 없이 Key Version 변경이 가장 단순할 수 있다.


✅ 237. Blue/Green Cache까지는 과하다

현재 규모에서는:

Versioned Key

TTL

Warm-up

정도로 충분하다.


✅ 238. Cache Preload Job

필요하다면 Scheduler를 이용해:

인기 상품 Top 100

을 주기적으로 Warm할 수 있다.


✅ 239. 하지만 실제 접근 패턴을 모르면 Warm-up이 낭비다

Metrics를 보고 결정한다.


✅ 240. Cache Miss Reason

단순 Miss에도 이유가 다르다.

NOT_FOUND

EXPIRED

EVICTED

VERSION_CHANGED

MANUAL_INVALIDATION

등이다.


✅ 241. 모든 이유를 정확히 추적할 필요는 없다

중요한 장애 분석에 도움이 되는 수준만 본다.


✅ 242. Cache Age

Response에:

cachedAt

을 내부적으로 알고 있으면 Stale 분석이 쉽다.


✅ 243. 사용자에게 Cache 여부를 노출할 필요는 없다

내부 Observability 용도다.


✅ 244. Admin Debug Response

내부 관리자 Debug Mode에서:

cache
HIT

age
12s

같은 Metadata를 볼 수도 있다.


✅ 245. Production Public API에는 불필요한 Cache 내부 정보를 노출하지 않는다

Infra 구조가 드러날 수 있다.


✅ 246. Cache Reconciliation

중요 데이터에서는 주기적으로:

Cache Value

vs

DB Value

를 비교할 수 있다.


✅ 247. 불일치 발견 시

Cache Delete

로 복구한다.

Source of Truth인 DB를 Cache 값으로 덮어쓰지 않는다.


✅ 248. Cache Self-Healing

예:

Invalid JSON

Unsupported Cache Version

Consistency Mismatch

를 발견하면:

Entry 삭제

↓

다음 요청에서 재생성

한다.


✅ 249. Cache Corruption은 비교적 복구가 쉽다

Cache는 재생성 가능한 데이터이기 때문이다.


✅ 250. 그래서 “삭제 후 재생성”이 좋은 복구 전략

DB처럼 직접 수정하려고 하지 않는다.


✅ 251. Serialization Error

Cache에서 가져온 JSON이 Parsing되지 않으면:

CACHE_DESERIALIZATION_FAILED

로 기록하고 Entry를 삭제한다.


✅ 252. Unsupported Cache Version

CACHE_VERSION_UNSUPPORTED

이면 마찬가지로 Miss처럼 처리할 수 있다.


✅ 253. Cache Error가 사용자 Error로 그대로 노출되면 안 된다

예:

Redis connection refused

를 고객에게 보여주지 않는다.

DB fallback이 가능하면 정상 응답한다.


✅ 254. Cache Error Logging

예:

{
  "event": "cache.error",
  "domain": "product",
  "operation": "get",
  "errorCode": "CACHE_TIMEOUT"
}

정도다.

Value 자체는 로그하지 않는다.


✅ 255. Cache Error Code

예:

CACHE_TIMEOUT

CACHE_UNAVAILABLE

CACHE_SERIALIZATION_FAILED

CACHE_DESERIALIZATION_FAILED

CACHE_VERSION_UNSUPPORTED

CACHE_INVALIDATION_FAILED

정도로 분류할 수 있다.


✅ 256. Cache Invalidation 실패는 항상 Critical이 아니다

예:

FAQ
TTL 5분

이면 Warning 정도일 수 있다.


✅ 257. 가격 Invalidation 실패는 더 중요할 수 있다

도메인에 따라 Severity가 달라진다.


✅ 258. Cache Observability Context

예:

cacheDomain

keyVersion

hit

latency

ttl

정도를 기록한다.


✅ 259. key 전체를 Log하지 않을 수도 있다

Key 안에 내부 ID가 너무 많거나 민감할 수 있다.

Domain + hashed key 형태도 가능하다.


✅ 260. Cache와 Tracing 연결

API Trace 안에:

cache.get

db.query

cache.set

Span을 보면 병목을 알기 쉽다.


✅ 261. 예

GET /products/123
120ms

cache.get
5ms
MISS

db.query
90ms

cache.set
4ms

이다.


✅ 262. Hit이면

GET
12ms

cache.get
4ms
HIT

처럼 보인다.


✅ 263. Cache 도입 전후 효과를 측정한다

예:

p95
400ms → 120ms

DB QPS
500 → 120

처럼 확인한다.


✅ 264. 효과가 미미하면 Cache를 제거할 수도 있다

Cache는 유지보수 비용이 있다.


✅ 265. 좋은 Cache의 조건

효과가 측정됨

Invalidation이 단순함

Stale 허용 범위가 명확함

장애 시 fallback 가능

이다.


✅ 266. 나쁜 Cache의 특징

왜 있는지 모름

TTL이 무한대

Invalidation 위치를 모름

Redis 장애 시 서비스도 장애

PII가 복제됨

Metrics 없음

이다.


✅ 267. Cache Read Policy

예:

CACHE_FIRST

DB_FIRST

CACHE_ONLY

STALE_WHILE_REVALIDATE

등을 개념적으로 나눌 수 있다.


✅ 268. CACHE_ONLY는 일반 Business Data에 거의 사용하지 않는다

Cache Miss가 곧 데이터 없음으로 판단되면 Source of Truth가 Cache가 되어버린다.


✅ 269. Cache-Aside가 현재 프로젝트에 가장 현실적

Cache First

Miss
→ DB

Set Cache

이다.


✅ 270. Product Cache 적용 예

GET /products/:id

↓

product:v1:{id}

Hit
→ 반환

Miss
→ ProductRepository
→ Cache 5분
→ 반환

이다.


✅ 271. Product Update

UpdateProductUseCase

↓

DB Transaction Commit

↓

product:v1:{id}
삭제

한다.


✅ 272. List Cache는 짧은 TTL

상품 Detail 변경마다 모든 List Key를 찾지 않고:

category product list
60초

정도로 둘 수 있다.


✅ 273. Feature Flag 변경

FeatureFlag Update

↓

관련 Local/Redis Cache Invalidate

한다.

Kill Switch는 짧은 TTL도 유지한다.


✅ 274. 주문 목록은 Cache하지 않을 수도 있다

실시간성, 필터 조합, PII 등을 고려하면 DB Query 최적화가 더 단순할 수 있다.


✅ 275. 현재 프로젝트 Cache 후보 우선순위

1단계

정적 Asset / 이미지
CloudFront

2단계

Frontend TanStack Query
staleTime 정리

3단계

상품 / FAQ / 카테고리
Backend Cache

4단계

공통 코드 / 외부 API 응답

5단계

정말 필요한 고비용 Query

정도로 가는 것이 현실적이다.


✅ 276. Redis 도입 판단 기준

다음이 실제로 발생할 때 가치가 커진다.

DB Read 부하 높음

동일 Query 반복 많음

서버 여러 대

외부 API 비용 큼

Local Cache 일관성 문제가 실제 발생

✅ 277. Redis를 “있으면 좋아 보이니까” 넣지 않는다

운영해야 할 Component가 하나 더 늘어난다.


✅ 278. Redis 장애 대응도 필요해진다

Monitoring

Backup 여부

Memory

Eviction

Network

Version Upgrade

를 관리해야 한다.


✅ 279. 기존 Queue가 Redis를 쓴다면 먼저 상태 확인

예:

현재 Memory 사용량

maxmemory

eviction policy

Queue Data 구조

를 확인한다.


✅ 280. Cache Namespace만 추가해도 되는지 판단

Queue 안정성을 해치지 않는지 확인한다.


✅ 281. Local Cache부터 시작할 수도 있다

변경이 거의 없는:

공통 코드

를 1분 Local Cache로 두는 정도는 매우 단순하다.


✅ 282. 단 Multi-instance로 확장할 계획이 있으면 TTL을 짧게

서버별 Cache 차이가 오래 지속되지 않게 한다.


✅ 283. Local + Redis 2단 Cache

나중에는:

L1 Local Cache

↓

L2 Redis

↓

DB

형태도 가능하다.


✅ 284. 하지만 지금은 과하다

Cache Layer가 두 개면 Invalidation도 두 개를 처리해야 한다.


✅ 285. Cache Layer가 많을수록 Debug가 어려워진다

Browser

CDN

Frontend Query

Local

Redis

DB

어디의 Stale Data인지 찾아야 한다.


✅ 286. 따라서 Cache Layer를 최소화한다

실제로 효과가 있는 곳만 사용한다.


✅ 287. Cache Debug Checklist

값이 오래됐다는 신고가 들어오면:

DB 값

Redis 값

Frontend Query Cache

CDN

Browser

순서로 확인한다.


✅ 288. Correlation ID와 Cache

API 요청 Trace에서:

cache HIT/MISS

를 확인할 수 있으면 원인 파악이 쉽다.


✅ 289. “DB는 바뀌었는데 화면이 왜 안 바뀌지?”

흔한 원인:

Backend Redis

Frontend staleTime

CDN

Browser Cache

중 하나다.


✅ 290. Frontend Mutation 후 Query Key 설계가 중요

예:

['orders']

['order', orderId]

둘 다 영향받을 수 있다.


✅ 291. 상태 변경 후 필요한 Query를 정확히 Invalidate

예:

invalidateQueries(['orders'])

invalidateQueries([
  'order',
  orderId,
])

한다.


✅ 292. 무조건 전체 Query Cache Clear는 피한다

불필요한 Refetch가 폭증한다.


✅ 293. Query Key Factory

Backend Cache Key Registry와 비슷하게:

const orderKeys = {
  all: ['orders'],

  detail: (id: string) =>
    ['order', id],
};

처럼 관리한다.


✅ 294. Client Cache와 Server Cache 규칙을 문서화

예:

Product
Server TTL 5m
Client staleTime 1m

Order
Server Cache 없음
Client staleTime 0

처럼 표로 정리한다.


✅ 295. Cache Policy Registry

예:

interface CachePolicy {
  domain: string;

  ttlMs: number;

  staleWhileRevalidateMs?: number;

  cacheNegative?: boolean;

  sensitive?: boolean;
}

정도로 관리할 수 있다.


✅ 296. Cache TTL도 Configuration이다

0928과 연결된다.

PRODUCT_CACHE_TTL

을 설정으로 뺄 수 있다.


✅ 297. 하지만 모든 TTL을 Runtime Config로 만들 필요는 없다

자주 바꿀 이유가 없다면 코드 상수로도 충분하다.


✅ 298. Runtime Config가 유용한 경우

장애 시:

TTL 10분
→ 30초

로 빠르게 조절해야 할 수 있는 영역이다.


✅ 299. TTL 변경도 Risk가 있다

예:

300초 → 1초

로 잘못 설정하면 DB Traffic이 급증한다.


✅ 300. TTL Config Validation

예:

min
5초

max
1시간

같은 범위를 둘 수 있다.


✅ 301. Cache Policy Change Audit

예:

Product TTL
300s → 60s

Reason
가격 변경 반영 단축

을 기록할 수 있다.


✅ 302. Cache Kill Switch

Cache 시스템 자체가 문제일 때:

product_cache_enabled=false

로 우회할 수도 있다.


✅ 303. Cache OFF 시 DB Load를 고려한다

Kill Switch를 누르면 DB가 버틸 수 있는지 알아야 한다.


✅ 304. Cache는 Availability 의존성이 된다

Cache가 없어도 서비스가 살아야 한다고 설계했더라도 실제 DB Capacity가 부족하면 Cache 장애가 곧 서비스 장애가 될 수 있다.


✅ 305. 그래서 Cache Failure Test가 필요하다

Staging/Integration 환경에서:

Redis Down

을 가정한다.


✅ 306. 확인할 것

API가 fallback하는가?

Timeout이 길지 않은가?

DB가 과부하되지 않는가?

Error가 고객에게 노출되지 않는가?

이다.


✅ 307. Failure Injection

0925와 연결한다.

예:

Redis Timeout

Redis Connection Refused

Cache Corrupted JSON

Cache Miss Storm

을 테스트한다.


✅ 308. Cache Miss Storm 테스트

동시에:

100 requests

를 동일 Key로 보낸다.

Single Flight가 있다면 DB Query 수가 제한되는지 확인한다.


✅ 309. TTL Expiry Storm 테스트

여러 Hot Key가 동시에 만료됐을 때 DB Load를 확인한다.

Jitter 효과를 볼 수 있다.


✅ 310. Stale-While-Revalidate 테스트

Fresh TTL이 끝났지만 Stale Window 안에서는:

기존 값 즉시 반환

Background refresh 1회

인지 확인한다.


✅ 311. Cache Invalidation Race Condition

예:

Request A
DB Old Read 시작

관리자
DB Update

Cache Delete

Request A
Old Data를 Cache Set

할 수 있다.


✅ 312. Cache Delete만으로도 Race가 생긴다

순서:

1. A DB에서 old value 읽음

2. B DB update

3. B cache delete

4. A old value cache set

결과 Cache가 다시 오래된 값이다.


✅ 313. 해결 방법은 여러 가지가 있다

예:

짧은 TTL

Version Check

Delayed Double Delete

Write-through

등이다.


✅ 314. Delayed Double Delete

개념적으로:

DB Update 전 Cache Delete

DB Update

잠깐 뒤 Cache Delete

를 수행한다.


✅ 315. 하지만 현재 규모에는 과할 수 있다

이런 Race가 실제 문제로 나타날 가능성과 복잡도를 비교한다.

대부분은:

DB Commit 후 Delete
+
짧은 TTL

이면 충분하다.


✅ 316. Versioned Value

Cache Entry에:

recordVersion

을 저장할 수 있다.

예:

{
  "version": 12,
  "data": {}
}

이다.


✅ 317. DB Update마다 Version 증가

Old Cache를 발견하면 무효화할 수 있다.

하지만 매 Read마다 DB Version을 조회하면 Cache 이점이 줄어든다.


✅ 318. Event 기반 Cache Invalidation

DB Update 후:

PRODUCT_UPDATED

Event를 발행하고 Cache Consumer가 삭제한다.


✅ 319. 여러 Application Instance가 Local Cache를 쓴다면 유용

Server A Local Cache

Server B Local Cache

Server C Local Cache

모두 Event를 받고 삭제한다.


✅ 320. 하지만 Event 누락을 고려해야 한다

그래서 Local Cache TTL을 유지한다.


✅ 321. Cache Invalidation Event에도 At-Least-Once가 괜찮다

같은 Key를 두 번 삭제해도 최종 결과는 같다.

즉 매우 Idempotent한 작업이다.


✅ 322. Cache Delete는 좋은 Idempotent Action

이미 없음
→ 성공

으로 처리한다.


✅ 323. Cache Set은 Race가 더 복잡하다

따라서 Update Event Consumer가 새 값을 Set하기보다:

Delete

만 하는 것이 단순할 수 있다.


✅ 324. 다음 Read가 최신 값을 다시 생성한다

이 방식이 Cache-Aside와 잘 맞는다.


✅ 325. Search Cache

검색 결과를 Cache하고 싶을 수 있다.

하지만 Query 조합과 변경 빈도가 높다.


✅ 326. 인기 검색어나 정해진 필터만 Cache

예:

갤럭시

아이폰

폴드

같은 고빈도 검색만 고려할 수 있다.


✅ 327. 현재는 검색 Query Optimization이 먼저다

Cache는 나중이다.


✅ 328. Pagination Cache

Page 1은 자주 조회되고 Page 50은 거의 조회되지 않을 수 있다.

모든 Page를 Cache하지 않는다.


✅ 329. Page 1만 짧게 Cache할 수도 있다

예:

홈 상품 목록
첫 페이지
30초

정도다.


✅ 330. Cursor Pagination과 Cache

Cursor 값에 따라 Key가 많아질 수 있다.

관리자 목록 Cache에는 더 불리하다.


✅ 331. Aggregate Cache

예:

오늘 주문 수

상태별 주문 수

같은 통계는 Cache 가치가 있다.


✅ 332. 하지만 매 요청마다 COUNT Query가 실제로 병목인지 확인한다

PostgreSQL Index/별도 집계가 더 나을 수도 있다.


✅ 333. Materialized View와 Cache는 다른 해결책

Materialized View는 DB 내부에서 계산 결과를 저장한다.

Cache는 Application/Redis에서 결과를 저장한다.


✅ 334. 반복적인 복잡 집계는 Materialized View가 더 적합할 수도 있다

다만 Refresh 전략이 필요하다.

현재 필요할 때 검토한다.


✅ 335. External API Cache

예:

외부 통신사 정보

환율이 아닌 정적 Provider 정보

모델 Metadata

같은 외부 API 결과를 Cache할 수 있다.


✅ 336. 외부 API Rate Limit 완화에 좋다

같은 데이터를 반복 호출하지 않는다.


✅ 337. External API Cache에는 Provider Error를 너무 오래 Cache하지 않는다

예:

503

을 1시간 Cache하면 장애가 복구돼도 계속 실패로 보인다.


✅ 338. Error Cache는 매우 짧게 또는 안 한다

단:

404 Resource Not Found

는 Negative Cache가 유용할 수 있다.


✅ 339. Provider Response Version

외부 API Schema 변경이 있을 수 있다.

Cache에 Raw Response를 그대로 오래 저장하지 않는다.


✅ 340. Normalized DTO Cache

외부 응답을 내부 구조로 변환한 뒤 Cache하는 편이 낫다.


✅ 341. Cache Stampede와 External API

DB뿐 아니라 외부 API에도 같은 문제가 생긴다.

TTL 만료 후 수백 요청이 Provider로 나갈 수 있다.

Single Flight가 특히 유용하다.


✅ 342. Rate Limit과 결합

Cache Miss

↓

Single Flight

↓

External API

로 호출 수를 제한한다.


✅ 343. Cache Refill Failure

Refresh가 실패하면:

Old Cache 삭제

부터 해버리면 서비스가 완전히 Miss 상태가 된다.


✅ 344. SWR에서는 Refresh 성공 후 교체

기존 Stale 값은 유지한다.

Refresh SUCCESS
→ 새 값 교체

FAIL
→ 기존 값 유지

한다.


✅ 345. Max Stale Age는 있어야 한다

외부 API가 3일 장애라고 3일 전 가격 정보를 계속 보여주면 안 된다.


✅ 346. Stale Hard Limit

예:

Fresh
5분

Stale 허용
30분

30분 초과
→ Error / Fallback

처럼 한다.


✅ 347. Error Budget과 Cache

Cache 장애가 서비스 Latency/Error Budget에 영향을 줄 수 있다.

Observability와 연결한다.


✅ 348. Cache Hit Rate 자체를 목표로 삼지 않는다

비즈니스 SLO:

상품 API p95

DB CPU

Error Rate

가 실제 목표다.

Hit Rate는 원인을 설명하는 보조 Metric이다.


✅ 349. Cache 도입 전 Baseline

예:

Product Detail p95
220ms

DB QPS
100

CPU
25%

을 기록한다.


✅ 350. 도입 후

p95
70ms

DB QPS
30

CPU
15%

라면 효과가 있다.


✅ 351. Cache 유지비용까지 고려

Redis 비용

운영 Complexity

Invalidation Bug

Debug 시간

이 성능 이득보다 크면 제거할 수 있다.


✅ 352. Cache Review Checklist

Cache마다 다음을 문서화한다.

Source of Truth

Key

TTL

Stale 허용 시간

Invalidation Trigger

Fallback

PII 여부

Metrics

✅ 353. 예: 상품 상세 Cache

Source
products table

Key
product:v2:{id}

TTL
300s

Invalidate
Product Update

Fallback
DB

PII
No

이다.


✅ 354. 예: Feature Flag Cache

Source
DB

TTL
30s

Critical Kill Switch
5s

Fallback
Last Known / Fail Closed 정책별

이다.


✅ 355. 예: 주문 상태

Cache
사용 안 함

으로 결정하는 것도 좋은 Cache 설계다.


✅ 356. “Cache를 쓰지 않는다”도 명시적 선택

성능 문제가 없고 Consistency 비용이 크다면 DB 직접 조회가 낫다.


✅ 357. Current Project 추천 Policy

상품/FAQ

Cache 가능

관리자 주문 목록

우선 DB 최적화

주문 Detail

기본 DB

공통 Metadata

Local/Redis Cache 가능

Feature Flag

짧은 Cache

개인정보

불필요 Cache 피하기

✅ 358. Cache Layer의 단순함이 중요하다

가능하면:

Frontend Query Cache

CloudFront

Backend Cache 일부

정도로 끝낸다.


✅ 359. Cache 전략을 README/Architecture 문서에 남긴다

예:

/docs/cache-strategy.md

✅ 360. 문서 내용

어떤 Domain을 Cache하는가

TTL

Invalidation

Cache Key 규칙

Redis 장애 시 동작

민감 데이터 정책

정도다.


✅ 361. Cache Key 변경도 Release Note에 포함 가능

예:

Product Cache Key
v2 → v3

는 Cold Cache와 DB Load를 만들 수 있다.


✅ 362. Release Gate에서 Cache Migration 확인

큰 Cache 변경이 있으면:

Cold Start 영향

Warm-up 여부

DB Capacity

를 본다.


✅ 363. Cache Version 변경과 Canary

신규 API 일부만:

v3 Cache

를 사용하도록 할 수도 있다.


✅ 364. 단 Cache 자체 Canary는 대부분 필요 없다

새 Query/Read Path Canary와 함께 자연스럽게 검증하면 된다.


✅ 365. Cache와 Zero-Downtime Deployment

Old App:

product:v1

New App:

product:v2

를 사용하면 동시에 배포돼도 서로 Payload를 오해하지 않는다.


✅ 366. 이게 Cache Key Version의 큰 장점

1002의 Compatibility 문제를 단순하게 해결한다.


✅ 367. Cleanup

이전 Cache Version은 TTL이 지나면 자동 삭제된다.

별도 Migration이 필요 없다.


✅ 368. TTL이 없으면 Version Key가 영원히 남는다

그래서 Cache TTL이 중요하다.


✅ 369. Redis Memory Growth Alert

예:

Memory > 80%

Warning,

> 90%

Critical

같은 기준을 둘 수 있다.

정확한 기준은 환경에 맞춘다.


✅ 370. Eviction 증가 Alert

평소:

0

이던 Eviction이 갑자기 증가하면 Memory 부족 신호일 수 있다.


✅ 371. Cache Latency Alert

Redis 자체가 느려지면 모든 API에 영향을 줄 수 있다.


✅ 372. Network Hop도 고려

Local Cache:

microseconds 수준

Redis:

network round trip

이 필요하다.

아주 단순한 DB Query에는 Cache가 이득이 작을 수도 있다.


✅ 373. Benchmark

실제 측정한다.

DB query
3ms

Redis
2ms

이면 복잡도 대비 이득이 거의 없을 수 있다.


✅ 374. Cache하면 좋은 Query

100ms 이상

복잡 JOIN

외부 API

큰 계산

처럼 비용이 큰 Query부터 본다.


✅ 375. Cache 하면 안 되는 Query

PK 조회
1~2ms

변경 매우 빈번

강한 일관성 필요

등이다.


✅ 376. N+1을 Cache로 덮지 않는다

예:

100개 상품

상품마다 DB Query
100번

을 각각 Cache하는 것보다 Query 자체를 고친다.


✅ 377. Pagination 누락을 Cache로 해결하지 않는다

전체 데이터를 Cache에 넣어 Application에서 잘라 쓰지 않는다.

DB Pagination이 우선이다.


✅ 378. Cache는 Architecture 문제를 숨기는 약이 아니다

원본 설계가 괜찮은 상태에서 추가하는 최적화다.


✅ 379. AI에게 Cache 후보 분석을 맡길 수 있다

AI가:

Query 빈도

변경 빈도

응답 비용

일관성 요구

를 기준으로 후보를 정리할 수 있다.


✅ 380. 하지만 실제 Traffic Metric 없이 단정하지 않는다

코드만 보고:

이 Endpoint는 반드시 Redis Cache 필요

라고 결론내리면 안 된다.


✅ 381. AI Cache Review 예시

GET /products/:id

Read Frequency
높을 가능성

Write Frequency
낮음

PII
없음

Staleness Tolerance
수분 가능

Candidate
YES

처럼 후보로만 표시한다.


✅ 382. AI가 찾아낼 수 있는 Invalidation 누락

예:

getProduct
cache 사용

updateProduct
cache delete 없음

을 찾아낼 수 있다.


✅ 383. Cache Key 생성 코드 중복도 찾을 수 있다

예:

product:${id}

product-${id}

처럼 Key 규칙이 다른 부분이다.


✅ 384. AI에게 Redis 전체 조작 권한은 필요 없다

분석 위주로 사용한다.


✅ 385. Cache Flush는 Human Approval

Production 전체 Cache 삭제는 DB 부하를 급증시킬 수 있다.


✅ 386. 특정 Domain Invalidate는 상대적으로 낮은 위험

예:

product cache invalidate

정도다.


✅ 387. Cache Action Permission

예:

READ_CACHE_METRIC
자동 가능

INVALIDATE_SINGLE_KEY
낮은 위험

INVALIDATE_DOMAIN
Approval

FLUSH_ALL
금지/강한 승인

처럼 나눌 수 있다.


✅ 388. AI Production Action과 연결

AI가 장애를 분석해:

product cache stale 추정

까지는 할 수 있다.

실제 Domain Cache 전체 삭제는 승인을 받는다.


✅ 389. Cache Incident 예시

증상:

관리자가 가격 변경

DB에는 새 가격

고객 화면은 5분간 옛 가격

✅ 390. 원인

Product Update 후
Cache Invalidation 없음

TTL 10분

이다.


✅ 391. 개선

Product Update 후 Detail Cache 삭제

TTL 5분 유지

가격 Critical Cache TTL 30초 검토

같이 처리한다.


✅ 392. 다른 Incident

증상:

10:00 API Latency 급증

DB CPU 95%

원인:

10:00 Cache Warm Version 변경

모든 Key Cold

1,000 요청 동시 Miss

일 수 있다.


✅ 393. 개선

TTL Jitter

Hot Key Warm-up

Single Flight

Progressive Release

를 적용한다.


✅ 394. Cache Failure Postmortem

다음 질문을 본다.

Cache가 왜 Stale했는가?

Invalidation은 있었는가?

TTL은 적절했는가?

Redis 장애 시 DB가 버텼는가?

Cache가 없어도 서비스가 가능한가?

✅ 395. Cache와 Reconciliation

중요 Cache에서 이상이 감지되면:

Cache 삭제
→ Source of Truth로 재생성

이 기본 복구다.


✅ 396. Cache는 Repair보다 Rebuild

DB와 달리 Cache Entry를 직접 수리하는 것보다:

Delete

Rebuild

이 단순하다.


✅ 397. Cache Rebuild Job

대량 Cache 복구가 필요하면:

Batch Warm

Job을 만들 수 있다.


✅ 398. 하지만 Cache Rebuild가 DB를 압박하지 않게 한다

Concurrency/Rate Limit을 둔다.


✅ 399. Cache Preload Schedule은 LOW Priority

고객 Traffic보다 우선하지 않는다.


✅ 400. Local LLM 자동화 Cache

예:

commitHash + promptVersion

기준으로 이미 Report Artifact가 존재하면 LLM 재실행을 피할 수 있다.


✅ 401. 이것은 일반 Cache보다 Artifact Reuse에 가깝다

결과가 중요한 기록이므로:

Cache Eviction

에 맡기기보다 Run Artifact로 관리한다.


✅ 402. Cache와 Artifact를 구분한다

Cache:

사라져도 다시 계산 가능

Artifact:

실행 결과로 보존 가치 있음

이다.


✅ 403. 최종 Markdown Report는 Artifact

중간 Git 분석 Result는 Cache가 될 수 있다.


✅ 404. 이 구분이 Data Retention에도 중요하다

1003과 연결된다.


✅ 405. 구현 우선순위

현재 프로젝트에서는:

1단계

Cache 후보 분석

2단계

Cache Key Registry

TTL Policy

3단계

상품/FAQ 등 저위험 Cache

4단계

Update 후 Invalidation

5단계

Metrics
Hit/Miss/Latency

6단계

Stampede 대응

순서가 좋다.


✅ 406. Redis 없이 가능한 1단계

CloudFront Cache

TanStack Query staleTime

NestJS Local Cache 일부

부터 시작한다.


✅ 407. 실제 병목이 확인되면 Redis

즉:

Metric
→ 문제 확인
→ Cache 도입

순서다.


✅ 408. 현재 가장 ROI 높은 항목

1. TanStack Query staleTime / invalidate 정리

2. CloudFront 정적 Asset Cache

3. 상품/FAQ Cache 후보 선정

4. Cache Key Version

5. DB Update 후 Detail Cache Invalidation

정도다.


✅ 409. 당장 과한 것

현재 규모에서는:

다중 Redis Cluster

Multi-level L1/L2 Cache

복잡한 Tag Invalidation Engine

자동 Adaptive TTL

Distributed Single Flight Everywhere

전문 Cache Mesh

까지 필요 없다.


✅ 410. Codex 구현 프롬프트

현재 React + TanStack Query + NestJS + PostgreSQL 기반 프로젝트의
Cache 사용 구조를 먼저 분석하고,
필요한 영역에만 최소한의 Cache Strategy를 적용해줘.

목표는 Redis를 무조건 도입하는 것이 아니라,

1. 이미 존재하는 Browser/CDN/TanStack Query Cache를 정리하고
2. 실제로 반복 조회 비용이 높은 Backend Read에만 Cache를 적용하며
3. Stale Data와 Cache Invalidation 문제를 제어하는 것이다.

먼저 현재 코드에서 다음을 확인해줘.

- TanStack Query queryKey 구조
- staleTime / gcTime
- mutation 후 invalidateQueries
- CloudFront / S3 Cache 설정
- NestJS Cache 사용 여부
- Redis 사용 여부
- Redis가 Queue 용도로 이미 사용 중인지
- 자주 호출되는 Product / FAQ / Category Query
- 관리자 Order Query

실제 Traffic Metric이 없는 영역은
Redis가 반드시 필요하다고 단정하지 않는다.

요구사항:

1. Cache 대상과 비대상을 구분한다.

우선 Cache 후보:
- Product Detail
- Product List 일부
- FAQ
- Category
- 공통 Metadata

초기 Cache 비추천:
- 주문 현재 상태
- 관리자 복잡 검색 결과
- 고객 개인정보
- 권한 상태
- 중요 재고/금전 데이터

2. Cache Key 생성 로직을 중앙화한다.

예:
CacheKeys.product(id)
CacheKeys.categoryProducts(categoryId, page)

문자열 Key를 각 Service에서 임의 생성하지 않는다.

3. Cache Key에 Namespace와 Version을 포함할 수 있게 한다.

예:
togethermall:prod:product:v1:{id}

4. CacheService 추상화를 만든다.

최소:
- get
- set
- delete

Redis Library를 Business Service가 직접 호출하지 않게 한다.

5. 모든 Cache Entry에는 TTL을 적용한다.

Dynamic Data에 No-Expiry Cache를 기본으로 허용하지 않는다.

6. Domain별 TTL 정책을 중앙 관리한다.

예:
- Product
- FAQ
- Category
- Feature Flag

실제 TTL 값은 현재 변경 빈도에 맞춰 보수적으로 설정한다.

7. Cache-Aside를 기본 전략으로 사용한다.

Cache HIT
→ 반환

MISS
→ Repository 조회
→ Cache 저장
→ 반환

8. Cache 장애 시
가능한 일반 Read는 DB로 Fallback한다.

Cache 오류 자체가 고객 API Error로 노출되지 않게 한다.

9. Cache 호출 Timeout은 짧게 설정할 수 있게 한다.

Cache 장애 때문에 API가 오래 대기하지 않게 한다.

10. Product Update 후
Product Detail Cache를 Invalidate한다.

DB Transaction이 성공한 뒤 Cache Delete를 수행한다.

11. Cache Invalidation 실패에도
TTL이 최종 복구 수단이 되도록 한다.

12. List/Search Cache는 Invalidation 복잡도를 고려한다.

초기에는 모든 관련 Key를 추적하는 복잡한 Tag Engine을 만들지 말고
짧은 TTL을 우선 고려한다.

13. Negative Cache를 지원할 수 있게 한다.

존재하지 않는 Product ID 반복 조회가
매번 DB까지 내려가지 않게 하되
Negative TTL은 짧게 둔다.

14. Cache Value에서
MISS와 cached null을 구분할 수 있게 한다.

15. Cache Payload 구조 변경 시
기존 Cache와 충돌하지 않도록
Cache Key Version을 사용한다.

16. Cache Payload에
불필요한 PII를 저장하지 않는다.

17. Cache Key에
전화번호, 이메일 등 개인정보를 직접 넣지 않는다.

18. Cache Value를 Log에 출력하지 않는다.

Structured Log에는 다음 정도만 남긴다.
- cache domain
- hit/miss
- latency
- key version
- errorCode

19. Cache Metric을 수집할 수 있게 한다.

- hit
- miss
- error
- latency
- set
- invalidation

Redis가 있다면 가능하면:
- memory
- evictions
도 확인한다.

20. Cache Stampede를 고려한다.

초기 대응:
- TTL Jitter
- Hot Key에 필요한 경우 Single Flight

모든 Cache Key에 Distributed Lock을 도입하지 않는다.

21. TTL Jitter는
정확한 만료 시점이 필요하지 않은 Cache에만 사용한다.

22. Single Flight는
동일 Key에 동시 Cache Miss가 집중되는
실제 Hot Query에만 적용할 수 있게 한다.

23. Stale-While-Revalidate는
FAQ / Product Description 등
Stale 허용 가능한 데이터에서만 고려한다.

Order / Permission 같은 데이터에는 적용하지 않는다.

24. SWR을 적용한다면
Fresh TTL과 Max Stale Time을 구분한다.

Stale Data를 무기한 반환하지 않는다.

25. Cache에서 Invalid JSON 또는
지원하지 않는 Version을 읽으면
DB 값을 덮어쓰지 말고
해당 Cache Entry를 삭제한 뒤 Miss로 처리한다.

26. TanStack Query queryKey를 정리한다.

예:
productKeys
orderKeys

Mutation 성공 후
관련 Detail/List Query만 정확하게 Invalidate한다.

전체 Query Cache를 무조건 Clear하지 않는다.

27. Order 같은 중요 관리자 데이터는
기본적으로 staleTime을 짧게 또는 0으로 유지하는 방향을 검토한다.

28. Product / FAQ처럼 변경 빈도가 낮은 데이터는
적절한 staleTime을 설정한다.

29. Backend Cache TTL과
Frontend staleTime이 겹쳐
실제 Staleness가 과도하게 길어지지 않는지 검토한다.

30. CloudFront / Static Asset은
가능하면 Content Hash 기반 Immutable Asset 전략을 유지한다.

같은 파일명을 덮어쓰고
전체 CDN Invalidation에 의존하는 구조는 피한다.

31. Redis가 현재 Queue 용도로 사용되고 있다면
Cache 추가가 Queue 안정성에 영향을 주지 않는지 확인한다.

특히:
- maxmemory
- eviction policy
- memory usage
를 점검한다.

32. Queue Data가 Eviction될 수 있는 위험이 있다면
Cache 용도를 무리하게 같은 Redis에 추가하지 않는다.

33. 테스트를 작성한다.

필수:
- Cache HIT
- Cache MISS → DB → Set
- Cache unavailable → DB fallback
- Product Update → Cache invalidate
- Cache invalidate 실패 후 TTL fallback 가능
- Negative Cache
- Cached null / Miss 구분
- Unsupported Cache Version
- Cache corrupted payload
- TTL 적용
- Query Mutation 후 TanStack Query invalidate

34. Reliability Test 가능 구조를 만든다.

Scenario:
- Redis Timeout
- Redis unavailable
- 동일 Key 동시 Miss
- Cache Cold Start
- Stale Entry
- Cache Invalidation 누락

35. Redis가 현재 프로젝트에서 실제 이득이 없다고 판단되면
무리하게 도입하지 말고
TanStack Query + CloudFront + DB 최적화 중심 대안을 제시해줘.

목표는 Cache 시스템을 크게 만드는 것이 아니라
실제 성능 병목을 줄이면서
Stale Data와 운영 복잡도를 최소화하는 것이다.

✅ 411. Cache 분석용 Codex 프롬프트

현재 코드베이스에서 Cache 적용 후보를 찾아줘.

코드를 수정하지 말고 분석만 수행한다.

다음 영역을 검토한다.

1. NestJS Controller / QueryService
2. Prisma Query
3. React / TanStack Query
4. S3 / CloudFront
5. 외부 API 호출
6. Redis 사용 코드
7. 반복 계산 로직

각 후보마다 다음을 정리한다.

## 대상

## 현재 Query / 호출 구조

## 변경 빈도

## 예상 Read 빈도

실제 Metric이 없으면
높음/낮음을 사실처럼 단정하지 않는다.

## Stale 허용 가능성

## PII 포함 여부

## Cache 추천 여부

- 추천
- 조건부
- 비추천

## 추천 Cache 위치

- Browser
- CDN
- TanStack Query
- Local Memory
- Redis

## TTL 방향

정확한 값보다는
짧음 / 중간 / 길음으로 먼저 분류한다.

## Invalidation Trigger

## Cache 장애 시 Fallback

## Cache 도입 전 먼저 해야 할 최적화

특히 다음은 Cache보다 Query 최적화를 우선 검토한다.

- 관리자 Order Search
- Pagination
- N+1
- Index 부족
- 불필요한 전체 Column 조회

마지막에 ROI 기준으로
P0 / P1 / P2로 정리한다.

P0:
Cache 또는 Query 최적화 효과가 클 가능성이 높음

P1:
Metric 확인 후 도입

P2:
현재는 Cache 불필요

✅ 412. Cache Reliability Test 프롬프트

현재 Cache 구조에 대해
Reliability Test 시나리오를 작성해줘.

Production 실제 Redis나 고객 데이터를 사용하지 않는다.

테스트할 상황:

1. 정상 Cache Hit

2. Cache Miss

3. Redis Timeout

4. Redis Connection Refused

5. Invalid JSON Cache

6. Unsupported Cache Version

7. Product Update 후 Invalidation

8. Invalidation 실패

9. 동일 Key 100개 동시 Miss

10. Hot Key TTL 만료

11. TTL Jitter

12. Negative Cache

13. Stale-While-Revalidate

14. Redis Cold Start

15. Cache Memory Eviction 상황

각 Scenario마다 다음을 확인한다.

- 사용자 Response 성공 여부
- DB Query 횟수
- Cache Hit / Miss
- Latency
- 중복 DB Query
- Stale Data 범위
- Error Code
- Structured Log
- Metric

특히 Cache 장애가
DB 과부하로 전이될 수 있는 Scenario를 구분해서 알려줘.

✅ 413. 실무 체크리스트

Cache 대상

  • Read 빈도가 실제로 높은가?
  • 원본 Query가 먼저 최적화되어 있는가?
  • 변경 빈도가 낮은가?
  • Stale Data를 허용할 수 있는가?
  • Cache 도입 효과를 측정할 수 있는가?

Source of Truth

  • 원본 DB가 명확한가?
  • Cache가 없어도 재생성 가능한가?
  • Cache Value로 원본 DB를 복구하려 하지 않는가?

TTL

  • 모든 Dynamic Cache에 TTL이 있는가?
  • 데이터별 TTL이 다른가?
  • TTL이 업무 Freshness 요구와 맞는가?
  • 필요하면 Jitter를 사용하는가?

Key

  • Namespace가 있는가?
  • 환경이 구분되는가?
  • Version이 있는가?
  • PII를 Key에 직접 넣지 않는가?
  • Key 생성 로직이 중앙화되어 있는가?

Invalidation

  • Write 후 Detail Cache가 삭제되는가?
  • Invalidation 실패 시 TTL이라는 방어선이 있는가?
  • List Cache를 과도하게 정밀 Invalidate하지 않는가?
  • Cache Delete가 Idempotent한가?

Stampede

  • Hot Key가 있는가?
  • 동일 Miss가 DB에 몰리지 않는가?
  • TTL Jitter가 필요한가?
  • Single Flight가 필요한 곳만 적용되는가?
  • SWR을 사용할 수 있는 데이터인가?

Consistency

  • 최대 Stale 허용 시간이 명확한가?
  • 주문/권한 같은 중요 데이터는 Cache에 신중한가?
  • DB와 Cache Drift를 복구할 수 있는가?
  • Corrupted Cache는 삭제 후 재생성하는가?

Failure

  • Cache 장애 시 DB Fallback이 가능한가?
  • Cache Timeout이 짧은가?
  • Cache 장애 시 DB가 버틸 수 있는가?
  • 필요하면 Cache Kill Switch가 있는가?

Security

  • PII를 불필요하게 Cache하지 않는가?
  • Cache Value를 Log하지 않는가?
  • Key에 전화번호/이메일을 넣지 않는가?
  • 권한 Cache TTL이 지나치게 길지 않은가?

Frontend

  • TanStack Query Key가 체계적인가?
  • Mutation 후 관련 Query를 Invalidate하는가?
  • 전체 Query Cache를 무조건 Clear하지 않는가?
  • staleTime과 Backend TTL을 같이 고려하는가?

Operations

  • Hit/Miss를 측정하는가?
  • Cache Latency를 보는가?
  • Redis Memory/Eviction을 확인하는가?
  • Cache 도입 전후 성능을 비교하는가?
  • 실제 이득이 없으면 Cache 제거를 고려하는가?

📌 요약

1003에서는:

데이터를 언제까지 보관하고

언제 안전하게 없앨 것인가

를 다뤘다.

1004에서는 반대로:

같은 데이터를
얼마나 효율적으로 반복해서 읽을 것인가

를 다뤘다.

Cache의 핵심은:

빠른 저장소

자체가 아니다.

가장 먼저:

Source of Truth

가 어디인지 명확해야 한다.

예:

PostgreSQL
=
원본

Redis
=
재생성 가능한 복제본

이다.

Cache 대상도 아무 Query나 고르는 것이 아니다.

좋은 후보는:

Read 많음

Write 적음

계산 비용 큼

약간 Stale해도 괜찮음

인 데이터다.

반면:

주문 상태

권한

중요 가격/재고

고객 개인정보

처럼 잘못된 값의 비용이 큰 데이터는 더 신중해야 한다.

가장 기본적인 전략은:

Cache Aside

다.

Cache 조회
↓
Hit
→ 반환

Miss
→ DB
→ Cache 저장
→ 반환

한다.

Write에서는:

DB Commit
↓
Cache Invalidate

방식이 이해하기 쉽다.

하지만 Cache Delete 자체도 실패할 수 있기 때문에:

Invalidation
+
TTL

을 같이 사용한다.

즉 TTL은 단순 만료 시간이 아니라:

Invalidation이 실패했을 때 오래된 데이터가 영원히 남는 것을 막는 최종 안전장치

이기도 하다.

또 많은 Cache가 동시에 만료되면:

Cache Miss
↓
수많은 DB Query

가 발생하는 Cache Stampede 문제가 있다.

현재 규모에서는 먼저:

TTL Jitter

Single Flight가 필요한 Hot Key만 적용

Stale-While-Revalidate

정도로 대응하면 충분하다.

특히 SWR은:

FAQ

상품 설명

배너

처럼 약간 오래돼도 되는 정보에 적합하고,

Order

Permission

가격 확정 상태

에는 함부로 사용하지 않는다.

또한 Cache Layer가 여러 개라는 점을 기억해야 한다.

Browser

CloudFront

TanStack Query

NestJS

Redis

DB

중 어디에서 Stale Data가 발생했는지 찾아야 하기 때문이다.

그래서 Cache Layer를 무조건 늘리는 것보다:

실제로 효과 있는 위치에만 Cache

하는 것이 중요하다.

현재 프로젝트에서는 우선:

CloudFront 정적 Asset

TanStack Query staleTime / invalidate 정리

상품 / FAQ / Category 일부

공통 Metadata

부터 보는 것이 현실적이다.

관리자 주문 Search처럼:

Filter

Pagination

Search

Sort

조합이 복잡한 기능은 Cache보다:

DB Index

Query Optimization

Pagination

이 먼저다.

그리고 Redis 역시:

있으면 좋아 보이는 기술

로 도입하는 것이 아니라,

반복 Query가 실제 병목이고

Cache 효과가 측정 가능하며

운영 복잡도보다 이득이 큰 경우

도입해야 한다.

전체 흐름을 연결하면:

Request
↓
Cache
↓
Hit / Miss
↓
Source of Truth
↓
Cache Populate
↓
Write
↓
Invalidate
↓
TTL
↓
Observe
↓
Drift / Failure
↓
Delete & Rebuild

이 된다.

결국 Cache Strategy의 핵심은

“얼마나 오래 값을 저장할까”가 아니라, 어떤 데이터는 잠시 오래돼도 괜찮고 어떤 데이터는 절대 그래서는 안 되는지 구분한 뒤, Cache가 없어지거나 틀려도 원본 상태를 안전하게 다시 만들어낼 수 있도록 설계하는 것

이다.

0개의 댓글