카탈로그를 502로 만든 조회수 카운터
배송
판매하는 모든 키트를 나열하는 페이지는 출시 후 약 41시간 동안 방문자마다 502 Bad Gateway로 응답했습니다. 각 키트의 개별 페이지, 홈 페이지, 결제는 모두 정상 작동했습니다. 문제는 페이지 조회수를 세는 몇 줄의 코드였습니다.
버그
저희 스토어 서버는 키트 페이지 방문 횟수를 집계합니다. 요청이 어떤 키트용인지 확인하기 위해 경로 두 번째 부분을 가져왔습니다:
self._count_view(path.strip("/").split("/")[1] if path.count("/") >= 2 else "")
/kits/studio/의 경우 studio입니다. 카탈로그 자체인 /kits/는 경로에 슬래시가 두 개 있어 가드를 통과하지만, 슬래시를 제거하면 부분 하나가 남고 [1]에서 IndexError가 발생합니다. 핸들러는 Python의 표준 http.server입니다. do_GET에서 예외가 발생하면 응답 없이 연결이 종료되고, Cloudflare는 방문자에게 502를 표시했습니다.
수정은 해당 부분이 없을 때 빈 문자열을 반환하는 작은 함수 하나입니다. 조회수 집계에는 키트 이름만 필요했고, 집계 대상 페이지를 망가뜨릴 필요는 전혀 없었습니다.
저희 점검 항목들이 이 문제를 놓친 이유
- 업타임 워치독은 홈 페이지만 열었습니다. 홈 페이지는 정상이었습니다.
- 릴리스 단계는 배포하는 각 키트를 확인했습니다: 페이지, 결제 리디렉션, 유료 경로 전체를 점검했습니다. 하지만 모든 키트를 나열한 목록은 열지 않았습니다.
- 분석 도구로는 확인할 수 없었습니다. 카탈로그 방문자를 집계할 코드가 바로 크래시된 코드였습니다. 서버 로그에는 이미 답이 있었습니다. 그 41시간 동안 17개의 트레이스백이 있었고, 각각은 사람이든 크롤러든 페이지 대신 오류를 받은 요청이었습니다.
변경 사항
- 매시간 외부에서 모든 페이지를 확인합니다. 작은 작업이 사이트맵을 읽고 Cloudflare를 통해 방문자처럼 모든 영어 페이지와 나머지 42개 언어 중 하나를 순차적으로 엽니다. 연속 두 번 실패하면 이메일을 보내고, 복구되면 다시 이메일을 보냅니다.
- 릴리스 단계는 사이트 재구축 후 모든 영어 페이지를 엽니다. 하나라도 200으로 응답하지 않으면 완료를 거부합니다.
- 인덱스 페이지도 페이지로 취급합니다. 다른 페이지를 나열하는 페이지는 방문자가 가장 먼저 도달하는 곳이지만, 아무도 테스트하지 않은 페이지였습니다.
핵심 요약
요청 핸들러에서 수행하는 카운팅, 로깅, 추적 같은 부가 작업은 요청 실패의 원인이 되어서는 안 되며, 방금 수정한 페이지만 검사하는 페이지 체크는 해당 페이지로 링크되는 다른 페이지들을 놓칩니다. Night Watch의 워치는 홈 페이지만이 아니라, 반드시 포함해야 할 텍스트가 지정된 페이지 목록을 임의로 입력받아 검사합니다.