#CacheStampede

1 posts loaded — scroll for more

Text
ps002026
ps002026

캐시가 만료되는 순간, 데이터베이스는 비명을 지른다

캐시 TTL 60초. 평소엔 아무 문제 없습니다.

60초마다 캐시가 갱신되고, 그 사이 수천 개의 요청은 캐시에서 처리됩니다. DB는 편하게 쉬고 있죠.

그런데 그 60초가 끝나는 바로 그 순간. 문제가 터집니다.

0.3초의 공백

캐시가 만료됐습니다. 첫 번째 요청이 DB에 쿼리를 날립니다.

그런데 그 쿼리가 끝나기 전에 두 번째, 세 번째… 만 번째 요청이 들어옵니다.

전부 캐시 미스. 전부 DB로 직행.

0.3초 동안 5만 개의 동일한 쿼리가 DB를 덮칩니다.

CPU 100%. 커넥션 풀 고갈. 타임아웃. 장애.

왜 이런 일이 생기나요

캐시는 “있으면 반환, 없으면 DB 조회” 로직입니다.

문제는 “없으면"의 순간이 모든 요청에게 동시에 온다는 겁니다.

첫 번째 요청이 DB에서 데이터를 가져와 캐시에 저장하기까지 보통 50~200ms 걸립니다. 그 짧은 시간 동안 들어오는 모든 요청이 캐시 미스를 경험합니다.


해결책 1: 락 걸기

첫 번째 요청만 DB에 가고, 나머지는 기다리게 합니다.

캐시 미스가 발생하면 락을 획득한 요청 하나만 DB를 조회합니다. 다른 요청들은 그 결과를 기다렸다가 공유합니다.

5만 개 쿼리가 1개로 줄어듭니다.


해결책 2: 미리 갱신하기

캐시가 만료되기 전에 미리 갱신합니다.

TTL이 60초라면, 50초쯤 됐을 때 백그라운드에서 미리 DB를 조회해 캐시를 갱신합니다. 사용자 요청은 항상 캐시에서 처리됩니다.

만료되는 순간 자체가 없어집니다.


해결책 3: 만료 시간 분산하기

모든 캐시가 동시에 만료되는 게 문제라면, 만료 시간을 흩어놓으면 됩니다.

TTL을 60초 고정이 아니라 55~65초 사이 랜덤으로 설정합니다. 캐시들이 제각각 만료되면서 DB 부하가 분산됩니다.


정리

캐시는 DB를 보호하는 방패입니다. 그런데 그 방패가 내려가는 순간, DB는 무방비 상태가 됩니다.

그 순간을 어떻게 관리하느냐가 시스템 안정성을 결정합니다.

실제로 5만 개 요청이 0.3초 만에 DB를 죽인 사례와 구체적인 코드 구현은 아래 글에서 확인하세요.

👉 I Watched 50,000 Requests Kill a Database in 0.3 Seconds