NATS JetStream 장애 분석

NATS JetStream 장애 분석

1. 개요

MSA(Microservice Architecture) 기반 애플리케이션은 하나의 거대한 시스템을 여러 개의 독립적인 서비스로 분리하여 구성하는 구조입니다. 각 서비스는 서로 다른 기능과 책임을 가지지만, 실제 서비스를 제공하기 위해서는 서비스 간 데이터 전달과 상태 공유가 지속적으로 이루어져야 합니다. 이러한 서비스 간 통신 방식 중 하나로 메시징 방식이 사용되며, 비동기적인 데이터 전달이 필요한 경우 메시지 브로커(Message Broker)를 활용할 수 있습니다.

A 프로젝트에서는 선박 내 여러 서비스 간 센서 데이터와 이벤트 데이터를 안정적이고 효율적으로 전달하기 위해 NATS 기반의 영속 메시징 시스템인 NATS JetStream을 사용하고 있습니다. NATS JetStream은 서비스 간 비동기 메시지 전달뿐만 아니라 메시지 저장, Consumer 상태 관리 및 재전송 등의 기능을 제공하여 지속적으로 발생하는 선박 데이터를 안정적으로 처리할 수 있도록 지원합니다.

그러나 실제 개발 및 운영 과정에서는 선박 서버의 강제 종료, 애플리케이션 비정상 종료 등 다양한 상황이 발생하게 되고 이러한 환경에서 NATS JetStream과 관련된 예기치 못한 장애를 빈번하게 경험하였습니다. 본 글에서는 NATS JetStream 운영 과정에서 발생한 장애 사례를 바탕으로 주요 발생 원인을 분석하고, 향후 유사한 장애가 발생할 경우 Consumer의 상태값을 기반으로 원인을 식별하고 대응할 수 있도록 하는 진단 절차를 정리하고자 합니다.

2. 메시지 처리 장애 발생

서버가 강제 종료되는 등 비정상적으로 종료된 이후 재부팅되었을 때, Stream에는 메시지가 지속적으로 Publish되고 있었음에도 불구하고 Durable Consumer가 해당 메시지를 정상적으로 소비하지 못하는 현상이 발생하였습니다.

본 애플리케이션은 구동 시 기존 Durable Consumer의 존재 여부를 확인하고, 이미 Consumer가 존재하는 경우에는 새롭게 생성하지 않고 기존 Consumer를 그대로 재사용하도록 구현되어 있습니다. Durable Consumer는 단순한 구독 정보뿐만 아니라 메시지가 어디까지 전달되었는지를 나타내는 Delivered Sequence, Ack가 완료된 위치를 나타내는 Ack Floor, 아직 Ack되지 않은 메시지 상태, 재전송 대상 메시지 등의 소비 상태를 지속적으로 유지합니다. 따라서 메시지를 처리하는 도중 서버가 강제로 종료되면 일부 메시지에 대한 Ack 처리가 완료되지 못하거나, Consumer의 전달 및 처리 상태가 정상적으로 마무리되지 않은 상태로 남을 수 있습니다.

서버 재부팅 이후 애플리케이션은 이러한 기존 Durable Consumer를 그대로 재사용하므로, 비정상 종료 직전에 남아 있던 메시지 처리 상태 역시 함께 이어받게 됩니다. 정상적인 경우에는 AckWait과 재전송 정책에 따라 미처리 메시지가 다시 전달되고 이후 메시지 소비가 계속 진행되어야 하지만, Ack Pending 메시지가 누적되거나 Delivered Sequence와 Ack Floor 사이의 처리 상태가 정상적으로 이어지지 않는 등 여러 케이스에 따라 Consumer의 메시지 전달이 지연되거나 중단될 수 있습니다.

3. Consumer 상태값 분석 방법

장애가 발생한 경우 먼저 Consumer 상태 정보를 확인하고, 어느 단계에서 장애가 발생했는지 파악해야 합니다.

Information for Consumer “stream” > “consumer”

Configuration:
    Name: “consumer”
    Pull Mode: true
    Filter Subject: subject.>
    Deliver Policy: All
    Ack Policy: Explicit
    Ack Wait: 30s
    Maximum Deliveries: 3
    Maximum Ack Pending: 1000
State:
    Last Delivered Message:
        Consumer sequence: 15230
        Stream sequence: 185400

    Acknowledgment Floor:
        Consumer sequence: 14230
        Stream sequence: 184400

    OutStanding Acks: 1000
    Redelivered Messages: 15
    Unprocessed Messages: 3270
    Waiting Pulls: 1

항목

의미

확인 사항

Last Delivered Message

Consumer가 어디까지 메시지를 전달했는지

특정 Sequence에서 장시간 멈춰 있는지 확인

Acknowledgment Floor

연속적으로 어디까지 Ack 처리가 완료되었는지

Last Delivered와 차이가 과도하게 큰지 확인

OutStanding Acks

전달되었지만 아직 Ack되지 않은 메시지 수

