콘텐츠로 이동

가이드 홈 | 이전: Location | 다음: 운영 — 메트릭 · drain · readiness

11. Monitoring — 상태 관측과 진단

이 장의 계약 소유 문서Java monitoring 공개 인터페이스가 다룬다. 이 챕터는 그 계약이 노출하는 네 관측 표면을 사용법 중심으로 설명한다.

handler 호출만으로는 운영을 다 볼 수 없다. 연결이 준비되었는지, 어느 peer가 빠졌는지, 메시지가 어디서 실패했는지도 framework 표면에서 읽어야 한다. Java framework는 이를 세 갈래로 노출한다 — 상태 snapshot과 Flow.Publisher, 메시지 흐름 기록, Micrometer 계기다.

runtime event를 bean handler로 받는 표면은 없다. 관측은 전부 아래 세 갈래를 통한다.

1. 관측 표면

무엇을 보나 표면 어디서 다루나
Host lifecycle(relocate · drain · readiness) ZLinkFrameworkRuntime.status() · observe() 12. 운영 §6.1
MeshNode의 node · peer · channel 준비 상태 ZLinkRouteMeshRuntime.snapshot(...) · observe(...) 12. 운영 §5
ClientServer channel의 target 상태 ZLinkClientServerRuntime 이 챕터 §2
pub/sub channel의 publisher 상태 ZLinkFanoutRuntime 이 챕터 §2
Location store 상태와 topology ZLinkLocationRuntimeQuery 10. Location §4
메시지 수신 · dispatch · 실패와 흐름 configureDispatch()의 message flow 이 챕터 §3
CCU · 큐 깊이 같은 수치 Micrometer MeterRegistry 이 챕터 §4

넷 다 bean으로 주입받는다. Spring 컨테이너에 등록되어 있으므로 생성자 인자로 선언하면 된다.

2. 상태 snapshot과 status stream

상태 표면은 모두 같은 모양이다 — snapshot(...)이 immutable record 한 장을, observe(...)가 그 이후 변화를 Flow.Publisher로 준다.

@Service
public class MeshWatcher {
    private final ZLinkRouteMeshRuntime meshRuntime;

    public MeshWatcher(ZLinkRouteMeshRuntime meshRuntime) {
        this.meshRuntime = meshRuntime;
    }

    public boolean ready() {
        // 지금 값 한 장.
        ZLinkMeshNodeSnapshot snapshot = meshRuntime.snapshot("game.room");
        return meshRuntime.isReady("game.room");
    }

    public void watch(Flow.Subscriber<ZLinkObservedStatus<ZLinkMeshNodeSnapshot>> subscriber) {
        // capacity를 넘기면 느린 구독자는 중간 값을 건너뛴다.
        // 건너뛴 개수는 각 항목의 loss()에 담겨 온다.
        meshRuntime.observe("game.room", 64).subscribe(subscriber);
    }
}

observe(...)ZLinkObservedStatus<T>를 준다. status()가 변경 뒤의 완전한 snapshot이고 — 바뀐 field만 담은 event가 아니라 매번 전체 record다 — loss()가 이 구독이 지금까지 놓친 개수다. 이전 값과 비교할 일이 있으면 구독하는 쪽에서 보관한다.

loss()는 둘로 나뉜다. coalescedCount()는 합치기로 건너뛴 중간 상태 수이고, discardedTerminalCount()는 보관 상한을 넘겨 영영 못 보게 된 terminal 상태 수다. 앞의 것은 최신 값을 받았다는 뜻이지만 뒤의 것은 그렇지 않다.

snapshot(...) observe(...)
무엇을 주나 호출 시점의 record 하나 변경마다 완전한 record
언제 쓰나 운영 endpoint 응답, 단발 확인 상태 전이를 기록하거나 반응할 때
놓칠 수 있나 해당 없음 capacity를 넘기면 중간 값을 건너뛴다

Flow.Publisher는 JDK 표준 reactive stream이다. Reactor를 쓴다면 JdkFlowAdapter.flowPublisherToFlux(...)로, Kotlin이면 asFlow()로 감싼다.

