📚FOS Study
홈카테고리
홈카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agents 페이지로 이동
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code 멀티 에이전트 — Teams
    • 멀티모달 LLM (Multimodal Large Language Model)
  • architecture 페이지로 이동
    • 캐시 설계 전략 총정리
    • 디자인 패턴
    • 분산 트랜잭션
  • css 페이지로 이동
    • FlexBox 페이지로 이동
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • monitoring 페이지로 이동
  • go 페이지로 이동
    • Go 언어 기본 학습
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • 210812 페이지로 이동
    • 뱅크샐러드 AI Native Server Engineer
    • CJ 올리브영 지원 문항
    • CJ 올리브영 커머스플랫폼유닛 Back-End 개발 지원 자료
    • 마이리얼트립 - Platform Solutions실 회원주문개발 Product Engineer
    • NHN 서비스개발센터 AI서비스개발팀
    • nhn gameenvil console backend 직무 인터뷰 준비
    • 면접을 대비해봅시다
    • Tossplace Node.js Developer
    • 토스플레이스 Node.js 백엔드 컬처핏
  • java 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • OpenTelemetry 란 무엇인가?
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • Data_Structures_and_Algorithms 페이지로 이동
    • Heap 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node.js
    • npm vs pnpm 선택기준은 무엇인가요?
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 기본
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • react 페이지로 이동
    • JSX 페이지로 이동
    • VirtualDOM 페이지로 이동
    • v16 페이지로 이동
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • the-future-company 페이지로 이동
📚FOS Study

개발 학습 기록을 정리하는 블로그입니다.

바로가기

  • 홈
  • 카테고리

소셜

  • GitHub
  • Source Repository

© 2025 FOS Study. Built with Next.js & Tailwind CSS

목록으로 돌아가기
🗄️database/ redis

Redis Pub/Sub & Stream

약 5분
2026년 3월 27일
GitHub에서 보기

Redis Pub/Sub & Stream

Redis는 두 가지 메시지 전달 메커니즘을 제공한다. Pub/Sub은 실시간 브로드캐스트에, Stream은 신뢰성 있는 메시지 큐에 적합하다. 용도가 다르므로 혼동하지 말아야 한다.


Pub/Sub

발행자(Publisher)가 채널에 메시지를 보내면, 해당 채널을 구독 중인 모든 구독자(Subscriber)에게 즉시 전달하는 Fire-and-Forget 방식이다.

핵심 명령어

# 구독 (SUBSCRIBE 이후 블로킹 상태)
SUBSCRIBE channel:notifications
SUBSCRIBE channel:chat:room1 channel:chat:room2  # 다중 채널

# 패턴 구독
PSUBSCRIBE channel:chat:*        # chat: 으로 시작하는 모든 채널
PSUBSCRIBE notification:user:*

# 발행 (반환값: 해당 채널 현재 구독자 수)
PUBLISH channel:notifications "새 주문이 들어왔습니다"
PUBLISH channel:chat:room1 "안녕하세요"

# 구독 해제
UNSUBSCRIBE channel:notifications
PUNSUBSCRIBE channel:chat:*

# 채널 정보 조회
PUBSUB CHANNELS *                # 활성 채널 목록
PUBSUB NUMSUB channel:notifications  # 채널별 구독자 수

동작 흐름

Publisher                Redis              Subscriber A    Subscriber B
    │                      │                     │               │
    │  PUBLISH ch "msg"    │                     │               │
    │─────────────────────>│                     │               │
    │                      │   push "msg"        │               │
    │                      │────────────────────>│               │
    │                      │   push "msg"        │               │
    │                      │─────────────────────────────────────>│
    │                      │                     │               │

한계 (중요)

  • 메시지 영속성 없음: 구독자가 오프라인이면 메시지 유실
  • 확인(ACK) 없음: 전달 성공 여부를 알 수 없음
  • 재처리 불가: 실패한 메시지를 다시 받을 방법 없음
  • 구독자 0명이어도 발행 가능: 아무도 받지 않아도 에러 없음

