증상
스케줄러 데몬이 3시간 정지했다. 동시 실행 프로세스가 32까지 올라가 자원 한계를 넘겼다.
원인 1 — 우리가 정한 상한이 추정값이었다
하루 전 "동시 실행 상한은 14"라고 확정해 둔 상태였다. 그 14의 출처는 아무 데도 없었다. 사고 후에 찾아보니 호스팅의 동시 프로세스 한계가 20으로 문서에 명시돼 있었다.
★★★ 이 컬렉션에는 "우리 코드의 기본값을 플랫폼 제약으로 오인한" 사례가 넷 있다. 이번은 정확히 그 반대다 — 플랫폼 제약을 찾지 않고 우리 숫자를 만들었다.
★★★ 정반대인데 결과는 같다: 근거 없는 숫자로 운영했다. → "이 한계를 플랫폼이 이미 정해 놓았는가"를 먼저 묻고, 추정은 그다음이다. 제약의 출처를 확인하라는 규칙은 양방향이다.
원인 2 — ★★★ 개별 상한의 합이 전역 한계를 넘었다
복구하면서 작업별 상한을 다시 잡았다.
가드 12 + 큐 11 + 이미지 8 = 31 > 플랫폼 한계 20
★★★ 셋이 동시에 각자 상한까지 가면 31이다. 각 상한은 자기 작업에 대해서는 합리적이고, 전역에 대해서는 아무 보호도 못 한다.
★★ 이건 이 컬렉션의 보호 목록 합계가 전체 상한을 넘었던 사례와 같은 형태다. 제약을 따로 두면 모순은 두 제약이 만나는 자리에만 있고, 그 자리가 코드에도 문서에도 없다.
→ 전역 카운터를 두거나, 개별 상한의 합을 전역 한계 이하로 잡아라. ★ 어서션 한 줄이면 된다.
assert sum(per_task_limits.values()) <= PLATFORM_PROCESS_LIMIT
이 줄이 있었으면 상한을 재설정하는 순간 걸렸다.
원인 3 — 같은 분에 몰려 있었다
같은 날 확인하니 스케줄의 21~23개가 0분과 30분에 몰려 있었다. 오프셋을 줘서 분당 7개로 흩었다.
★★ 전날 17개로 세어 처방을 적어둔 항목이었다. 적용되지 않았거나 다시 몰렸다. ★★★ 처방을 적은 것과 적용된 것은 다르다 — 정시 집중은 적용 여부를 자동 점검해야 한다. 0 * * * * 형태의 개수를 세는 한 줄이면 된다.
⚠️ ★★ 그리고 가드가 막지 못했다
폭주 방지 가드가 있는데도 32까지 갔다. ★ 자원이 고갈되면 진단·방어 도구도 프로세스를 못 띄운다 — 이 컬렉션에 이미 적혀 있는 것이다.
★★★ 방어 장치가 방어 대상과 같은 자원을 쓰면, 가장 필요한 순간에 없다. → 사고 구간에 가드가 실제로 실행됐는지 로그로 확인하라. "가드를 켜 뒀다"는 가드가 돌았다는 증거가 아니다.
★ 복구는 단계적으로
비상 축소로 4줄까지 줄인 뒤 7줄까지 단계적으로 회복했다. ★★ 한 번에 되돌리면 같은 상태로 돌아간다. 원인이 확정되기 전에는 줄인 상태에서 관찰한다.
검증 방법
★ 재설정 직후 sum(개별 상한)과 플랫폼 한계를 한 줄로 비교하고, 같은 분에 몰린 항목 수를 센다. 둘 다 계산이지 측정이 아니라서 사고 없이도 지금 당장 확인할 수 있다.