두 달 동안 관찰 모드로 돌려둔 기능이 있었다. 실제 차단은 하지 않고 “차단했을 것"만 로그로 남기는 방식이었다. 전환 시점이 되어 로그를 확인했더니 관련 로그가 한 줄도 없었다.

처음 든 생각은 “오탐이 0이구나, 안전하게 켜도 되겠다"였다. 다행히 켜기 전에 한 번 더 봤는데, 실제로는 오탐이 0이 아니라 관찰 결과가 한 시간 이상 남지 않는 상태였다. 로그를 매시간 비우는 크론이 따로 돌고 있었던 것이다.

두 달치 관찰을 했다고 믿었지만 실제로 본 것은 직전 몇십 분이었다. 그 뒤로 비슷한 유형을 몇 번 더 밟았고, 공통점이 있어서 정리해둔다. 전부 관찰 결과가 관찰 대상보다 먼저 사라지는 문제다.

1. 보존 주기가 관찰 주기보다 짧으면 측정은 없었던 것과 같다

로그 보존 정책은 대개 디스크를 지키려고 만든다. 관찰을 시작하는 사람과 보존 정책을 만든 사람이 다르고, 시점도 다르다. 그래서 이 둘은 아주 쉽게 어긋난다.

문제는 어긋났을 때 아무 에러도 나지 않는다는 점이다. 로그는 정상적으로 쌓이고, 크론도 정상적으로 지운다. 각자 자기 일을 잘 하고 있다. 실패는 두 설정 사이에서만 존재하고, 그래서 아무도 알려주지 않는다.

관찰을 시작하기 전에 이것부터 확인하는 습관을 들였다.

# 이 로그는 언제부터의 기록인가
head -1 <로그파일>
stat <로그파일>

# 이 파일을 건드리는 스케줄이 따로 있는가
crontab -l
ls /etc/cron.*/ /etc/logrotate.d/
systemctl list-timers --all

관찰 기간이 보존 기간보다 길다면, 관찰 결과는 다른 곳에 따로 모아야 한다. 뒤에서 다시 이야기한다.

2. 지운 파일에 계속 쓰고 있는 경우

더 고약한 경우가 있다. 파일을 지웠는데 프로세스가 그 파일에 계속 쓰고 있는 상태다.

리눅스에서 rm은 디렉터리 엔트리를 지울 뿐이다. 프로세스가 그 파일을 열고 있으면 inode는 살아 있고, 쓰기도 정상적으로 계속된다. 다만 그 내용을 볼 방법이 없다. 파일 이름이 없으니 열 수가 없다. 프로세스가 재시작될 때까지 데이터는 아무도 못 보는 곳에 쌓이고, 그대로 사라진다.

증상은 이렇게 나타난다.

  • 애플리케이션은 로그를 남긴다고 하는데 파일이 없거나 크기가 0이다
  • df로 본 사용량과 du로 센 합계가 크게 어긋난다
  • 프로세스를 재시작하면 갑자기 디스크가 확 빈다

확인은 이렇게 한다.

# 삭제됐는데 열려 있는 파일
lsof -nP | grep '(deleted)'

# 특정 프로세스가 연 파일 목록
ls -l /proc/<PID>/fd | grep deleted
# → 52 -> /var/log/app/access.log (deleted)

여기서 중요한 게 하나 있다. 크론을 고쳐도 이미 열린 핸들은 그대로다. 삭제 스케줄을 멈추더라도 프로세스가 새 파일을 열기 전까지는 여전히 유령 inode에 쓴다. 그래서 조치의 효과는 다음 로테이션 시점이나 재시작 이후부터 나타난다. 고친 직후에 확인하고 “안 고쳐졌다"고 판단하면 또 한 번 헛짚는다.

로그 로테이션 도구가 copytruncate 대신 이름 변경 방식을 쓸 때 애플리케이션에 재열기(reopen) 신호를 보내야 하는 것도 같은 이유다.

3. 0건이 “없음"인지 “안 돎"인지 구분할 수 없다

이게 가장 자주 밟는 함정이다.

차단 로그가 0줄이라고 하자. 가능한 해석이 둘이다.

  1. 조건을 만족한 요청이 정말 없었다
  2. 그 로직이 아예 실행되지 않고 있다

결과 지표만 봐서는 이 둘을 영원히 구분할 수 없다. 배포가 안 됐거나, 설정이 꺼져 있거나, 대상 경로 등록이 빠졌거나, 예외가 먹혀서 조용히 넘어가고 있어도 결과는 똑같이 0이다.

필요한 건 결과가 아니라 과정을 보는 지표다. 차단 로그는 조건을 만족해야 생기지만, 카운터는 요청마다 증가한다.

차단 로그 0줄  +  카운터 증가 중   → 로직은 돌고 있고, 걸린 요청이 없다
차단 로그 0줄  +  카운터 없음      → 로직이 안 돈다. 배포·설정부터 확인

카운터를 어디에 두느냐는 상황에 따라 다르다. 인메모리 카운터를 지표로 노출해도 되고, 이미 캐시 저장소를 쓰고 있다면 거기 남는 키의 개수 자체가 증거가 된다. 형태보다 중요한 건 “조건을 만족하지 않아도 남는 흔적"이 하나는 있어야 한다는 점이다.

같은 이야기를 일반화하면 이렇다. 어떤 기능이든 동작했다는 증거와 결과를 냈다는 증거를 분리해서 남긴다. 헬스체크가 하는 일도 결국 이것이다.

4. append는 쓸 내용이 없으면 mtime을 바꾸지 않는다

관찰 결과를 따로 모으려고 이런 수집 스크립트를 걸었다고 하자.