Pub/Sub은 "최대한 전달하지만 보장은 안 한다(at-most-once)"는 구조다. 중요한 이벤트라면 Stream을 써야 한다.

적합한 사용 사례

  • 실시간 채팅: 메시지 손실을 어느 정도 허용하는 경우
  • 캐시 무효화 브로드캐스트: 여러 서버의 로컬 캐시를 동시에 삭제
  • 실시간 알림: 접속 중인 사용자에게만 전달하면 충분한 알림
  • 이벤트 버스: 서비스 내부 간단한 이벤트 전파
# 캐시 무효화 예시
# 상품 정보 변경 시 모든 서버의 캐시 삭제 신호 전송
PUBLISH cache:invalidate "product:9901"

# 각 서버는 해당 채널 구독 중
# SUBSCRIBE cache:invalidate
# → 수신 시 로컬 캐시에서 해당 키 삭제

Redis Stream

Redis 5.0에 추가된 로그 구조 자료구조. Kafka처럼 메시지를 영속하고, 소비자 그룹(Consumer Group)으로 분산 처리하며, ACK 기반으로 재처리를 보장한다.

핵심 명령어

# 메시지 추가 (ID는 자동 생성: milliseconds-sequence)
XADD orders * userId 1001 amount 50000 itemId 9901
# 반환: "1711500000000-0" 형태의 ID

# 길이 제한하며 추가 (오래된 것 자동 삭제)
XADD orders MAXLEN ~ 10000 * userId 1001 amount 50000

# 메시지 읽기 (0 = 처음부터)
XREAD COUNT 10 STREAMS orders 0

# 특정 ID 이후부터 읽기
XREAD COUNT 10 STREAMS orders 1711500000000-0

# 블로킹 읽기 (새 메시지 올 때까지 대기)
XREAD COUNT 10 BLOCK 5000 STREAMS orders $

# 스트림 길이
XLEN orders

# 범위 조회
XRANGE orders - +              # 전체
XRANGE orders 1711500000000-0 + # 특정 ID 이후

소비자 그룹 (Consumer Group)

여러 소비자가 메시지를 분산 처리하는 구조다. 같은 그룹 내에서 하나의 메시지는 하나의 소비자만 받는다.

# 그룹 생성 ($ = 이후 새 메시지만, 0 = 처음부터)
XGROUP CREATE orders workers $ MKSTREAM

# 소비자 그룹으로 읽기 (> = 아직 전달 안 된 메시지)
XREADGROUP GROUP workers consumer1 COUNT 10 STREAMS orders >

# 처리 완료 ACK
XACK orders workers 1711500000000-0

# 처리 실패/미완료 메시지 확인 (PEL: Pending Entry List)
XPENDING orders workers - + 10

# 오래된 미처리 메시지 강제 재할당
XCLAIM orders workers consumer2 3600000 1711500000000-0

동작 흐름

Producer
    │
    │ XADD orders * ...
    ↓
[Stream: orders]
 ─────────────────────────
 msg-1 | msg-2 | msg-3 | ...
 ─────────────────────────
    │               │
    │ XREADGROUP    │ XREADGROUP
    ↓               ↓
Consumer 1      Consumer 2
 (msg-1)         (msg-2)
    │               │
    │ XACK          │ XACK
    ↓               ↓
  완료 ✅          완료 ✅

Consumer 3 (장애)
 (msg-3) → ACK 없음
    ↓
XPENDING으로 감지
    ↓
XCLAIM으로 Consumer 1에 재할당
    ↓
재처리 ♻️

Spring Boot 연동 예시

// 메시지 발행
StreamOperations<String, String, String> ops =
    redisTemplate.opsForStream();

Map<String, String> message = Map.of(
    "userId", "1001",
    "amount", "50000"
);
ops.add("orders", message);

