Redis를 메시지 브로커로 사용해야 하는 경우는 언제인가요?

Redis의 두 가지 주요 기능인 Pub/Sub과 Streams를 활용하여 메시지 브로커로 사용하기에 이상적인 시나리오를 알아보세요. 이 포괄적인 가이드는 Redis 메시징의 성능 이점, 낮은 지연 시간, 인프라 이점을 자세히 설명합니다. 일시적인 Pub/Sub과 지속적인 Streams의 중요한 차이점, Kafka와 같은 전용 브로커와 비교한 한계를 이해하고, 간단한 캐시 무효화부터 강력하고 가벼운 작업 큐까지 실행 가능한 사용 사례를 찾아 비동기 통신 요구에 맞는 올바른 도구를 선택하는 데 도움을 드립니다.

Redis를 메시지 브로커로 사용해야 하는 경우는 언제인가요?

Redis는 작업이 작고 빠르며 이미 Redis에 저장된 데이터와 가까울 때 좋은 메시지 브로커가 될 수 있습니다. 또한 강력한 전달 보장, 긴 보존, 라우팅 기능, 또는 메시지 기록이 메모리보다 훨씬 클 때도 편안하게 작동하는 브로커가 필요한 경우에는 잘못된 도구가 될 수 있습니다.

"Redis가 메시징을 할 수 있나요?"라고 묻는 대신 "내가 허용할 수 있는 실패 모드에 맞는 Redis 메시징 기본 요소는 무엇인가요?"라고 묻는다면 결정이 더 쉬워집니다.

Redis는 여러 패턴을 제공하지만, 이 질문에 가장 중요한 두 가지는 다음과 같습니다:

  • 라이브 방송을 위한 Pub/Sub.
  • 소비자 그룹을 사용한 내구성 있는 로그형 메시지 처리를 위한 Streams.

멀리서 보면 비슷해 보입니다. 운영적으로는 비슷하지 않습니다.

메시지 누락이 허용되는 경우 Pub/Sub 사용

Redis Pub/Sub은 라이브 방송입니다. 발행자가 채널로 보냅니다. 연결된 구독자가 메시지를 수신합니다. Redis는 연결이 끊긴 구독자를 위해 해당 메시지를 저장하지 않으며, 기본 제공 확인 응답도 없습니다.

이는 일부 작업에 완벽합니다:

SUBSCRIBE cache:invalidations
PUBLISH cache:invalidations 'product:123'

한 애플리케이션 인스턴스가 잘못된 시점에 재시작되어 해당 무효화를 놓친다고 해도 세상이 끝나는 것은 아닙니다. 로컬 캐시에는 TTL, 버전 검사 또는 복구할 수 있는 다른 방법이 있어야 합니다. Pub/Sub은 알림 경로일 뿐, 진실의 원천이 아닙니다.

좋은 Pub/Sub 사용 사례:

  • 애플리케이션 인스턴스 간 캐시 무효화.
  • 클라이언트가 현재 상태에만 관심이 있는 라이브 UI 업데이트.
  • "사용자가 입력 중입니다" 또는 "작업자 하트비트가 변경되었습니다"와 같은 상태 신호.
  • 가벼운 배포 또는 구성 변경 알림.
  • 메시지 누락이 허용되는 팬아웃 이벤트.

나쁜 Pub/Sub 사용 사례:

  • 결제 처리.
  • 결국 전송되어야 하는 이메일 작업.
  • 건너뛰어서는 안 되는 재고 업데이트.
  • 감사 로그.
  • 연결이 끊긴 소비자가 나중에 따라잡아야 하는 모든 것.

Pub/Sub은 덜 하기 때문에 빠릅니다. 그것이 트레이드오프입니다.

소비자가 따라잡아야 하는 경우 Streams 사용

Redis Streams는 스트림 데이터 구조에 항목을 저장합니다:

XADD orders:events * order_id 42 status paid

소비자는 위치에서 읽을 수 있습니다:

XREAD COUNT 10 STREAMS orders:events 0

소비자 그룹을 사용하면 여러 작업자가 작업을 공유할 수 있습니다:

XGROUP CREATE orders:events order-workers 0 MKSTREAM
XREADGROUP GROUP order-workers worker-1 COUNT 10 STREAMS orders:events >
XACK orders:events order-workers 1740000000000-0

