Redis TTL 설정 실수로 생긴 버그의 추억
- 03 Dec, 2025
Redis TTL 설정 실수로 생긴 버그의 추억
시작은 평범했다
월요일 오전 10시. 커피 두 잔 마시고 PR 하나 머지했다. 기분 좋았다. 점심 먹기 전에 간단한 캐시 로직 하나 추가하는 작업. “30분이면 되겠네.”
Redis에 상품 정보 캐싱하는 거였다. DB 부하 줄이려고. 간단하다. SET 하고 EXPIRE 설정하면 끝. TTL은 1시간으로. 자주 바뀌는 데이터 아니니까.
코드 짰다. 로컬에서 돌려봤다. 잘 된다. PR 올렸다. 팀장이 Approve. 배포했다. 11시 반. 점심 먹으러 갔다.
김치찌개 먹으면서 슬랙 확인했다. 조용하다. 좋다.

3일이 지났다
수요일 오후 4시. 평화로웠다. 다음 스프린트 계획 세우는 중이었다. 슬랙에 빨간 점. CS팀에서 멘션.
“개발님, 상품 가격이 업데이트가 안 되는 것 같은데요?”
심장이 쿵. 했다. 아니 근데 가격? 우리 가격 업데이트 로직 건드린 적 없는데.
“언제부터요?” “월요일부터 고객 신고가 몇 건 들어왔어요. 근데 저희가 확인해보니까 관리자 페이지에선 가격이 바뀌는데, 실제 상품 페이지에선 안 바뀌더라고요.”
월요일. 월요일. 내가 월요일에 뭐 했지.
Redis.
캐시.
아.
씨발.

코드를 열었다
손이 떨렸다. 파일 열었다. 내가 짠 코드.
redisTemplate.opsForValue().set(key, productInfo);
redisTemplate.expire(key, 3600, TimeUnit.SECONDS);
3600초. 1시간. 맞는데?
아니 잠깐.
상품 정보 업데이트하는 로직 봤다. 기존 코드. DB만 업데이트한다. 캐시 무효화 로직이 없다. 당연하다. 원래 캐시가 없었으니까.
내가 캐시를 추가했으면. 업데이트 로직에도. 캐시 삭제를 추가했어야 했다.
근데 난 추가 안 했다.
상품 정보는 한 번 캐싱되면. 1시간 동안 죽어도 안 바뀐다. 아니 1시간도 아니다. 누군가 그 상품을 조회할 때마다. 캐시 히트. TTL 다시 1시간.
월요일 오전에 캐싱된 데이터가. 수요일 오후까지 살아있었다. 72시간.
고객들은 3일 동안. 구 가격을 봤다.

