대기열을 만들 때, Redis와 Kafka는 어떻게 다를까?

“대기 시스템에는 왜 Kafka 말고 Redis를 쓰지?”

요청을 쌓아 두고 순서대로 처리한다는 점에서는 Kafka도 떠오른다. 다만 사용자에게 “내 앞에 몇 명이 남았는지”를 보여주려면, 메시지 전달에 더해 현재 대기 상태를 조회할 수 있어야 한다.

티켓 예매처럼 접속자를 차례로 들여보내는 가상 대기실을 기준으로 두 기술의 역할을 정리했다. 실제 구축 후기나 성능 비교를 다루는 글은 아니다.

무엇을 기다리는 대기열인가

주문 요청을 받아 나중에 처리하는 작업 큐와, 예매 페이지에 들어가기 전 기다리는 대기실은 요구사항이 다르다.

작업 큐는 처리할 일을 전달하고 결과와 실패를 관리한다. 대기실에서는 “내 앞에 몇 명이 있나”, “이미 참여한 사용자인가”, “지금 들어가도 되나”에 계속 답해야 한다. 입장 가능한 인원과 입장 권한의 유효 시간도 관리해야 한다.

Redis는 이런 현재 상태를 조회하고 바꾸는 데 활용할 수 있다. Kafka는 이벤트를 로그로 보관하고 소비자가 읽어 처리하도록 전달하는 데 쓰인다. 아래 그림은 두 역할을 나눈 예시다.

Redis를 통한 대기 순번 조회와 입장 제어, Kafka의 파티션별 이벤트 소비를 비교한 개념도
그림 1. 대기 화면의 순번과 뒤에서 처리되는 이벤트는 서로 다른 문제다. 두 기술을 반드시 함께 써야 한다는 뜻은 아니다.

Redis로 표현하기 쉬운 상태

Redis의 Sorted Set에는 member와 score를 함께 저장한다. 사용자를 식별하는 값을 member로, 순서를 정할 값을 score로 두면 score 순으로 정렬된다. ZRANK로 특정 사용자의 현재 위치도 조회할 수 있다. 반환값은 0부터 시작하므로, 첫 번째 사용자의 rank는 0이다.

다만 score가 같으면 삽입 순서가 아니라 member의 사전식 순서로 정렬된다. 밀리초 단위 시간값을 넣더라도 같은 값이 나올 수 있다. 선착순이 중요하다면 무엇을 도착 순서로 볼지 정하고, 동점을 어떻게 처리할지도 설계해야 한다.

같은 member는 중복 저장되지 않지만, 일반 ZADD는 기존 member의 score를 갱신한다. 새로고침할 때마다 순서가 바뀌지 않도록 기존 값을 유지하려면 ZADD NX를 사용할 수 있다. 이 역시 같은 식별값으로 다시 요청한다는 전제다. 식별값을 매번 새로 발급한다면 중복 참여를 막아 주지 못한다.

Sorted Set으로 대기 순서를 관리하더라도 입장 허용 여부와 만료 처리는 별도로 필요하다. 허용된 사용자가 들어오지 않은 채 유효 시간이 끝났을 때, 그 자리를 다음 사용자에게 넘기는 정책도 정해야 한다.

남은 자리가 하나인데 요청은 두 개라면

입장 가능한 자리가 하나 남은 상황을 가정했다. 서버 A와 B가 각각 남은 인원을 조회해 둘 다 1을 읽고 사용자를 입장시키면 정원을 넘길 수 있다. 개별 Redis 명령이 원자적이어도, 조회와 갱신 사이에는 다른 요청이 끼어들 수 있기 때문이다.

입장 가능 인원이 한 명일 때 두 요청이 조회와 갱신을 따로 수행하는 경우와 하나의 원자적 처리로 수행하는 경우의 비교
그림 2. 남은 자리를 확인하는 단계와 입장 상태를 바꾸는 단계를 묶어야, 뒤의 요청이 앞의 요청이 반영된 상태를 보고 판단할 수 있다.

허용 인원 확인, 대기열에서 제거, 입장 상태 기록은 하나의 원자적 처리로 묶어야 한다. Redis에서는 Lua 스크립트로 실행 중 다른 명령이 끼어들지 않게 할 수 있다.

원자적 실행이 롤백까지 보장하지는 않는다. 스크립트 실행 도중 오류가 나도 앞서 반영한 쓰기는 자동으로 되돌아가지 않는다. 입력과 상태를 먼저 검증하고, 실패 시 상태가 어떻게 남는지도 고려해야 한다. Redis Cluster에서 여러 키를 함께 다룬다면 같은 해시 슬롯에 배치하는 제약도 확인해야 한다.

동시성 처리와 장애 복구도 별개의 문제다. 영속화 설정과 장애 상황에 따라 대기 정보가 유실될 수 있으므로, 재시작 뒤 순번과 입장 상태를 어떻게 복구할지 따로 정해야 한다.

Kafka에서 말하는 순서

Kafka는 이벤트를 로그에 보관하고 소비자가 읽는 구조다. 보관 중인 이벤트를 다시 읽을 수 있고, 일반적인 컨슈머 그룹에서는 파티션을 소비자에게 나눠 맡긴다. 각 파티션은 같은 그룹 안에서 한 시점에 하나의 소비자에게 할당된다.

순서의 기준은 파티션이다. 서로 다른 파티션을 아우르는 전체 순서는 기본적으로 보장하지 않는다. 또 로그에 기록된 순서와 애플리케이션이 병렬로 처리한 작업의 완료 순서는 구분해야 한다.

Kafka로 대기 시스템을 구성한다면 이벤트의 offset과 사용자의 대기 상태를 연결할 구조가 필요하다. offset만으로는 현재 순번이나 입장 권한을 알 수 없고, 취소와 만료까지 반영해 상태를 계산하고 조회해야 한다.

대기 상태와 후속 이벤트의 역할 나누기

가상 대기실에서는 사용자별 순번과 입장 상태를 직접 조회하고 바꿔야 한다. Redis의 Sorted Set과 상태 저장 기능을 검토하는 이유도 여기에 있다. 단순한 속도 비교보다 어떤 상태를 관리할지가 먼저다.

예매 완료 이벤트를 여러 서비스가 소비하거나 기록을 다시 읽어 처리해야 한다면 Kafka를 검토할 수 있다. 필요에 따라 Redis가 입장을 제어하고 Kafka가 후속 이벤트를 전달하도록 역할을 나눌 수 있다.

이 대기실에서 먼저 풀어야 할 일은 현재 순번을 알려 주고 정해진 인원만 입장시키는 것이다. 후속 이벤트의 전달과 재처리는 그다음 요구사항으로 나눠 판단할 수 있다.

참고 자료

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다