Peer 상태는 Node RID와 현재 상태, 사용할 수 없는 이유만 담는다. 연결 의도(connection intent) · discovery source · lifecycle generation은 framework 내부 상태라 공개하지 않는다.

3. 메시지 흐름 추적

메시지 하나가 어디서 어떻게 끝났는지는 message flow로 본다. 수준은 configureDispatch()가 정한다.

@Bean
ZLinkFrameworkConfigurer zlink(PlaySettings settings) {
    return options -> {
        options.configureDispatch()
            .messageFlow(ZLinkMessageFlowLogMode.ERRORS) // 기본값 — 실패와 backpressure만.
            .traceSampleRate(1.0)
            .includeMessageSizes(true);
    };
}
수준 남기는 것
OFF 남기지 않는다
ERRORS(기본) dispatch 실패와 backpressure
NORMAL 위 + 수신 · dispatch · 완료 같은 주요 전이
DETAILED 위 + 개별 메시지 단위 상세 진단

운영에서는 ERRORS로 두고 필요할 때만 올린다. DETAILED는 메시지마다 기록을 남기므로 처리량이 많은 구간에서 그 자체가 부하가 된다.

Framework는 application이 구성한 standard logger·trace·metric provider에 structured record를 기록한다. Message-flow callback observer, runtime error sink, raw event DTO나 diagnostics file path는 public API가 아니다. Provider 호출 실패는 원래 message operation의 terminal 결과와 격리한다.

처리기가 없는 dispatch의 동작은 unhandled()가 정한다 — setRequest · setSend · setPublish로 갈래마다, setSendLogLevel · setPublishLogLevel로 기록 수준을 정한다.

4. 메트릭

Framework 계기는 Micrometer로 나간다. Spring Boot Actuator를 쓰면 registry가 이미 컨테이너에 있으므로 추가 배선이 필요 없다. 이름을 바꾸거나 공통 tag를 붙이려면 ZLinkMetricsCustomizer bean을 둔다.

@Bean
ZLinkMetricsCustomizer zlinkMetrics(PlaySettings settings) {
    return registry -> registry.config()
        .commonTags("node", settings.instanceName());
}

계기 이름은 zlink.으로 시작한다. 정확한 이름 · 종류 · 단위 · label은 Runtime metric과 집계 규칙이 소유한다.

현재 Java runtime이 방출하는 계기는 계약의 일부뿐이다. 계약이 정의한 47개 중 14개만 나오고, request 관련 셋은 계약 이름(zlink.mesh_node.request.*)이 아니라 zlink.channel.request.*로 나온다. 대시보드를 만들기 전에 실제 방출 이름을 확인한다.

5. readiness와 liveness

Java에는 별도 health check 표면이 없다. runtime 상태로 판정한다.

@Component
public class ZLinkReadinessIndicator implements HealthIndicator {
    private final ZLinkFrameworkRuntime runtime;

    @Override
    public Health health() {
        return runtime.status().isReady()
            ? Health.up().build()
            : Health.outOfService().build();
    }
}

store 연결처럼 잠깐 끊길 수 있는 의존성은 readiness에만 반영한다. liveness에 넣으면 store가 잠시 끊겼을 때 오케스트레이터가 프로세스를 죽인다.

6. 자주 발생하는 문제

  • observe(...)를 구독했는데 값이 안 온다Flow.Publisher는 구독해야 흐른다. subscribe(...)를 부르고 Subscription.request(n)으로 수요를 알렸는지 본다.
  • 상태 전이 일부가 안 보인다observe(...)의 capacity를 넘겨 건너뛴 것이다. capacity를 늘리고 구독자가 더 빨리 소비하게 한다.
  • flow 기록이 비어 있다 → 기본 수준이 ERRORS라 정상 흐름은 남지 않는다. NORMAL 이상으로 올린다.
  • 메트릭이 안 보인다 → Actuator와 registry가 컨텍스트에 있는지 본다. framework는 registry에 계기를 올릴 뿐 registry 자체를 만들지 않는다.
  • store가 잠깐 끊겼는데 프로세스가 재시작된다 → store 상태가 liveness에 들어가 있다. readiness로 옮긴다.

7. 관련 문서