한 채널의 주문 수집이 사흘째 멈췄다는 긴급 건이 왔다. order_collect_logs 의 일자별 건수가 사흘째 0이었고 그 수집이 어디서 도는지부터 찾아야 했다.
Table of contents
Open Table of contents
상황 — 아는 곳에 없다는 단서
주 시스템 둘의 app/Console/Kernel.php 에 그 항목이 없었고 작업 서버 네 대의 crontab -l 과 ps aux 출력과 storage/logs 에도 흔적이 없었다. 어디 있는지를 계속 묻는 대신 내가 아는 곳에는 없다는 것으로 정리했다.
아는 배포 구조 안에 없다는 사실 자체가 그것이 구조 밖에 있다는 단서였다. 그러면 남은 후보는 다른 목적으로 쓰던 서버들이다.
원인 — 다른 앱에 얹혀 있던 수집
다른 용도로 쓰던 인터페이스 서버에 들어가 보니 그 앱의 Kernel.php 안에 $schedule->command('collect:orders') 가 얹혀 있었다. Laravel 9 공식 문서가 스케줄 정의 자리로 지목하는 그 파일이 전혀 다른 목적의 앱에 있었고 거기에 이 채널 수집이 붙어 있었다.
연동을 만들 때 새 배포 대상을 만드는 것보다 얹는 편이 빨랐을 것으로 보인다. 문제는 배포 구조 밖에 있으면 감시 대상에서도 빠져서 사흘이 지나서야 알게 된다는 점이었다.
이름이 아니라 실행 경로
그 서버에 /var/www/interface 와 /var/www/interface-prod 가 나란히 있었고 뒤쪽 이름이 운영본을 뜻했다. collect:orders 코드도 그쪽에 있고 git log 의 최근 커밋도 그쪽이었다.
crontab -l 의 cd 경로와 떠 있는 queue:work 프로세스의 readlink /proc/<pid>/cwd 를 확인하니 둘 다 접미사가 없는 /var/www/interface 였다. proc(5) 매뉴얼은 /proc/[pid]/cwd 를 그 프로세스의 현재 작업 디렉터리를 가리키는 심볼릭 링크로 설명하므로 이름이 아니라 그 링크가 실제로 어디서 떴는지를 말한다.
판단 기준 — 읽은 곳을 밝히는 이유
interface-prod 쪽만 고쳤으면 저장도 되고 커밋도 되지만 queue:restart 를 해도 옛 코드가 그대로 돈다. 고쳤는데 왜 안 되는지를 다시 조사하게 되는 종류였다.
같은 이유로 그쪽 Kernel.php 에서 읽은 스케줄 주기도 구동본과 같다는 보장이 없다. 보고에는 두 가지 방법이 있었는데 그 값을 그대로 적는 것과 어느 디렉터리에서 읽었는지와 구동본 일치 보장이 없다는 단서를 함께 적는 것이다. 뒤쪽을 골랐다. 이유는 값만 적으면 받는 쪽이 그것을 구동본 사실로 믿고 다음 판단을 세우기 때문이다. 대신 보고가 길어지고 확정된 값이 하나도 없어 보이는 것을 감수했다.
검증 — 행위가 아니라 결과로 판정
멈췄다는 판정도 프로세스 상태가 아니라 order_collect_logs 의 일자별 COUNT(*) 로 했다. 프로세스가 떠 있어도 적재가 0일 수 있고 죽어 있어도 다른 경로로 들어왔을 수 있다.
어느 날부터 0인지는 집계에서만 나온다. storage/logs/laravel.log 위치도 구동 디렉터리를 따라가므로 interface-prod 쪽에서 찾으면 옛 기록만 나온다.
정리
- 수집이 전혀 다른 목적의 앱에 얹혀 있을 수 있다
- 배포 구조 밖에 있으면 감시 대상에서도 빠진다
- 아는 곳에 없다는 것 자체가 단서가 된다
- 디렉터리 이름이 운영본을 뜻하지 않는다
- 실행 중인 프로세스의 작업 디렉터리를 확인한다
- 그것을 건너뛰면 고쳐도 반영되지 않는다
- 코드에서 읽은 값도 어디서 읽었는지를 밝힌다
- 멈춤 판정은 프로세스 상태가 아니라 적재 건수로 한다