소비자 그룹을 사용하면 작업자에게 전달된 메시지는 XACK으로 확인될 때까지 보류 항목 목록에 남아 있습니다. 작업자가 읽은 후 확인하기 전에 죽으면 다른 작업자가 보류 중인 작업을 검사하고 청구할 수 있습니다. 이를 통해 소비자를 올바르게 구축하면 최소 한 번 처리가 가능합니다.

최소 한 번은 중복이 가능함을 의미합니다. 작업자는 멱등성을 가져야 합니다. 예를 들어, 이메일 작업자는 다시 보내기를 시도하기 전에 email_job_id=abc123이 전송되었음을 기록해야 합니다. 주문 작업자는 동일한 스트림 항목을 두 번 보더라도 두 번 청구하지 않도록 해야 합니다.

좋은 Streams 사용 사례:

  • 가벼운 백그라운드 작업.
  • 짧은 중단 후 재생이 필요한 내부 서비스 이벤트.
  • 소규모에서 중간 규모의 이벤트 로그.
  • 각 작업이 그룹 내 한 명의 작업자에 의해 처리되어야 하는 작업자 풀.
  • 제한된 보존 기간의 활동 피드 또는 상태 변경 로그.

Streams는 공짜가 아닙니다. 항목은 설계에 의해 트리밍되거나 만료되지 않는 한 Redis 메모리에 남아 있습니다. 사용 중인 스트림을 트리밍하지 않으면 스트림이 다음 메모리 사고가 됩니다.

트리밍 사용:

XADD orders:events MAXLEN ~ 100000 * order_id 42 status paid
XTRIM orders:events MAXLEN ~ 100000

~를 사용한 근사 트리밍은 일반적으로 정확한 트리밍보다 저렴합니다. 희망이 아닌 복구 요구 사항에 따라 보존을 선택하세요.

Redis Lists는 여전히 간단한 큐에 유용합니다.

Streams가 존재하기 전에는 많은 Redis 큐가 리스트를 사용했습니다:

LPUSH jobs:email '{"to":"[email protected]"}'
BRPOP jobs:email 5

Lists는 특히 차단 팝 동작이 필요하고 소비자 그룹이나 기록이 필요하지 않은 매우 간단한 큐에 여전히 적합합니다. 제한 사항은 복구입니다. 작업자가 작업을 꺼내고 완료하기 전에 충돌하면 추가 부기(bookkeeping)를 하지 않는 한 해당 작업은 사라집니다.

BRPOPLPUSH 또는 BLMOVE를 사용하여 작업을 처리 목록으로 이동한 다음 성공 후 제거하는 패턴이 있습니다. 이러한 패턴은 작동할 수 있지만, 보류 추적, 재시도 및 여러 소비자가 필요해지면 Streams가 일반적으로 더 명확한 시작점을 제공합니다.

단순함이 브로커 기능보다 더 중요할 때 Redis 선택

Redis 메시징은 Redis가 이미 스택의 일부이고 워크로드가 적당할 때 매력적입니다. 다른 분산 시스템을 운영하지 않아도 됩니다. 개발자는 이미 Redis 클라이언트, 모니터링, 자격 증명 및 배포 경로를 이해하고 있습니다.

이는 타당한 이유입니다. 운영 단순성은 실제 가치가 있습니다.

Redis는 또한 지연 시간이 매우 낮습니다. 애플리케이션과 Redis가 동일한 지역이나 사설 네트워크에 있는 경우 작은 알림을 게시하는 것은 일반적으로 저렴하고 빠릅니다. 캐시 무효화나 라이브 상태 업데이트의 경우 더 무거운 브로커가 필요하지 않을 수 있습니다.

Redis를 사용하면 상태 변경과 메시지를 신중하게 결합할 수도 있습니다. Lua 스크립트나 트랜잭션은 하나의 Redis 측 작업에서 키를 업데이트하고 스트림에 추가할 수 있습니다. 이는 Redis가 중앙 상태 보유자인 소규모 시스템에 유용할 수 있습니다.

문제는 Redis가 우연히 모든 것을 처리하는 브로커가 되어서는 안 된다는 것입니다. 모든 서비스가 보존 계획 없이 대용량 스트림을 추가하기 시작하면 "간단한" 선택이 과부하된 인메모리 로그 저장소가 됩니다.

