기존 로직 소개
저희 서비스는 매일 이커머스 서비스의 상품/지표를 수집해 내부 분석에 활용합니다. 수집 파이프라인은 다음과 같이 동작합니다. 먼저 Kubernetes CronJob이 스케줄에 맞춰 SNS Topic으로 수집 요청을 발행합니다. 이 Topic은 회사명 태그를 기준으로 각 파트너사에 대응하는 SQS 큐로 팬아웃됩니다. 이후 Portainer에서 관리되는 Crawler가 Selenium을 통해 해당 사이트에서 데이터를 수집하고, 결과를 crawled-response 큐로 적재합니다. 마지막으로 CrawlerPipeline이 이 응답을 소비하여 내부 DB에 저장합니다.
참고로, 우리 Crawler는 페이지 크롤링 외에도, 가능한 구간은 사이트의 공개 API를 직접 호출해 응답을 받아 처리하도록 구현되어 있습니다. 이렇게 크롤링과 API 호출을 병행해 신뢰성과 수집 속도를 함께 확보하고 있습니다.
즉, Cron 스케줄 → SNS 팬아웃 → SQS별 크롤러 실행 → 응답 큐 → 파이프라인 적재 → DB 로 이어지는 이벤트 드리븐 데이터 적재 구조입니다.
문제 상황
11월 6일, 운영 환경의
cuco-crawler-pipeline이 CrashLoopBackOff에 빠지면서 수집이 멈췄습니다.우리 파이프라인은 위 그림 처럼
K8s CronJob → SNS → (회사별) SQS → Selenium Crawler → Crawled Response Queue → CrawlerPipeline → MySQL 순서로 흘러갑니다. 이 체인이 한 군데만 막혀도 데이터가 통째로 빠지게 됩니다.가설과 확인
처음엔 세 가지를 의심했습니다.
- 네트워크 단절
- 배포 실패
- API 응답 스펙 변경
Rancher에서 Pod 로그를 보고, 애플리케이션 로그까지 함께 확인해 보니 pydantic validation 오류가 반복되고 있었습니다. 문제 필드는
inquiry.order.customer_id였고, 최신 API 응답에서 이 필드가 빠지는 경우가 생긴 걸 확인했습니다.버그 수정
일단 장애부터 풀기 위해
customer_id를 Optional로 바꿨습니다.from typing import Optional from pydantic import BaseModel, Field class InquiryOrder(BaseModel): customer_id: Optional[int] = Field(default=None)
배포 후 파이프라인은 바로 정상화되었고, 빠진 N건은 다시 크롤러를 돌려서 복구 했습니다.
재발 방지
- Validation에 실패한 데이터는 DLQ로 분리하고, 정상 데이터는 계속 처리되도록 구성해 전체 파이프라인이 멈추지 않게 했습니다.
- DLQ에 쌓인 메시지는 이후 로컬 환경에서 개별적으로 가져와 디버깅이 가능하도록 했습니다.
- Kubernetes Deployment의 restart=always 설정을 통해, 일부 오류로 인해 서비스가 지속적으로 중단되는 상황(CrashLoopBackOff) 을 방지했습니다.
- 에러 로그를 Datadog으로 수집하고 Slack 알림과 연동해, 문제를 빠르게 인지하고 대응할 수 있도록 했습니다.