grep '[TAG]' <원본로그> >> <아카이브파일>

수집할 줄이 없으면 >>는 아무것도 쓰지 않고, 파일의 mtime도 바뀌지 않는다. 그러면 아카이브 파일은 0바이트에 며칠 전 타임스탬프를 달고 있게 된다.

이걸 보고 “크론이 안 돌았네"라고 판단하면 틀린다. 크론은 매번 정상 실행됐고, 수집할 게 없었을 뿐이다. 반대로 진짜로 크론이 죽었을 때도 화면상 똑같이 보인다. 또다시 두 상태를 구분할 수 없는 지표를 만든 것이다.

실행 자체를 확인하려면 스케줄러 로그를 본다.

journalctl --since today | grep -E 'CMD \(|CMDEND \('

시작과 종료가 짝으로 찍히면 오류 없이 실행된 것이다. 아니면 아예 수집 스크립트가 매번 무언가는 쓰도록 만들어도 된다. 빈 날에도 헤더 한 줄을 남기면 파일 자체가 실행 증거가 된다.

5. 재구성한 데이터에는 커버리지 한계가 있다

로그가 사라졌으면 다른 데이터로 되짚어보게 된다. 접근 이력 테이블 같은 걸로 지난 기간을 재구성하는 식이다.

이때 놓치기 쉬운 건, 그런 이력 테이블은 대개 화이트리스트 기반이라는 점이다. 기록할 대상을 명시적으로 등록한 것만 쌓인다. 그래서 재구성 결과는 실제보다 항상 과소평가된다.

한 번은 이 방식으로 기준선을 뽑았는데, 정작 관심 대상이던 경로 일부가 이력 테이블에 애초에 등록돼 있지 않아 전 기간 0행이었다. 그 상태로 “최댓값이 임계의 몇 %“를 계산했으면 완전히 잘못된 안전 판단을 내렸을 것이다.

재구성 데이터를 쓸 때는 결과보다 커버리지를 먼저 확인해야 한다. 대상 목록과 실제로 적재된 목록을 대조해보면 금방 드러난다.

그래서 어떻게 설계하는가

몇 번 밟고 나서 정리한 원칙은 세 줄이다.

하나, 관찰 결과는 관찰 대상과 다른 수명주기·다른 위치에 둔다.

로그 디렉터리 안에 아카이브를 만들면 그 디렉터리를 청소하는 정책에 같이 쓸려나간다. 실제로 가장 밟기 쉬운 함정이었다. 보존 정책이 걸린 경로 에 두고, 그 아카이브에는 별도의 보존 기간과 크기 상한을 명시한다.

둘, 기존 정리 스케줄은 건드리지 않고 옆에 붙인다.

디스크를 지키는 규칙을 수정해서 관찰을 살리려는 시도는 위험하다. 그 한 줄이 실패하면 디스크가 차고, 그건 관찰 실패보다 훨씬 큰 사고다. 정리 규칙은 그대로 두고 수집을 따로 얹은 다음, 그 사이에 생기는 소량의 유실은 감수하는 편이 낫다. 지속적으로 발생하는 패턴을 찾는 게 목적이라면 몇 분의 공백은 결론을 바꾸지 않는다.

셋, 모든 지표에 대해 “이 값이 0일 때 가능한 해석이 몇 가지인가"를 묻는다.

해석이 둘 이상이면 그 지표만으로는 판단할 수 없다는 뜻이다. 그때는 지표를 하나 더 만들어야 한다. 이 질문 하나가 위의 사례 대부분을 사전에 걸러준다.


정리하고 보니 다섯 가지 모두 같은 이야기였다. 측정 장치가 스스로를 측정하지 않는다는 것. 로그를 남기는 코드를 짤 때는 로그가 남을 거라고 당연히 가정하게 되는데, 운영에서 그 가정은 자주 틀린다.

관찰을 시작하기 전에 “이 관찰이 실패하면 나는 그걸 어떻게 알게 되나"를 한 번 물어보는 것만으로도 대부분은 미리 잡힌다. 로그 설계 자체에 대해서는 console.log는 로깅이 아니다에 따로 정리해두었다.

자주 묻는 질문

Q. 로그 파일이 비어 있으면 해당 이벤트가 없었다고 봐도 되나요?

아닙니다. 로그 로테이션이나 초기화 주기가 관찰 주기보다 짧으면 이벤트가 기록됐다가 사라졌을 수 있습니다. 파일이 비어 있다는 사실은 “그 파일의 현재 보존 구간 안에 없다"는 뜻일 뿐이며, 판단하려면 먼저 보존 정책과 그 파일이 실제로 언제부터의 기록인지를 확인해야 합니다.

Q. 삭제된 로그 파일에 프로세스가 계속 쓰고 있는지 어떻게 확인하나요?

리눅스에서 파일을 지워도 프로세스가 파일 핸들을 열고 있으면 inode는 살아 있고 쓰기도 계속됩니다. lsof에서 (deleted) 표시가 붙은 항목이나 /proc/PID/fd 심볼릭 링크의 (deleted) 접미사로 확인할 수 있습니다. 디스크 공간도 반환되지 않으므로 dfdu 결과가 어긋나는 것도 신호입니다.

Q. 차단이나 알림이 0건일 때 기능이 동작 중인지 어떻게 확인하나요?

결과가 아니라 과정을 보는 지표가 따로 필요합니다. 차단 로그는 조건을 만족해야 생기지만 카운터는 요청마다 증가하므로, 카운터가 늘고 있다면 로직이 실행되고 있다는 직접 증거가 됩니다. 결과 지표만 보면 “해당 없음"과 “동작 안 함"을 영원히 구분할 수 없습니다.