실패 처리가 제품일 때 전용 브로커 선택

Kafka, RabbitMQ, Pulsar, NATS JetStream 및 클라우드 큐 서비스는 메시징이 빠르게 복잡해지기 때문에 존재합니다.

다음과 같은 기능이 필요할 때 전용 브로커를 사용하세요:

  • 몇 주, 몇 달 또는 몇 년 단위의 긴 보존.
  • 메모리보다 훨씬 큰 메시지 기록.
  • 브로커에 내장된 데드 레터 큐 및 재시도 정책.
  • 지연 전달, 우선 순위, 라우팅 키, 교환 또는 토픽 파티셔닝.
  • 메시징용으로 설계된 교차 지역 복제 패턴.
  • 동일한 이벤트 기록을 재생하는 많은 독립적인 소비자 그룹.
  • 지연, 오프셋, 리밸런싱 및 감사에 대한 강력한 도구.

Kafka는 일반적으로 대용량 이벤트 파이프라인과 재생 가능한 로그에 더 적합합니다. RabbitMQ는 정교한 라우팅, 확인 및 작업 큐에 더 적합한 경우가 많습니다. 클라우드 큐는 관리되는 내구성과 간단한 운영 경계를 원할 때 더 좋습니다.

Redis Streams는 유용한 프로덕션 워크로드를 처리할 수 있지만 여전히 Redis입니다. 데이터는 메모리 중심이며, 지속성 설정을 이해해야 하며, 브로커 기능은 의도적으로 전용 시스템보다 작습니다.

구체적인 결정 방법

Redis를 선택하기 전에 다음 질문을 해보세요:

  1. 소비자가 데이터 손실 없이 메시지를 놓칠 수 있습니까?
  2. 연결이 끊긴 소비자가 따라잡아야 합니까?
  3. 메시지를 얼마나 오래 보관해야 합니까?
  4. 보관된 메시지 데이터가 Redis 메모리에 편안하게 들어갈 수 있습니까?
  5. 작업자가 중복 메시지를 안전하게 처리합니까?
  6. 데드 레터 큐, 지연 재시도, 우선 순위 또는 라우팅 규칙이 필요합니까?
  7. 이 트래픽이 Redis 캐싱이나 세션을 방해합니까?

메시지 누락이 허용된다면 Pub/Sub으로 충분할 수 있습니다.

소비자가 따라잡아야 하고 보존이 제한적이라면 Streams로 충분할 수 있습니다.

메시지 데이터에 긴 보존, 여러 팀의 재생, 복잡한 브로커 동작 또는 강력한 운영 분리가 필요하다면 전용 브로커를 사용하세요.

예: 캐시 무효화

애플리케이션이 제품 페이지를 로컬 프로세스 메모리와 Redis에 저장합니다. 제품이 변경되면 관리 서비스가 게시합니다:

PUBLISH cache:invalidate product:123

cache:invalidate를 구독하는 모든 웹 인스턴스는 로컬 복사본을 삭제합니다. 한 웹 인스턴스가 메시지를 놓치더라도 로컬 항목에는 5분 TTL이 있으며 다음 요청 시 제품 버전 필드도 확인합니다. 복구 경로가 있으므로 Pub/Sub으로 충분합니다.

여기서 Kafka를 사용하면 가치보다 운영 부담이 더 커질 수 있습니다.

예: 백그라운드 이메일 작업

사용자가 가입하고 환영 이메일을 보내야 합니다. 작업자가 1분 동안 다운되더라도 작업은 나중에 전송되어야 합니다. Pub/Sub은 적합하지 않습니다.

Redis Streams가 작동할 수 있습니다:

XADD email:jobs MAXLEN ~ 100000 * job_id abc123 type welcome user_id 42

작업자는 소비자 그룹을 통해 읽고, 이메일을 보내고, job_id를 완료로 기록하고, XACK을 호출합니다. 모니터는 보류 중인 작업을 확인하고 오래된 작업을 회수합니다. 이는 적당한 내부 큐에 합리적입니다.

이메일 전송이 대규모가 되고, 지연 재시도, 데드 레터 처리, 고객별 속도 제한 및 풍부한 운영 대시보드가 필요해지면 전용 큐가 더 나아 보이기 시작합니다.