// 소비자 그룹 설정 (한 번만)
ops.createGroup("orders", ReadOffset.latest(), "workers");

// 메시지 소비
List<MapRecord<String, String, String>> messages =
    ops.read(Consumer.from("workers", "consumer1"),
             StreamReadOptions.empty().count(10),
             StreamOffset.create("orders", ReadOffset.lastConsumed()));

for (var record : messages) {
    try {
        processOrder(record.getValue());
        ops.acknowledge("orders", "workers", record.getId());  // ACK
    } catch (Exception e) {
        // ACK 안 함 → PEL에 남아서 나중에 재처리
        log.error("처리 실패: {}", record.getId(), e);
    }
}

Pub/Sub vs Stream 선택 기준

항목Pub/SubStream
메시지 영속성❌ 없음✅ 있음
전달 보장❌ at-most-once✅ at-least-once
ACK / 재처리❌ 없음✅ 있음
소비자 그룹❌ 없음✅ 있음
오프라인 구독자❌ 메시지 유실✅ 나중에 수신 가능
사용 복잡도낮음높음
적합 용도실시간 브로드캐스트신뢰성 이벤트 큐

판단 기준:

  • 메시지 유실이 허용된다 → Pub/Sub
  • 모든 메시지가 정확히 처리되어야 한다 → Stream
  • Kafka가 과하다 싶은 경량 이벤트 큐 → Stream

Stream vs Kafka 선택 기준

항목Redis StreamKafka
설치/운영 복잡도낮음높음
처리량수만 TPS수십만~수백만 TPS
보존 기간메모리/설정에 따라무제한 (디스크)
메시지 재생(replay)제한적강력
운영 도구기본적Kafka UI, Schema Registry 등

같은 Redis 인프라를 이미 쓰고 있고, 처리량이 크지 않다면 Stream이 경제적이다.


관련 문서

  • Redis 기본 — Stream, Pub/Sub 자료구조 개요
  • 분산 락 — Redisson Pub/Sub 기반 락 대기 메커니즘
database 카테고리의 다른 글 보기수정 제안하기

댓글

댓글을 불러오는 중...
목차
  • Redis Pub/Sub & Stream
  • Pub/Sub
  • 핵심 명령어
  • 구독 (SUBSCRIBE 이후 블로킹 상태)
  • 패턴 구독
  • 발행 (반환값: 해당 채널 현재 구독자 수)
  • 구독 해제
  • 채널 정보 조회
  • 동작 흐름
  • 한계 (중요)
  • 적합한 사용 사례
  • 캐시 무효화 예시
  • 상품 정보 변경 시 모든 서버의 캐시 삭제 신호 전송
  • 각 서버는 해당 채널 구독 중
  • SUBSCRIBE cache:invalidate
  • → 수신 시 로컬 캐시에서 해당 키 삭제
  • Redis Stream
  • 핵심 명령어
  • 메시지 추가 (ID는 자동 생성: milliseconds-sequence)
  • 반환: "1711500000000-0" 형태의 ID
  • 길이 제한하며 추가 (오래된 것 자동 삭제)
  • 메시지 읽기 (0 = 처음부터)
  • 특정 ID 이후부터 읽기
  • 블로킹 읽기 (새 메시지 올 때까지 대기)
  • 스트림 길이
  • 범위 조회
  • 소비자 그룹 (Consumer Group)
  • 그룹 생성 ($ = 이후 새 메시지만, 0 = 처음부터)
  • 소비자 그룹으로 읽기 (> = 아직 전달 안 된 메시지)
  • 처리 완료 ACK
  • 처리 실패/미완료 메시지 확인 (PEL: Pending Entry List)
  • 오래된 미처리 메시지 강제 재할당
  • 동작 흐름
  • Spring Boot 연동 예시
  • Pub/Sub vs Stream 선택 기준
  • Stream vs Kafka 선택 기준
  • 관련 문서