한 채널 상품의 기간 연장이 안 된다는 얘기가 있어서 연장 작업이 실패했겠거니 하고 조사를 시작했다. jobs 이력을 보니 create와 update는 있는데 extend가 0건이었다.
Table of contents
Open Table of contents
원인 — 실패가 아닌 부재
실패한 것이 아니라 만들어진 적이 없었고 이 차이가 조사 방향을 완전히 바꾼다.
실패로 보고 조사하면 실패 로그를 찾게 되는데 시도 자체가 없으니 로그가 없다. 증상을 확인하기 전에 그 작업이 애초에 걸리는지를 먼저 봐야 했다.
문제 — 두 경로의 서로 다른 게이트
연장 작업이 어디서 만들어지는지를 찾으니 경로가 두 곳이었다. ExtendHandler가 jobs에 적재하고 스케줄러가 나중에 실행하는 경로와 Observer가 dispatch()로 큐 작업을 던지는 경로다.
관리 화면에서 수동으로 연장하면 두 경로가 다 발화하는 구조였다. 첫 번째 경로의 코드를 보니 $channel이 null일 때는 in_array로 특정 세 채널만 통과하는 조건이 있었고 문제의 채널이 그 목록에 없었다.
원인 — 부르는 방식이 바꾸는 제한
같은 핸들러에 다른 진입점도 있었는데 $channel을 명시해서 부르면 in_array 검사를 건너뛰고 바로 작업을 적재한다.
같은 기능인데 부르는 방식에 따라 제한이 다른 구조였다. 더 보니 특정 supplier_id만 예외로 허용하는 하드코딩된 조건도 있었고 어떤 사정으로 한 곳만 열어 준 것으로 보이는데 이유는 코드에 없다.
주의 — 조사 중 나온 부수 버그
조사하는 중에 성격이 다른 문제가 하나 더 나왔다. extend()가 허용 목록 밖의 days 값을 받으면 조용히 return하는데 그것을 부르는 컨트롤러가 반환값을 안 보고 event()를 무조건 발생시킨다.
허용 목록 밖의 값으로 요청하면 기간은 안 늘어나는데 이벤트는 발생하고 리스너가 연장 작업을 만든다. 기간은 그대로이고 작업만 쌓이는 구멍이다. 두 가지 방법이 있었는데 이번 조사에서 같이 고치는 것과 별도 항목으로 기록만 하는 것이다. 뒤쪽을 골랐다. 이유는 이번 조사의 대상이 아니고 호출부를 고치면 검증 범위가 두 배가 돼서다. 대신 그 구멍이 다음 배포까지 열려 있는 것을 감수했다.
교훈 — 조용한 반환의 문제
이 부수 버그의 원인은 하나로 모인다. 함수가 아무것도 안 알리고 반환하는데 호출부가 그 반환을 보지 않는 것이다.
호출부는 성공했는지 아무것도 안 했는지를 구분할 수 없다. 최소한 false라는 반환값이 있어야 하고 호출부가 그것을 봐야 이 조합이 구멍이 되지 않는다.
정리
- 작업이 실패한 것이 아니라 만들어진 적 없을 수 있다
- 같은 기능에 경로가 둘 이상이고 각각 게이트가 다를 수 있다
- 대상을 명시하느냐에 따라 게이트를 건너뛰기도 한다
- 하드코딩된 예외가 있을 수 있고 이유는 코드에 안 적혀 있다
- 진단 전에 그 작업이 애초에 걸리는지를 본다
- 조용히 반환하는 함수와 반환값을 안 보는 호출부는 구멍을 만든다
- 최소한
false반환값이 있어야 한다 - 조사 중 나온 부수 버그는 별도 항목으로 기록한다