예: 감사 이벤트

감사 이벤트는 일반적으로 내구성, 검색, 보존, 때로는 법적 또는 규정 준수 처리가 필요합니다. Redis Streams는 짧은 버퍼로 도움이 될 수 있지만, Redis가 최종 감사 저장소가 되어서는 안 됩니다. 보존 및 검토를 위해 설계된 내구성 있는 로그, 데이터베이스, 객체 스토리지 파이프라인 또는 관리형 이벤트 서비스를 사용하세요.

Redis를 선택하는 경우 운영 참고 사항

Pub/Sub의 경우:

  • client-output-buffer-limit pubsub를 구성하세요.
  • 전용 구독자 연결을 사용하세요.
  • 재연결 및 재구독 동작을 구축하세요.
  • 메시지를 힌트로 취급하고 내구성 있는 사실로 취급하지 마세요.

Streams의 경우:

  • MAXLEN, MINID 또는 명시적 트리밍으로 보존 정책을 설정하세요.
  • 보류 항목을 모니터링하세요.
  • 소비자를 멱등성으로 만드세요.
  • 작업이 성공한 후에만 XACK을 사용하세요.
  • 중단된 메시지가 어떻게 청구되고 재시도되는지 계획하세요.
  • 메모리, 지속성 및 복제 지연을 주시하세요.

Redis는 작업에 맞는 Redis 부분을 선택할 때 좋은 메시지 브로커입니다. Pub/Sub은 라이브 신호입니다. Streams는 제한된 내구성 로그입니다. Redis가 이미 실행 중이라는 이유만으로 선택되어서는 안 되지만, 실패 모드가 애플리케이션과 일치할 때 가장 간단한 올바른 답이 될 수 있습니다.

불편한 중간 지점

많은 팀이 중간에 위치합니다: Pub/Sub은 너무 손실이 많고, Kafka는 너무 크게 느껴지며, RabbitMQ는 운영할 시스템이 하나 더 늘어난 것처럼 느껴집니다. Redis Streams는 그곳에서 좋은 답이 될 수 있지만, 마법의 리스트가 아닌 실제 큐로 취급할 때만 가능합니다.

건강한 Streams 설계에는 다음과 같은 세부 사항에 대한 소유권이 있습니다:

  • 누가 스트림과 소비자 그룹을 생성합니까?
  • 예상되는 소비자 수는 얼마입니까?
  • 메시지가 회수되기 전 최대 보류 기간은 얼마입니까?
  • 반복 실패 후 어떻게 됩니까?
  • 얼마나 많은 스트림 기록이 보존됩니까?
  • 증가하는 지연을 보여주는 대시보드나 알림은 무엇입니까?

이러한 답변이 없으면 Streams는 조용히 실패할 수 있습니다. 작업자가 메시지를 읽고 XACK 전에 충돌하여 항목이 영원히 보류 상태로 남을 수 있습니다. 다른 작업자가 이를 청구하지 않을 수 있습니다. 아무도 트리밍을 구성하지 않았기 때문에 스트림 길이가 계속 증가할 수 있습니다. Redis 메모리는 증가하지만 애플리케이션 팀은 "큐가 내구성이 있다"고 생각하여 인스턴스가 압박을 받을 때까지 알아차리지 못합니다.

간단한 작업자는 일반적으로 다음 루프를 수행해야 합니다:

작은 배치 읽기
각 메시지를 멱등성으로 처리
성공한 메시지만 확인
주기적으로 보류 메시지 검사
오래된 보류 메시지 청구
보존 정책에 따라 트리밍

이는 Pub/Sub보다 더 많은 작업이며, 그것이 요점입니다. 내구성은 항상 어딘가로 복잡성을 이동시킵니다. Redis Streams는 브로커 측을 상당히 작게 유지하지만, 애플리케이션은 여전히 재시도, 데드 레터 동작 및 멱등성을 소유합니다.

팀 내 누구도 이러한 세부 사항을 소유하고 싶어하지 않는다면, 관리형 큐가 첫날에는 더 무거워 보일지라도 장기적으로 더 저렴할 수 있습니다. 가장 좋은 브로커는 벤치마크에서 가장 빠른 브로커가 아닙니다. 팀이 오전 3시에 추측 없이 운영할 수 있는 실패 동작을 가진 브로커입니다.