Redis Hot Key?
Hot Key는 말 그대로 "너무 자주 접근되는 단일 키" 이다. 이때 요청이 모두 읽기일 경우와, 일부라도 쓰기가 섞인 경우는 시스템 관점에서 부하의 성격이 완전히 달라진다.
MESI 프로토콜
MESI 프로토콜은 멀티코어 CPU 환경에서 캐시 일관성(Coherency)을 유지하기 위한 프로토콜이다.
MESI는 각각의 캐시 라인이 4가지 상태 중 하나를 가지도록 해서, 각 CPU 코어가 동일한 메모리에 주소에 접근하더라도 데이터 불일치를 막아주는 방식이다.
MESI의 4가지 상태는 다음과 같다:
Modified (수정) | 이 캐시 라인만 최신 데이터를 가지고 있고, 메모리에는 반영 안 됨 | 다른 캐시에 없음 + dirty 상태 |
Exclusive (배타적) | 이 캐시 라인만 데이터 가지고 있고, 메모리와 동일 | 다른 캐시에 없음 + clean 상태 |
Shared (공유) | 여러 캐시가 동일한 데이터를 가지고 있음 + 메모리와 동일 | 다른 캐시에도 있음 + clean 상태 |
Invalid (무효화) | 더 이상 유효하지 않은 데이터 | 다른 캐시에서 수정됨 등으로 무효 처리됨 |
Read Cache Miss -> 다른 캐시에 데이터가 없으면 E, 있으면 S
Write Cache Miss -> 다른 캐시에 데이터 있으면 그 캐시들은 I로 전환 -> 현재 캐시는 M
Read Hit -> 현재 상태가 E,S,M이면 그냥 읽음
Write Hit -> S 상태에서 쓰면 -> 다른 캐시들 무효화 -> M으로 변환
예시:
CPU1이 메모리 A를 읽음 -> 캐시에 없음 -> 메인 메모리에서 읽어옴 -> 상태는 E
CPU2도 A를 읽음 -> CPU1의 A는 S, CPU2의 A도 S
CPU2가 A를 씀 -> CPU1의 A는 I, CPU2의 A는 M
CPU1이 다시 A를 읽음 -> CPU2로부터 snooping통해 data 받음 -> 둘다 S
(snooping은 cpu끼리 도청(?)해서 캐시 상태를 훔쳐보는.. 그런느낌)
이로써
쓰기 충돌을 방지하고(M상태는 오직 한 개의 캐시 라인만 가질 수 있어서 write-update 필요없음, 나머지는 I상태 됨),
불필요한 메모리 접근을 줄이고(E상태 덕분에 메모리 쓰기 없이 연산 가능),
캐시 일관성을 유지할 수 있다(snooping + 상태 전이).
인텔은 대부분 MESI, AMD는 MOESI이다.
(MOESI는 Owner 상태가 추가된 건데.. 이건 dirty + 공유를 가능하게 해주는 상태다.
CPU 캐시끼리 메모리의 관여 없이 최신 데이터를 주고받는 것이고 버스를 아끼고 메모리 병목을 줄임)
Hot Key - 읽기 요청만 있는 경우
먼저, 읽기만 있는 경우 성능 저하의 핵심 원인은 Redis의 단일 스레드 구조다.
아무리 읽기만 하더라도, Redis는 요청을 하나씩 순차적으로 처리해야 한다.
따라서 Key 하나에 너무 많은 요청이 몰리면 이 요청들은 큐잉되며 지연된다.
CPU나 캐시 측면에서 보면, 이 상황은 오히려 효율적이다. 해당 key가 자주 조회되므로, 그 데이터는 L1이나 L2캐시에 상주할 확률이 높다.
또한 MESI 프로토콜 상에서도 이 데이터는 Shared상태로 유지되며, 여러 코어가 동시에 읽는게 허용된다. 캐시 무효화나 동기화 없이 데이터를 빠르게 조회할 수 있으므로 하드웨어 레벨에서는 큰 부하가 없다.
Hot Key - 쓰기 요청이 존재하는 경우
하지만 쓰기 요청이 섞이면 상황이 달라진다. 한 코어가 특정 Key값을 수정하는 순간, MESI 프로토콜은 해당 캐시 라인의 상태를 Modified로 바꾸고 동시에 다른 모든 코어의 동일 캐시 라인을 Invalid상태로 만든다. 이게 cache invalidation이다.
만약 여러 코어가 이 데이터를 반복해서 읽고 쓰게 되면, 캐시 라인이 여러 코어 사이에서 계속 이동하며 상태 전환을 반복한다.
이걸 cache line bouncing이라고 한다. 캐시 라인 자체가 여러 코어 간을 바운스볼처럼 오가면서 성능이 급격히 저하된다.
되게 생각해볼 게 많은 주제다. 읽기 요청이 100번, 쓰기 요청이 단 1번만 발생해도 1의 쓰기가 모든 걸 흔들 수 있다.
MESI 프로토콜에서는 단 한번의 쓰기만 발생해도 그 데이터의 모든 Shared 캐시 상태가 Invalid로 바뀌어야 하고, 그러면 다음 읽기는 모두 메모리 접근이나 캐시 재로딩을 필요로 하고, 결국 전체 읽기 처리 성능도 다 같이 끌어내려버리게 된다.
다음 글에서 모니터링 툴을 이용해 직접 내 서비스의 문제 구조를 분석하고, 해결해보자.
'팀 프로젝트 > 플러스 프로젝트' 카테고리의 다른 글
Actuator가 자동으로 Session을 생성한다 (0) | 2025.04.02 |
---|---|
조회 성능 향상시키기(5) Semaphore로 비동기 쿼리 동시 실행 제어하기 (0) | 2025.03.26 |
조회 성능 향상시키기(4) 인기 품목 조회 최적화 (0) | 2025.03.24 |