패닉
팀 단톡에 쳤다. “긴급. 상품 캐시 버그. 지금 핫픽스 준비합니다.”
팀장이 전화했다. “뭔데?”
설명했다. 3분 동안. 팀장이 한숨 쉬는 소리 들렸다.
“일단 캐시 전부 날려. 그리고 핫픽스 PR 올려.”
Redis CLI 켰다. FLUSHDB. 엔터. 캐시 전부 날아갔다. DB 쿼리가 폭증할 거다. 근데 어쩔 수 없다.
모니터링 대시보드 봤다. DB CPU 사용률 급등. 60%. 70%. 80%. 멈췄다. 85%에서. 다행이다. 죽진 않았다.
코드 수정했다. 상품 업데이트 로직에 캐시 삭제 추가. 아니 더 정확하게는 캐시 키 패턴으로 관련된 거 전부 삭제. 상품 상세, 리스트, 카테고리별. 다.
30분 걸렸다. PR 올렸다. 팀장 리뷰. 1분 만에 Approve. “빨리 배포해.”
배포했다. 6시 10분.
사후 처리
배포 끝났다. 테스트했다. 가격 업데이트. 캐시 삭제. 잘 된다.
CS팀에 보고했다. “수정 완료했습니다. 현재는 정상 작동합니다.”
“고객들한테 어떻게 안내드리죠?”
어떻게. 어떻게 하긴. “죄송합니다. 시스템 오류로 가격 정보가 지연 반영되었습니다. 현재는 정상화되었습니다.” 이렇게 쓰는 거지.
근데 문제가 하나 더 있었다. 구 가격으로 주문한 사람들. 있을까? 봤다. 있었다. 12건. 신규 가격보다 저렴한 구 가격으로.
회계팀 전화 왔다. “이거 손실 처리 어떻게 해요?”
”…제가 결정할 사항은 아닌 것 같은데요.”
“개발팀장님이랑 통화 연결해드릴게요.”
통화 30분. 결론. 12건은 그냥 구 가격으로 인정. 손실 처리. 약 24만원. 내 잘못으로 회사가 24만원 날렸다.
퇴근은 9시였다.
회고 미팅
다음날 오전. 팀 회고. 나 혼자 하는 반성문 낭독회.
“캐시 추가할 때 쓰기 경로를 체크 안 했습니다.” “TTL 설정만 하면 된다고 생각했습니다.” “캐시 무효화 전략을 설계 안 했습니다.”
팀장이 말했다. “이번 건으로 배운 게 뭐야?”
“캐시는 읽기만 빠르게 하는 게 아니라, 쓰기도 고려해야 한다는 거요.” “TTL은 만능이 아니다.” “캐시 도입하려면 설계 단계부터 쓰기 경로 파악해야 한다.”
맞는 말만 했다. 근데 의미 없다. 이미 사고 쳤는데.
후배가 물었다. “그럼 앞으로 캐시 추가할 땐 어떻게 해야 해요?”
“체크리스트 만들어야겠지. 읽기 경로 분석. 쓰기 경로 분석. TTL 전략. 무효화 전략. 모니터링. 롤백 시나리오.”
팀장이 끄덕였다. “그거 문서로 만들어. 다음 주까지.”
일이 늘었다.
그 후
체크리스트 만들었다. 캐시 도입 가이드. 5페이지짜리. 예시 코드 포함. Confluence에 올렸다.
후배들이 읽었다. “오 좋은데요?” 댓글 달았다.
근데 솔직히. 이거 안 만들어도. 다들 내 실수 알고 있으니까. 안 그러려고 할 거다.
한 달 지났다. 다른 팀에서 나한테 물었다. “Redis 캐시 도입하려는데, 조언 좀 해주실 수 있어요?”
“제가요? 제가 조언을?”
“네, 개발님이 관련 문서 작성하셨다고 들었어요.”
아. 내 삽질이 레퍼런스가 됐다.
이상한 기분이었다. 좋은 건지 나쁜 건지.
남은 것들
지금도 상품 업데이트 로직 볼 때마다. 캐시 삭제 코드 확인한다. 있나 없나. 두 번 본다.
Redis 모니터링 대시보드. 즐겨찾기에 있다. 하루에 세 번 연다. 캐시 히트율. TTL 분포. Key 개수. 다 본다.
후배가 캐시 관련 PR 올리면. 꼼꼼히 본다. “쓰기 경로는 확인했어?” 댓글 단다. 매번.
팀장이 말했다. “네가 너무 신경 쓰는 거 아니야?”
“한 번 당했으니까요.”
“실수는 누구나 해. 중요한 건 반복 안 하는 거고.”
맞는 말이다. 근데 나한테는. 그 3일이. 72시간이. 아직도 생생하다.
고객들이 틀린 가격 보고 있을 때. 나는 커피 마시면서 다음 작업 계획 세우고 있었다. 그게 제일 씁쓸하다.
결국
버그는 고쳤다. 문서는 만들었다. 프로세스는 개선했다.
근데 그보다. 배운 건.
“간단해 보이는 것도 간단하지 않다.”
캐시 하나 추가하는 거. 30분 작업. 그렇게 생각했다. 근데 시스템은 연결돼 있다. 읽기만 빠르게 하면 끝이 아니다. 쓰기도 있다. 업데이트도 있다. 삭제도 있다.
TTL 설정했다고 끝이 아니다. 데이터는 변한다. 변하면 캐시도 바뀌어야 한다. 안 바뀌면 거짓말을 하는 거다. 고객한테.
7년 차다. 이런 실수는 안 해야 한다. 근데 했다.
그래도. 다음엔 안 한다. 이번엔 확실히 배웠으니까.
Redis 띄울 때마다. 생각한다. “TTL은 보험이지 전략이 아니다.” 쓰기 경로부터 확인한다. 무효화 로직부터 짠다. 그 다음에 캐시를 추가한다.
좀 느려졌다. 예전보다. 신중해졌다. 그게 나쁘진 않다.
실수는 또 할 거다. 근데 같은 실수는 아닐 거다. 그걸로 됐다.
