website: git 드리프트 알람 문서화 + 알람 튜닝 사고 모델
멋사 경희대 사이트 프로젝트 인프라에서, 앞서 추가한 git 드리프트 감지 알람을 observability 문서에 반영하고 튜닝 과정에서 얻은 사고 모델을 정리한 PR입니다.
요약
이번 PR은 코드 변경 없이 infra/observability.md 문서 하나만 36줄 추가하는 작업입니다. #105~#107에서 만든 git 드리프트 감지 알람이 문서에 반영이 안 되어 있던 걸 채워 넣었고, 그 과정에서 겪은 pending-duration 튜닝(5분→3분→8분) 경험을 바탕으로 "쿼리 윈도우·pending-duration·cron 주기가 서로 어떻게 제약하는지"에 대한 사고 모델을 별도 섹션으로 정리했습니다. 2026-07-12에 생성되고 2분 만에 바로 병합됐습니다.
배경 및 목적
배포 서버는 git으로 관리되는데, SSH로 직접 파일을 고치거나 gitignore 안 된 낯선 파일이 워킹트리에 남으면 git pull이 조용히 실패합니다. 실제로 서버가 몇 주째 옛날 커밋에 고정된 채 배포마다 롤백만 반복한 사고가 있었다고 합니다. 이를 감지하기 위해 push-git-drift-metric.py라는 커스텀 메트릭 스크립트를 만들어 git status --porcelain 라인 수를 그대로 메트릭으로 올리는 알람을 구축했는데, 정작 이 알람이 observability.md의 Alarm 목록/파일 표에는 빠져 있었습니다.
여기에 더해, 알람을 실제로 운영하면서 흥미로운 오탐 사고가 하나 있었습니다. 배포 스크립트가 정상적으로 만들었다가 지우는 롤백 마커 파일(infra/.prev_backend_tag_stage)이 gitignore에서 누락되어 드리프트로 잡혔고, 이게 1분짜리 정상적인 상태였는데도 알람이 FIRING된 겁니다. 근본 원인은 gitignore 누락이라 그건 따로 고치면 되지만, 이 과정에서 "왜 이렇게 짧은 순간의 상태 변화도 알람이 잡아버리는가"를 파고들다 보니 커스텀 메트릭 알람 튜닝에 대한 일반적인 사고 모델이 나왔고, 이걸 재사용 가능하도록 문서에 남긴 게 이 PR의 핵심입니다.
구현 내용
변경된 파일은 infra/observability.md 하나뿐입니다.
Alarm 목록/파일 표 업데이트
기존 알람 표에 git 드리프트 알람 항목을 추가했습니다.
| likelion-prod 배포서버 git 드리프트 감지 | custom_likelion | GitDriftFileCount[10m].max() > 0 | 8분 지속 시 (pending-duration) | CRITICAL |
그리고 왜 이 알람이 필요한지도 함께 적었습니다. gitignore된 파일(.env.*, infra/nginx.conf, infra/data/, infra/.prev_backend_tag_*)은 애초에 git status에 안 잡히기 때문에, "서버 전용 정상 파일"과 "git이 몰라야 하는데 존재하는 파일"이 자동으로 구분된다는 설계 의도도 짚었습니다.
파일 표에도 한 줄이 추가됐습니다.
| infra/push-git-drift-metric.py | 배포 서버 git 워킹트리 드리프트(git status --porcelain 라인 수) → custom metric. cron */5 * * * *로 실행 |
알람 튜닝 사고 모델 섹션
이번 PR에서 가장 분량이 큰 부분입니다. 커스텀 메트릭 알람을 튜닝할 때 서로 얽혀 있는 변수 4개를 정의했습니다.
- C (cron 주기): 메트릭이 몇 분마다 새로 찍히는가
- W (쿼리 윈도우): 알람 쿼리
[Xm]이 매 평가 시점에 최근 몇 분을 보는가 - R (resolution): 알람이 몇 분마다 재평가하는가 (보통 1분 고정)
- P (pending-duration): breaching이 몇 분 연속돼야 실제 FIRING으로 전환되는가
이 변수들 사이의 관계를 네 가지로 정리했습니다.
첫째, W는 C 이상이어야 한다는 것. 메트릭이 C분에 한 번만 찍히니 W < C면 윈도우 안에 데이터가 아예 없는 구간이 생겨 breaching 카운트가 끊깁니다.
둘째는 "스미어링" 현상입니다. [Xm].max() 같은 쿼리는 최근 X분 안에 나쁜 값이 있었는지만 보기 때문에, 값이 단 한 틱만 나빴어도 그 뒤로 W분 동안 계속 "나쁨"으로 관측됩니다. 실제 지속시간과 무관하게 관측되는 breaching 지속시간이 W분이 되는 셈입니다.
셋째는 파이어에 필요한 최소 연속 tick 수를 공식으로 정리한 부분입니다.
k_min = ceil( (P - W) / C ) + 1
여기서 구조적인 결론이 나옵니다. W ≥ P면 단 한 번의 blip만으로 파이어되고, W < P여야 비로소 2번 이상 연속된 진짜 상태가 필요해집니다. 그런데 관계①(W≥C)과 동시에 만족하려면 C ≤ W < P, 즉 P가 C보다 확실히 커야만 blip 한 번은 무시하고 진짜 지속만 잡는 설계가 가능합니다. 뒤집어 말하면 P ≤ C면 W를 아무리 조정해도 구조적으로 단일 blip을 못 피한다는 것이 이번 문서화의 핵심 결론입니다.
넷째, OK 복귀 속도는 P와 무관하게 W만 결정한다는 점도 짚었습니다. 문제가 실제로 사라지면 마지막 나쁜 push가 윈도우에서 밀려나는 순간(W분 후) 바로 OK로 돌아가고, P가 크다고 복귀가 늦어지지는 않습니다.
이 사고 모델을 실제 사례에 적용한 부분도 남겼습니다. C=5분, W=10분, P=5분(이후 3분으로 낮췄다가) 조합에서 1분짜리 정상 상태가 오탐 FIRING을 일으켰는데, P=3분(≤C=5분) 상태에서는 어떤 W를 골라도 구조적으로 단일 blip을 못 피한다는 걸 확인하고 **P=8분(>C=5분)**으로 조정했습니다. 이제 순간적 상태 한 번으로는 안 뜨고, 최소 2번 연속 cron tick 동안 실제로 더러워야 파이어합니다.
기술적 의사결정
이 PR 자체는 문서만 수정하는 작업이라 새로운 기술 선택은 없지만, 알람 튜닝 방식에 대한 의사결정 근거를 명시적으로 남겼다는 점이 눈에 띕니다. pending-duration을 단순히 "오탐이 나니까 늘려보자"는 식으로 조정한 게 아니라, W·C·P의 관계를 수식으로 정리해서 "P가 C보다 커야만 blip을 걸러낼 수 있다"는 구조적 이유를 먼저 확인하고 값을 정했습니다. 이렇게 하면 다음에 비슷한 오탐이 생겼을 때 감으로 값을 조정하는 대신 같은 사고 모델을 재사용할 수 있습니다.
배운 점 및 개선점
알람을 만들 때 "언제 울리게 할까"를 감각적으로 정하기 쉽지만, 실제로는 cron 주기·쿼리 윈도우·pending-duration이 서로를 제약하는 구조적인 관계가 있다는 걸 이번에 명확히 정리하게 됐습니다. 특히 "스미어링" 개념 — 단일 blip이 윈도우 크기만큼 뭉개져서 보인다는 점 — 은 알람 설계 시 놓치기 쉬운 부분이라 문서로 남겨둔 게 의미가 있어 보입니다.
또 하나는, 알람 자체를 추가하는 작업(#105~#107)과 그걸 문서화하는 작업 사이에 시차가 생겼다는 점입니다. 알람이 실제로 잘 동작하는지 검증하고 튜닝하는 과정에서 자연스럽게 사고 모델이 정리된 거라 순서 자체는 나쁘지 않았지만, 앞으로는 알람을 추가하는 시점에 문서화까지 같이 묶어서 진행하면 이런 공백을 줄일 수 있을 것 같습니다.