오래된 정상 화면은 어떻게 장애를 숨기는가
오전 10시, 노드 운영 화면에 정상적인 수치가 보인다. 메모리 사용량은 평소와 비슷하고, 대기 중인 작업도 없다. 잠시 다른 일을 하고 10시 3분에 돌아왔는데 화면은 그대로다. 노드가 계속 안정적으로 동작한 것일까?
그사이 상태 조회가 실패했다면 이야기가 달라진다. 화면이 보관한 값은 10시의 기록이다. 노드가 지금도 같은 상태인지, 노드는 정상인데 조회 경로만 끊겼는지 알 수 없다. 이전의 정상 표본을 계속 보여주는 일과 현재 정상이라고 말하는 일은 구별해야 한다.
HTTP 응답과 실제 합의 진행을 구분한 글에서는 서버가 응답해도 거래 확정이 멈출 수 있음을 살펴봤다. 이번에는 그 근거를 읽는 화면의 문제다. 올바른 관측값도 시각과 범위를 잃으면 잘못된 판단을 만들 수 있다. nigo-protocol의 9월 운영 콘솔 구현은 이 경계를 화면에 남기는 데서 출발했다.
마지막으로 받은 값에는 시각이 붙어야 한다
앞의 상황을 조금 더 구체적으로 보자. 다음은 동작을 설명하기 위한 시간선이며, 실제 장애 기록은 아니다. 표의 시각은 하나의 시계로 정리했다.
| 시각 | 일어난 일 | 화면에서 말할 수 있는 것 |
|---|---|---|
| 10:00 | 상태 조회에 성공했다 | 이때 받은 표본에는 이상 징후가 없었다 |
| 10:01 | 다시 조회했지만 응답을 받지 못했다 | 새 상태를 확인하지 못했다 |
| 10:03 | 운영자가 같은 화면을 읽는다 | 마지막 표본은 10:00 것이며 현재 상태는 확인되지 않았다 |
이때 이전 값을 모두 지워버리면 장애 직전의 단서도 사라진다. 반대로 아무 표시 없이 남겨두면 10시 3분의 현재값처럼 보인다. 그래서 콘솔은 일반적인 재조회 실패에서 이전 값을 보존하되, 조회 오류와 함께 마지막 정상 응답이며 현재 상태와 다를 수 있다는 안내를 표시한다. 실패한 조회 때문에 마지막 성공 시각을 새 시각으로 바꾸지도 않는다.
시각도 하나가 아니다. 브라우저의 ‘마지막 성공’은 응답을 받은 시각이다. 프로세스나 저장소 응답에 담긴 서버 관측 시각은 서버가 값을 수집한 시점을 가리킨다. 오래 걸린 조회라면 둘 사이에 간격이 생길 수 있고, 두 컴퓨터의 시계 자체가 다를 수도 있다. 이 시각들을 단순히 빼서 통신 지연이라고 부르면 안 된다.
따라서 화면은 ‘방금 응답을 받았다’와 ‘방금의 상태를 측정했다’를 같은 뜻으로 쓰지 않는다. 운영자는 값과 함께 그 값이 언제 만들어졌고 언제 도착했는지를 읽을 수 있어야 한다.
빈칸을 0으로 채우면 결론이 달라진다
10시 3분 화면에서 CPU 사용률을 가져오지 못했다고 해보자. 이 자리에 0%를 넣으면 화면은 한결 깔끔해진다. 하지만 뜻은 ‘부하를 측정할 수 없다’에서 ‘측정했더니 부하가 없다’로 바뀐다. 저장소 건수를 가져오지 못했을 때 0개로 표시하면, 조회 실패가 빈 저장소라는 주장으로 변한다.
nigo-protocol의 콘솔은 초기 측정 전이나 미지원인 CPU 값, 수집하지 못한 저장소 건수를 미측정으로 남긴다. 미측정이라는 답은 장애 판정도 아니다. 관측하지 못한 범위를 드러내는 답이다. 저장소 지표 조회가 실패했다는 사실만으로 데이터베이스 연결이 끊겼다거나 데이터가 사라졌다고 단정하지 않는다.
한 요청 안에서도 성공과 실패가 섞일 수 있다. 예를 들어 저장소 건수 집계는 실패했지만, 어떤 과거 상태를 보호하고 있는지에 관한 보존 집계는 성공할 수 있다. 이때 성공한 보존 정보까지 버릴 필요는 없다. 실패한 건수 영역을 따로 표시하고, 확보한 보존 정보는 유지한다.
이 응답을 브라우저가 정상적으로 받으면 ‘마지막 성공’ 시각은 갱신될 수 있다. 그것은 응답 수신에 성공했다는 뜻이다. 응답 안에 있는 모든 지표를 수집했거나 저장소의 정상 여부를 검사했다는 뜻은 아니다. 그래서 저장소 화면은 수신 시각과 항목별 수집 결과를 함께 보여준다. ‘무결성 미검사’도 정상 판정으로 바꾸지 않는다.
그래프의 공백도 관측 결과다
이후 10시 4분에 조회가 복구되어 새 표본을 받았다고 하자. 10시의 점과 10시 4분의 점을 직선으로 이으면, 그동안의 변화가 계속 관측된 것처럼 보인다. 그러나 가운데 조회가 실패했다면 그 선은 측정값이 아니다. 브라우저를 다른 탭으로 옮겨 주기 조회가 멈춘 경우에도 마찬가지다.
콘솔의 프로세스 추세는 실제 성공 응답으로 받은 표본만 모은다. 현재 화면에 들어오기 전의 이력을 채우지 않으며, 긴 표본 공백이나 프로세스 재시작이 있으면 구간을 나눈다. 서버 시각이 멈추거나 뒤로 가는 경우, 누적 카운터가 줄어드는 경우에도 앞뒤 값을 자연스럽게 이어진 변화로 취급하지 않는다.
가령 이전 표본의 누적 메모리 회수 횟수가 120이고, 재접속 뒤 값이 2라면 ‘118회 감소했다’는 계산은 도움이 되지 않는다. 프로세스가 재시작됐을 가능성이 있고, 적어도 두 값을 같은 누적 구간으로 비교할 근거는 없다. 이때는 비교 불가를 보여준다. 여기서 말하는 메모리 회수는 Java 프로세스의 GC이며, 원장의 오래된 검증 자료를 정리하는 저장소 GC와는 다른 작업이다.
추세를 끊어 그리면 화면이 덜 매끄러워 보일 수 있다. 하지만 빈 구간은 수집하지 못했다는 사실을 보존한다. 이 화면의 추세는 현재 세션에서 관측한 짧은 이력이다. 화면을 닫아둔 동안까지 포함하는 장기 운영 이력으로 읽을 수는 없다.
명령이 접수됐다는 답도 완료 화면은 아니다
조회가 복구된 뒤, 같은 운영자가 저장소 정리 작업을 살펴본다고 하자. 화면에서 진단을 시작했고 서버는 요청을 수락했다. 이 응답을 받았다는 이유로 정리가 끝났다고 표시하면, 앞에서 오래된 표본을 현재값으로 보였던 것과 비슷한 문제가 생긴다. 근거가 답하는 범위보다 더 큰 결론을 붙이는 것이다.
nigo-protocol의 수동 GC는 명령 수락 기록과 현재 작업 상태를 나눠 둔다. 수락 기록은 특정 명령이 접수됐다는 근거로 남는다. 그 뒤의 진단 진행, 삭제 승인 대기, 삭제 진행과 완료는 작업 상태에서 확인한다. 진단이 끝났다고 삭제가 승인된 것도 아니고, 삭제를 승인했다고 모든 후보의 삭제가 끝난 것도 아니다.
GC의 안전한 중단과 실제 완료를 구분한 글은 왜 이런 구분이 필요한지 보여준다. 작업이 보호 조건을 지키며 멈춘 경우, 자료의 안전성은 지켰어도 정리 목표는 아직 달성하지 못할 수 있다. 화면에서 ‘수락’이나 ‘오류 없음’을 ‘완료’로 합쳐버리면 이 차이를 잃는다.
응답을 받지 못한 명령도 주의해서 읽어야 한다. 서버가 명령을 접수한 뒤 연결이 끊겼다면, 브라우저는 응답을 못 받았어도 작업은 시작됐을 수 있다. 그래서 결과가 불명확할 때는 같은 명령 식별자와 작업을 먼저 조회한다. 화면은 새로운 식별자로 자동 재전송하지 않는다. 조회에서 기록을 찾지 못했다는 결과도 그 명령이 한 번도 실행되지 않았다는 증거로 단정하지 않는다.
브라우저를 닫는 일도 서버 작업을 중단하는 명령과 다르다. 수동 GC는 묶음 작업이 실행 중일 때 중단 요청을 거절할 수 있다. 중단을 눌렀다는 사실만으로 멈췄다고 판단하지 않고, 서버의 현재 작업 상태가 중단 상태인지 확인해야 한다. 이미 저장한 삭제 결과를 되돌리는 기능도 아니다. 운영자가 보는 화면의 수명과 서버에서 진행하는 작업의 수명을 분리해야, 재접속 뒤에도 같은 작업을 올바르게 따라갈 수 있다.
화면이 말할 수 있는 범위를 남긴다
이 원칙은 실패한 화면에 경고 하나를 더 붙이는 것으로 끝나지 않는다. 값마다 관측 시각과 수집 결과가 있고, 명령마다 접수와 실행 결과가 있으며, 그래프의 빈 구간에도 이유가 있다. 서로 다른 사실을 하나의 초록색 ‘정상’으로 줄이면, 운영자가 다음 행동을 결정할 단서가 사라진다.
콘솔의 회귀 검사도 이런 차이를 직접 다룬다. 프로세스 재조회가 실패해도 다른 합의 관측은 유지되는지, 이전 표본 옆에 오류가 표시되는지, 저장소 한 영역의 수집 실패를 다른 영역의 실패나 빈 저장소로 바꾸지 않는지를 확인한다. 화면의 의미를 검사하는 사례와 실제 노드·저장소 작업의 검증은 역할이 다르다.
10시 3분의 운영자에게 필요한 답은 ‘계속 정상’이라는 막연한 확신이 아니다. 10시에 무엇을 확인했고, 그 뒤 무엇을 확인하지 못했으며, 지금 어느 근거부터 다시 읽어야 하는지다. 관측 화면이 이 범위를 정확히 남겨야, 정상 대기를 불필요하게 중단하지 않고 실제 장애의 조사를 놓치지도 않을 수 있다.