백엔드 데이터 인프라 56편. Redis Keyspace Notifications — 키 만료·변경·삭제 이벤트를 Pub/Sub 으로 받는 메커니즘. notify-keyspace-events 설정 flag·__keyspace__·__keyevent__ 채널·세션 만료 후속 처리 패턴까지 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 130편 중 56편이에요. 55편 에서 TTL(키 수명) · eviction policy(메모리 부족 시 키 강제 삭제 규칙) 를 잡았다면, 이번 56편은 Keyspace Notifications — 키에 일어난 일을 Pub/Sub(발행/구독 메시지 모델) 으로 받는 메커니즘이에요. "세션 만료될 때 알림 받고 후속 처리 자동 실행" 같은 반응형 패턴의 빌딩 블록.
Keyspace Notifications가 어렵게 느껴지는 이유
이름이 길고, 설정 flag 가 알파벳 한 글자씩 묶여 처음 보면 암호 같아요.
첫째, 두 가지 채널 모델이 헷갈립니다. __keyspace__@<db>__:<key> 와 __keyevent__@<db>__:<event> — 이름이 비슷한데 어느 쪽을 구독해야 하는지 가 안 잡혀요. "키 하나에 일어난 일을 본다" 와 "이벤트 타입별로 본다" 의 차이.
둘째, notify-keyspace-events 설정이 알파벳 조합. KEA·Ex·AKE 같이 flag 문자열 로 켜는데, 어느 글자가 어떤 이벤트인지 매번 외울 수가 없어요. 표 하나만 옆에 두면 끝나는 영역이지만 처음에는 마법주문 처럼 보이죠.
셋째, 기본값이 꺼져 있음 입니다. 설치 직후에는 notify-keyspace-events "" 라서 아무 이벤트도 발송 안 됨. "Redis 코드 박았는데 알림이 안 오는데요?" 의 가장 흔한 원인 1순위.
이 글에서 두 채널 모델·flag 표·세션 만료 패턴까지 한 번에 정리해요.
두 가지 채널 모델
같은 이벤트를 두 가지 다른 각도 로 발송해요. 어느 쪽을 구독할지는 내 관점이 키 중심이냐 이벤트 중심이냐 에 달려 있어요.
__keyspace__@<db>__:<key> — 키 중심
특정 키 에 일어난 모든 이벤트 를 받아요. 채널 이름에 키 이름 이 박혀요.
# user42 의 세션 키에서 일어나는 모든 이벤트 구독
> SUBSCRIBE "__keyspace__@0__:session:user42"
받는 메시지 = 명령 이름 (set·del·expire·expired 등).
언제 쓰나 — 특정 키 하나에만 관심 이 있을 때.
__keyevent__@<db>__:<event> — 이벤트 중심
특정 이벤트 타입 의 모든 키 를 받아요. 채널 이름에 이벤트 이름 이 박혀요.
# 만료된 모든 키 구독
> SUBSCRIBE "__keyevent__@0__:expired"
받는 메시지 = 키 이름 (만료된 키 이름이 메시지로 옴).
언제 쓰나 — "모든 만료 이벤트를 잡아 후속 처리" 같은 광역 모니터링.
어느 쪽을 쓸까
대부분 __keyevent__ 가 자주 쓰여요. 세션 만료 후속 처리·캐시 무효화·삭제 트래커 같이 이벤트 타입 한 가지 를 광역으로 봐야 하는 자리가 많아서.
__keyspace__ 는 특정 키 하나에 다양한 일이 일어나는지 보는 디버깅·모니터링 자리에 자주 써요.
한 줄 정리 — keyspace = 키 한 개의 모든 이벤트 / keyevent = 이벤트 한 개의 모든 키.
notify-keyspace-events 설정 flag 표
이 표 하나만 옆에 두면 끝나요.
| flag | 의미 |
|---|---|
| K | __keyspace__ 채널 발송 (키 중심) |
| E | __keyevent__ 채널 발송 (이벤트 중심) |
| g | Generic 명령 (DEL·UNLINK·RENAME 등) |
| $ | String 명령 (SET·APPEND 등) |
| l | List 명령 (LPUSH·RPOP 등) |
| s | Set 명령 (SADD·SREM 등) |
| h | Hash 명령 (HSET·HDEL 등) |
| z | Sorted Set 명령 |
| x | Stream 명령 |
| e | 만료 이벤트(expired) ← 가장 자주 쓰이는 자리 |
| t | eviction 이벤트 (메모리 부족으로 쫓겨남) |
| m | 키 없음 (missing) 이벤트 |
| n | 새 키 (new key) 이벤트 |
| A | 모든 g$lshzxet (편의 alias) |
자주 쓰는 조합
Ex— 만료 이벤트만 keyevent 채널로 — 가장 흔한 조합 (세션 만료 후속 처리)Exe— manual TTL 만료 + eviction 만료 모두 받기 (메모리 압박 모니터링)KEA— 모두 켜기 — 개발·디버깅용Kg$— 키 중심으로 generic+string 명령 — 캐시 무효화 트래커
설정 방법:
# 런타임 변경 (재시작 X)
> CONFIG SET notify-keyspace-events Ex
OK
# 현재 설정 확인
> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) "Ex"
영구 적용:
# redis.conf
notify-keyspace-events "Ex"
여기서 시험 함정이 하나 있어요 — K 또는 E 둘 중 하나는 반드시 있어야 발송됨. g$lshzxe 만 박으면 발송할 채널을 안 지정한 셈이라 아무것도 안 옴. 가장 자주 보이는 "왜 이벤트 안 오나" 의 원인 2순위.
패턴 1: 세션 만료 후속 처리
가장 흔한 활용처. 사용자 세션이 만료될 때 로그아웃 처리·정리 작업·통계 기록 같은 후속 작업을 거는 자리.
설정
> CONFIG SET notify-keyspace-events Ex
Python 예제
import redis
r = redis.Redis(decode_responses=True)
pubsub = r.pubsub()
pubsub.psubscribe("__keyevent@0__:expired")
# 세션 박기 (60초 후 만료)
r.set("session:user42", "{...}", ex=60)
# 다른 워커: 만료 이벤트 받기
for msg in pubsub.listen():
if msg["type"] == "pmessage":
expired_key = msg["data"] # "session:user42"
if expired_key.startswith("session:"):
uid = expired_key.split(":")[1]
log_session_end(uid) # 통계·정리 작업
psubscribe = pattern subscribe (와일드카드 지원). 단순 subscribe 는 정확한 채널 이름만 받아요.
주의 — 정확한 시각 보장 X
여기서 정말 중요한 시험 함정 — 만료 이벤트는 정확히 그 시각에 발송되지 않아요. 55편 lazy + active expiration 메커니즘 때문에 실제 만료된 후 약간 늦게 도착할 수 있어요. 접근 시 만료 발견 이면 즉시 이벤트가 나가지만, active expiration 으로 정리 되는 경우엔 정리 시점에 발송되어 수 초 지연이 생길 수 있어요.
"정확히 만료 순간에 X를 해야 한다" 가 요구사항이라면 keyspace notifications 만으로 부족해요 — 별도 스케줄러 또는 delayed queue (53편 Sorted Set 패턴) 검토.
패턴 2: 캐시 무효화 트래커
> CONFIG SET notify-keyspace-events "K$"
pubsub.psubscribe("__keyspace@0__:cache:*")
for msg in pubsub.listen():
if msg["type"] == "pmessage":
cmd = msg["data"] # "set"·"del" 등
channel = msg["channel"]
key = channel.split(":", 2)[2] # 키 이름 추출
log_cache_event(cmd, key)
"누가 어느 캐시 키를 언제 갱신했나" 추적용. 캐시 운영 디버깅에 유용해요.
패턴 3: Eviction 모니터링 — 메모리 압박 감지
> CONFIG SET notify-keyspace-events Et
pubsub.psubscribe("__keyevent@0__:evicted")
evicted_count = 0
for msg in pubsub.listen():
if msg["type"] == "pmessage":
evicted_count += 1
if evicted_count > 100: # 임계 초과
alert("Redis eviction surge!")
evicted_count = 0
eviction 이 자주 발생 = 메모리 부족 신호. 알람 트리거로 빠르게 대응 가능.
한계 — Redis 7+ 의 개선 (Sharded Pub/Sub)
여기까지 따라오셨다면 한 가지 의문 — "Redis Cluster(분산 노드 구성) 환경에서도 keyspace notifications 가 작동하나요?". 답은 "각 노드별로 작동, 클러스터 전체 한 채널은 X".
Redis Cluster 의 전통적 Pub/Sub 은 모든 노드에 broadcast(전 노드 전파) 라 대규모에서 부담이 커요. Redis 7+ 는 Sharded Pub/Sub (SSUBSCRIBE·SPUBLISH) 으로 특정 슬롯만 메시지 받는 효율적 모델을 도입했어요.
다만 keyspace notifications 자체는 sharded 가 아닌 일반 pubsub 기반 — Cluster 환경에서는 각 노드에 개별 subscribe 가 필요해요. 68편 Cluster 에서 풀어요.
부작용·제한
Pub/Sub 의 본질적 한계 — Fire-and-Forget
54편 Stream 비교에서 본 대로, Pub/Sub 은 휘발성. 구독자가 끊겨 있는 동안 발생한 이벤트는 영원히 손실.
즉 keyspace notifications 는 항상 살아 있는 워커 가 listen 해야 하고, 워커 재시작 동안 발생한 만료 이벤트는 손실 되며, 재처리 보장 없음(at-most-once, 최대 1회 전달) 이라는 세 가지 제약을 그대로 안고 가요.
100% 보장이 필요한 후속 처리 라면 keyspace notifications 만으로 부족해요. Stream + Consumer Group (54편) 으로 상태 변화를 명시적으로 publish 하는 패턴이 더 안전.
성능 영향
이벤트 발송 자체가 비용 — 모든 명령이 발송 대상이면 상당한 CPU 사용. 운영 환경에서는 최소 flag 만 켜는 게 원칙. KEA 같은 모두 켜기 는 개발 환경만.
시험 직전 한 번 더 — Keyspace Notifications 함정 압축 노트
- Keyspace Notifications = 키 이벤트를 Pub/Sub 으로 발송
- 기본값 = 꺼져 있음 (
notify-keyspace-events "") — "왜 안 오나" 원인 1순위 - 두 채널 =
__keyspace__(키 중심) ·__keyevent__(이벤트 중심) - keyspace 채널 메시지 = 명령 이름 (set·del·expired 등)
- keyevent 채널 메시지 = 키 이름
- 자주 쓰는 채널 =
__keyevent__(이벤트 중심 광역) - 설정 flag 핵심 = K·E·g·$·l·s·h·z·x·e·t·m·n·A
- K 또는 E 둘 중 하나는 반드시 — 안 박으면 발송 안 됨 (원인 2순위)
Ex= 만료 이벤트만 keyevent 채널로 — 세션 만료 후속 처리 표준Exe= 만료 + eviction 모두KEA= 모두 켜기 (개발용)- 런타임 변경 =
CONFIG SET notify-keyspace-events ... - 영구 =
redis.conf또는CONFIG REWRITE - 만료 이벤트는 정확한 시각 보장 X — lazy + active expiration 때문에 지연 가능
- 정확한 시각 필요 = 별도 스케줄러 또는 Sorted Set delayed queue (53편)
- Pub/Sub 기반 = fire-and-forget, 구독자 끊겨 있는 동안 손실
- 100% 보장 필요 = Stream + Consumer Group (54편) 으로 대체
- Cluster 환경 = 각 노드 개별 subscribe 필요 (전통 Pub/Sub 모델)
- Redis 7+ Sharded Pub/Sub =
SSUBSCRIBE·SPUBLISH(별도) - 성능 영향 = 모든 명령 발송 시 CPU 부담 — 최소 flag 만 켜는 게 원칙
- 자주 쓰는 패턴 = 세션 만료 후속 처리 · 캐시 무효화 트래커 · eviction 모니터링
공식 문서: Redis Keyspace Notifications 에서 모든 flag 와 사양을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 51편 — Redis List + 큐·캡 리스트 패턴
- 52편 — Redis Set + 집합 연산 패턴
- 53편 — Redis Sorted Set + 랭킹·Sliding Window Rate Limiter 패턴
- 54편 — Redis Stream + Consumer Group + Kafka 비교
- 55편 — Redis TTL + Eviction Policy 8가지
다음 글: