jobs 표에 처리 대기가 수천 건 쌓였다.
재투입 명령을 돌렸는데 건수가 안 줄었다. 다시 돌려도 여전히 그대로였다.
Table of contents
Open Table of contents
대기가 두 종류였다
명령이 성공했는데 상태가 안 바뀌면 그 명령이 이 상황에 안 맞는 것이다. 큐 테이블을 자세히 보니 상태 컬럼 말고 시각 컬럼이 세 개 있었다.
queued_at 큐에 들어온 시각
started_at 워커가 집어간 시각
finished_at 끝난 시각
대기 중인 것들을 started_at 기준으로 나눠 봤다.
| 상태 | 건수 | 뜻 |
|---|---|---|
started_at 없음 | 40 | 아직 안 꺼냄 |
started_at 있고 finished_at 없음 | 3,200 | 워커가 이미 가져감 |
대부분이 두 번째였는데 안 꺼내진 것이 쌓인 게 아니라 꺼내 갔는데 안 끝나고 있는 것이었다.
대기 중이라는 한 상태가 성격이 다른 둘이었다. 화면에 같은 글자로 찍히니 구분할 생각을 안 했다.
원인 — 재투입이 무의미했던 이유
재투입 명령은 started_at 이 없는 것만 다시 큐에 넣는데 그것은 40건뿐이었다.
나머지 3,200건은 이미 worker 손에 있어서 다시 넣어도 중복 처리 위험만 생긴다.
봐야 할 곳이 jobs 가 아니라 워커였고 명령을 두 번 돌린 시간은 통째로 헛수고였다.
두 종류를 안 가른 채 조치를 고른 것이 문제였고 가르고 나면 안 맞는다는 것이 바로 보인다.
워커가 도는지 확인하기
워커가 살아 있는지는 ps 로 아는데 살아 있으면서 일을 안 하는 경우가 있다.
그래서 최근 완료 건수를 함께 봤다.
SELECT COUNT(*) FROM jobs
WHERE finished_at > NOW() - INTERVAL 5 MINUTE;
finished_at 이 최근 5분 안인 건을 센다.
| 관측 | 판정 |
|---|---|
| 최근 5분 완료가 수백 건 | 워커 정상, 그냥 밀린 것 |
| 최근 5분 완료가 한 자릿수 | 워커가 멈췄거나 특정 작업에 걸림 |
한 자릿수였고 프로세스는 떠 있는데 사실상 멈춰 있는 상태였다.
살아 있음과 일하고 있음이 다른 확인이다. 앞은 ps 로 보고 뒤는 COUNT(*) 로 본다.
무엇에 걸렸나
워커 로그를 보니 한 작업에서 계속 재시도하고 있었다. 외부 응답이 안 와 타임아웃까지 기다리기를 반복했다.
그 하나가 워커를 붙잡고 뒤의 3,200건이 그것을 기다렸는데 단일 스레드 워커라 더 그랬다.
하나가 오래 걸리면 그 worker 는 아무것도 못 한다. 큐가 안 준다는 현상의 실체가 이 한 건이었다.
조치 — 상한과 격리와 분리
셋을 고쳤는데 첫째는 재시도에 상한을 둔 것이다.
무한히 시도하지 않고 세 번 실패하면 기록하고 넘어간다. 둘째는 실패 작업을 별도 큐로 뺀 것이다.
계속 실패하는 것이 정상 작업을 막지 않게 하려는 것이다. 셋째로 worker 를 여러 개로 나눴다.
하나가 막혀도 나머지가 도는데 부분적인 대책이라 같은 이유로 전부 막힐 수는 있다.
전체 흐름 — 진단 순서
큐가 안 줄어든다는 제보를 받으면 이 순서로 본다.
1. 대기를 두 종류로 나눈다 — 안 꺼낸 것과 처리 중인 것
2. 안 꺼낸 게 많으면 → 워커 수 부족이거나 투입이 안 되는 것. 재투입·증설
3. 처리 중인 게 많으면 → 워커를 본다. 재투입은 무의미
4. 최근 완료 건수를 센다 — 워커가 도는지 멈췄는지 갈린다
5. 멈췄으면 무엇에 걸렸는지 로그에서 찾는다
둘째와 셋째의 조치가 정반대라는 것이 요점이다. 구분 안 하고 재투입만 반복하면 아무것도 안 바뀐다.
첫째를 건너뛰면 나머지 넷이 전부 엉뚱한 자리를 향하는데 이번이 그랬다.
대장에 없으면 안 돈다
부수적으로 하나를 더 발견했는데 어떤 작업 종류가 자동 투입 스케줄에 아예 등록이 안 돼 있었다.
수동으로 넣지 않으면 영원히 대기이고 jobs 에는 있는데 아무도 안 꺼내 간다.
이것도 겉으로는 jobs 에 대기가 쌓이는 것으로만 보인다. 앞의 둘과 또 다른 세 번째 경우다.
그래서 스케줄 등록 목록과 실제 작업 종류를 대조하는 점검을 넣었다. 새 종류를 추가하고 등록을 빠뜨리면 그때 잡힌다.
정리
- 큐의 대기는 아직 안 꺼낸 것과 이미 처리 중인 것 두 종류다
started_at이 있는지로 그 둘을 가른다- 두 종류의 조치가 정반대라 안 가르면 재투입이 헛수고가 된다
finished_at이 최근인 건수를 세면 워커가 도는지 멈췄는지 갈린다- 프로세스가 살아 있음과 일하고 있음은 다른 확인이다
- 워커가 한 건에 걸리면 그 뒤가 전부 밀린다
- 재시도 상한과 실패 격리와 워커 분리 셋이 필요하다
- 진단은 두 종류로 나누는 것에서 시작해야 나머지가 맞는 자리를 향한다
- 스케줄에 등록 안 된 종류는 큐에 있어도 아무도 안 꺼낸다
- 등록 목록과 실제 종류를 대조하는 점검을 따로 둔다