본문으로 건너뛰기
JSL107's Tech Note
돌아가기

조용히 멈춘 크롤러를 찾는 세 겹의 탐지망

6분 분량이 글 고치기
목차 · 5

오류가 없다고 수집이 정상인 것은 아니다

크롤러에 이상이 생겼다는 첫 단서는 오류 기록이에요. 페이지에 접근하거나 파싱하다가 예외가 발생하면 실패 기록이 남고, 운영자는 이를 보고 어떤 수집 규칙에서 문제가 생겼는지 확인할 수 있어요.

하지만 게시판 구조가 바뀌어도 오류가 발생하지 않을 때가 있어요. 목록에서 게시물을 하나도 읽지 못했는데도 크롤러가 “새 글이 없다”고 판단하고 정상 종료할 수 있거든요. 프로세스는 성공했지만 실제 수집은 멈춘, 전형적인 silent failure예요.

이 문제를 보완하려고 두 번째 탐지망으로 매일 새벽 커버리지 감사를 실행했어요. 교육기관 홈페이지의 게시판 목록을 직접 열고, 화면에 보이는 게시물이 내부 DB에 모두 저장돼 있는지 비교하는 작업이에요.

이 감사에도 범위의 한계가 있었어요. 전체 수집 룰 천여 개 가운데 수백 개는 다음과 같은 이유로 감사 대상에서 빠졌어요.

특정 지역 교육기관이 홈페이지를 개편한 뒤 한 초등학교 게시판의 수집이 석 달 넘게 중단됐는데도 발견하지 못한 이유가 여기에 있었어요. 오류가 발생하지 않은 데다 커버리지 감사 대상에도 포함되지 않아 기존 두 탐지망을 모두 빠져나간 거예요.

세 번째 기준은 마지막 수집 시각이었다

세 번째 탐지망에서는 수집 방식이나 게시판 구조를 분석하지 않고 질문 하나만 던졌어요.

이 게시판에서 마지막으로 수집한 게시물은 언제 작성됐는가?

마지막 수집 시각은 게시판이 로그인 방식인지, API 기반인지, HTML 목록을 파싱하는지와 관계없어요. 기존 감사에서 제외했던 수백 개 룰에도 같은 기준을 적용할 수 있어요.

다만 마지막 수집 시각만으로 장애를 판단할 수는 없었어요. 기준 기간 이상 새 게시물을 수집하지 못한 게시판을 조회하자 수백 곳이 잡혔지만, 대부분은 크롤러 장애가 아니라 방학 중이라 학교에서 새 글을 올리지 않은 경우였어요.

단순한 임계값만으로는 수집이 중단된 상태와 게시물이 없는 상태를 구분할 수 없어요. 이대로 대시보드에 노출하면 false positive가 너무 많아져 운영자가 실제 장애 신호까지 믿기 어려워져요.

서로 다른 관측 결과를 교차했다

false positive를 줄이려고 마지막 수집 시각과 기존 커버리지 감사 결과를 교차했어요.

커버리지 감사에서 해당 게시판의 첫 페이지 게시물을 내부 DB가 모두 보유하고 있다고 확인했다면, 마지막 수집 시각이 오래됐어도 장애 후보에서 제외했어요. 학교가 실제로 새 글을 올리지 않았을 가능성이 높기 때문이에요.

반대로 마지막 수집 시각이 오래됐고 커버리지 감사에서도 정상 상태를 확인하지 못했다면, 사람이 살펴봐야 할 후보로 남겼어요.

판단 흐름은 다음과 같아요.

  1. 게시판별 마지막 수집 시각을 확인한다.
  2. 기준 기간 이상 새 게시물이 없으면 정체 후보로 분류한다.
  3. 같은 게시판의 최신 커버리지 감사 결과를 조회한다.
  4. 감사에서 DB 보유 상태를 확인한 후보는 제외한다.
  5. 감사로 정상임을 확인하지 못한 후보만 대시보드에 노출한다.

