사고
크론이 74개까지 늘어 프로세스 한도를 넘겼다.
fork: Resource temporarily unavailable
★★ 여기서부터가 진짜 문제다. 외부 명령이 전부 fork를 필요로 하므로 ps · pkill · cat · chmod · crontab이 전부 안 된다. 무엇이 돌고 있는지 볼 수 없으니 무엇을 죽여야 할지도 모른다.
★ 평소 쓰던 확인 명령이 사고 시점에는 못 쓰는 도구가 된다. 디스크가 찼을 때 SSH가 안 열리는 것과 같은 형태다 — 고갈은 관측을 먼저 먹는다.
★★ 비상 수단이 정상 자원을 쓰면 비상시에 못 쓴다
/proc은 컨테이너 격리로 자기 것만 보여 다른 프로세스를 못 죽인다남은 수단은 관리 패널의 크론 목록 화면에서 항목을 지우는 것 하나였다. 프로세스를 안 쓰는 유일한 경로였기 때문이다.
★ 복구 경로를 설계할 때 "그 경로가 소모하는 자원"을 본다. 고갈되는 자원과 같은 것을 쓰면 그건 복구 경로가 아니다.
★★ 복구 기법 — fork 없이 도는 것들
셸 내장(echo · read · kill · for)은 fork 없이 돈다. 그리고 인터프리터가 하나만 뜨면, 그 안에서 os.kill·파일 쓰기·서브프로세스로 나머지를 전부 처리할 수 있다.
★ 자원이 0에 가까울 땐 "명령을 여러 번 치는" 방식에서 "한 번의 실행으로 전부 하는" 방식으로 바꾼다. 관건은 프로세스 하나를 확보하는 것이다.
원인을 없애도 증상은 남는다
★ 크론을 지워도 이미 뜬 프로세스는 안 죽는다. 회복까지 기다려야 한다. 그 사이에 "안 고쳐졌다"고 판단해 더 손대면 악화된다.
★★ 주기 표기는 시간당 발생량으로 센다
문제의 절반은 분 단위 반복 크론 10개였다.
1-59/4 · 3-59/8 · 1-59/5 · 2-59/9 · 9-59/10 · ...
등록할 때는 "몇 분마다 한 번"이라 작아 보인다. 10개가 모이면 시간당 100회 가까이 뜬다.
★ 크론 개수를 세는 것으로는 부족하다. 각 줄의 시간당 발생량을 더한다.
★★ 그리고 축이 하나 더 있다 — 같은 분에 몰리는 수
나머지 절반은 정시(0분)에 걸린 크론 17개였다. 매시 정각에 17개가 동시에 뜬다.
그 순간 프로세스 한도를 넘겨 원격 접속까지 막혔다.
★★ 총량이 같아도 몰리면 터진다. 시간당 17회는 흩어지면 아무 일 없고, 같은 분에 몰리면 그 1분이 한도를 넘긴다. 한도는 시간당 평균이 아니라 순간 동시 실행 수에 걸린다.
★ 주기가 같으면 위상도 같다. 0 * * * *를 17줄 쓰면 17개가 항상 같이 뜬다. 아무도 몰아넣을 생각을 안 했는데, 0분이 기본값처럼 손에 붙어서 저절로 몰린다.
★★ 스케줄은 세 가지로 본다 — 줄 수 · 시간당 발생량 · 같은 분의 동시 발생 수. 셋째가 실제로 한도를 치는 값이다.
→ 분을 흩어라. 0 대신 3 · 7 · 11 · 17 …
해법 — 순차 러너
작업 목록을 파일에 두고, 크론 하나가 주기마다 하나씩 꺼내 실행한다.
pgrep 방식은 자기 자신을 잡으므로 금지)timeout으로 강제 종료 — 다음 회차와 겹치지 않게★★ 처리량보다 가용성이 우선이다. 동시 20개보다 순차 1개가 낫다.
★★ 게이트를 올리면 처리량이 늘 것 같지만, 한도를 넘기면 전체가 멈춘다. 20을 노리다 0이 된다.
★★★ 어쩌다 이렇게 됐나
배치 크기를 키우고, 동시 실행 게이트를 올리고, 크론을 늘렸다. 셋을 동시에 했다. 각각은 "처리량을 늘리는 합리적 조정"이었다.
★ 우선순위가 높다는 것이 한 번에 많이 해도 된다는 뜻이 아니다.
★ 상한이 있는 자원에서 최적점은 양 끝이 아니라 가운데다. 다른 항목에서는 한도를 너무 낮게 잡아 스스로를 막았고, 여기서는 너무 높게 잡아 전부 멈췄다. 같은 종류의 실수의 양쪽 끝이다.