Maximum Ack Pending 값에 도달했는지 확인

Unprocessed Messages

Consumer에게 아직 전달되지 않은 메시지 수

0보다 큰데 소비가 진행되지 않는지 확인

Redelivered Messages

재전송된 메시지 수

서버 재부팅 이후 비정상적으로 증가하는지 확인

Waiting Pulls

현재 메시지를 요청하고 대기 중인 Pull 요청 수

0이면 애플리케이션의 Pull 루프가 동작하지 않을 가능성 확인

  • 표1. Consumer 주요 상태값과 의미

1) OutStanding Acks가 MaxAckPending에 도달한 경우

OutStanding Acks는 Consumer가 애플리케이션에 전달했지만 아직 Ack를 받지 못한 메시지 수를 의미합니다. 이 값이 MaxAckPending 설정값에 도달하면 JetStream은 Ack가 처리되어 여유가 생길 때까지 새로운 메시지 전달을 제한할 수 있습니다.

Maximum Ack Pending:  1000
OutStanding Acks:     1000
Unprocessed Messages: 3270

위 예시에서는 Stream에 아직 3270개의 메시지가 남아 있지만 Consumer가 이미 허용된 Ack Pending 한도까지 메시지를 전달한 상태입니다. 따라서 애플리케이션에서 Ack가 진행되지 않으면 신규 메시지가 더 이상 전달되지 않아, 외부에서 Publish는 정상인데 Consumer는 아무 메시지도 받지 못하는 현상이 나타날 수 있습니다.

2) Pull 요청이 정상적으로 발생하지 않는 경우

Pull Consumer는 애플리케이션이 fetch() 또는 consume() 방식으로 Pull 요청을 보내야 메시지가 전달됩니다. 따라서 전달할 메시지가 남아 있는데 Waiting Pulls가 계속 0이라면 Consumer 자체보다는 애플리케이션의 Pull 처리 로직을 의심할 수 있습니다.

Unprocessed Messages: 3270
OutStanding Acks:      0
Waiting Pulls:         0

위 상태에서는 Ack에 의해 메시지 전달이 막힌 것은 아니지만, 메시지를 요청하는 Pull 요청이 존재하지 않습니다. 서버 재부팅 이후 Pull 처리 Thread나 consume() 루프가 정상적으로 시작되었는지, NATS 재연결 이후 Subscription이 정상적으로 복구되었는지 확인해야 합니다.

3) Last Delivered와 Ack Floor가 장시간 진행되지 않는 경우

Last Delivered는 Consumer가 어디까지 메시지를 전달했는지를, Ack Floor는 연속적으로 어디까지 Ack가 완료되었는지를 나타냅니다. 두 값 자체의 차이보다 시간이 지나도 두 값이 전혀 진행되지 않는지를 확인하는 것이 중요합니다.

Last Delivered Stream Sequence: 185400
Ack Floor Stream Sequence:      185380
OutStanding Acks:               20
Unprocessed Messages:           3270


몇 초 또는 몇 분이 지나도록 Last Delivered가 185400에서 멈춰 있고 Unprocessed Messages가 계속 증가한다면 Consumer의 소비 흐름이 정체된 상태를 의심할 수 있습니다. 이때 Waiting Pulls, Redelivered Messages, 애플리케이션 로그 등을 함께 확인하여 Consumer 측 문제인지 애플리케이션 처리 문제인지 구분해야 합니다.

이러한 장애 상황에서는 기존 Durable Consumer를 삭제한 후 애플리케이션을 재기동하여 새로운 Consumer를 생성하였을 때 메시지 소비가 정상적으로 이루어졌습니다. 이를 통해 Publisher나 Stream 자체의 문제보다는 기존 Durable Consumer가 비정상 종료 이전부터 유지하고 있던 상태가 메시지 소비 장애에 영향을 주었다는 것을 확인할 수 있었습니다.

이후, 서버가 재시작되는 경우 기존 Consumer를 삭제한 후 재생성하여 메시징 시스템의 상태를 초기화하는 복구 스크립트를 구성하였고 선박에서 NATS JetStream과 관련된 장애 발생을 줄일 수 있었습니다.

4. 마치며

실제 운영 환경에서는 서버 강제 종료나 애플리케이션 비정상 종료와 같은 예기치 못한 상황으로 인해 NATS JetStream의 메시지 처리에 장애가 발생할 수 있습니다. 특히 메시지 처리는 서비스 간 데이터 전달과 연계된 핵심 기능이므로, 장애 발생 시 원인을 신속하게 파악하고 적절한 조치를 취하는 것이 중요합니다.

따라서 향후 유사한 장애가 발생할 경우 본 글에서 정리한 Consumer의 주요 상태값과 각 케이스별 분석 방법을 바탕으로 장애 원인을 빠르게 식별하고 대응함으로써 서비스 중단 시간을 최소화하고 안정적인 운영을 유지할 수 있어야 합니다.

deeeneee

Site footer