새로운 감시 루프나 별도 테이블은 만들지 않았어요. 기존 감사가 이미 전체 룰의 결과를 감사 결과 테이블에 저장하고 있었기 때문이에요. 이 테이블에 네 개의 상태 필드를 추가하고 같은 감사 루프에서 필요한 상태까지 함께 기록했어요. 관측 장치를 추가하면서도 실행 경로와 저장 구조는 최소한으로만 바꾼 셈이에요.

탐지망 자체도 멈출 수 있었다

세 번째 탐지망을 추가한 뒤에는 커버리지 감사 자체에 공백이 없는지도 확인해야 했어요. 대시보드에서 마지막 감사 시각을 조회해 보니 마지막 실행 이후 며칠째 감사가 실행되지 않고 있었어요.

당시 기록은 다음과 같았어요.

마지막 감사: 며칠 전 새벽 (KST)
확인 시각:   현재 시각 (KST)
누락:        연속된 며칠

대시보드에는 감사끊김 천여 건이라는 숫자가 표시돼 있었지만, 이 숫자만으로는 며칠 동안 실행이 비었다는 사실을 바로 알아보기 어려웠어요.

원인은 스케줄러의 실행 조건에 있었어요.

if (currentTime.getHours() !== scheduledHour) return false;

감사는 설정한 정각에 프로세스가 실행 중일 때만 시작됐어요. 하지만 대시보드는 Kubernetes 워커가 아니라 개발 PC에서 돌아가는 로컬 데몬이었어요. 새벽에 PC가 꺼져 있거나 정각이 지난 뒤 프로세스를 재시작하면 그날은 감사를 실행할 기회가 사라졌어요.

실제로 확인했을 때도 프로세스는 예정된 감사 시각이 지난 뒤에 시작됐어요. 실행 시각이 이미 지났기 때문에 그날 감사 역시 실행될 예정이 없었어요.

마지막 실행 시각을 저장하는 상태가 메모리 변수라는 문제도 겹쳤어요. 프로세스를 재시작할 때마다 값이 null로 초기화돼 재시작 전에 감사를 실행했는지 판단할 수 없었거든요. 정각 실행 여부와 휘발성 메모리에 의존하는 구조가 로컬 데몬의 수명주기와 맞지 않았던 거예요.

실행 조건을 바로잡아 정각을 놓쳤다는 이유만으로 하루 전체를 건너뛰지 않게 했어요. 실행 여부도 프로세스 메모리에만 의존하지 않도록 바꿔, 재시작한 뒤에도 감사 이력을 기준으로 동작하게 했어요.

세 겹의 탐지망이 맡는 역할

변경한 뒤에는 서로 다른 세 가지 신호로 크롤러 상태를 확인할 수 있게 됐어요.

각 신호가 그 자체로 완전한 것은 아니에요. 오류 기록은 정상 종료처럼 보이는 실패를 놓치고, 커버리지 감사는 접근 방식에 따라 제외 대상이 생겨요. 마지막 수집 시각은 학교가 실제로 글을 올리지 않는 기간을 장애로 오인할 수 있어요.

중요한 것은 완벽한 지표 하나를 만드는 게 아니라 서로 다른 사각지대를 지닌 관측 결과를 교차하는 일이었어요. 마지막 수집 시각으로 탐지 범위를 넓히고 기존 감사 결과로 오탐을 줄였으며, 실행 이력을 확인해 감사 작업 자체가 멈추지는 않았는지도 살폈어요.

그 결과 한 초등학교에서 석 달 넘게 수집이 중단된 일이나 며칠 동안 감사가 누락된 일처럼 기존 구조에서 놓치기 쉬운 상태를 대시보드에서 확인할 근거가 생겼어요. 이번 변경으로 모든 수집 중단을 자동 판정하거나 알림까지 완성한 것은 아니에요. 대신 “오류가 없으니 정상”이라는 가정을 버리고, 운영자가 조용한 실패를 조사할 수 있는 상태로 끌어올렸어요.


이 글 고치기
이 글 공유하기:

이전 글
만료 파일 삭제를 안전하게 활성화하는 설계