쩜오도깨비 트러블슈팅: 문제별 해결 가이드

업계에서 “쩜오도깨비”라는 이름이 쓰이는 맥락은 하나로 단정하기 어렵다. 누군가는 특정 예약 자동화 스크립트를 부를 때, 또 누군가는 데이터 수집 매크로나 알림 봇의 별칭으로 쓴다. 구현은 제각각인데, 부르는 이름은 비슷하다 보니 문제를 이야기할 때도 엇갈린다. 그래서 이 글은 특정 제품을 다루지 않는다. 그 대신 커뮤니티에서 널리 공유되는 동작 패턴, 예컨대 헤드리스 브라우저와 프록시를 엮은 자동화, 계정 기반의 이벤트 응답, 일정 주기 크롤링과 푸시 알림 같은 공통 요소를 기준으로, 현장에서 반복되는 증상별 진단법과 복구 절차를 정리했다. 실제로 강남권 커뮤니티에서 회자되는 강남도깨비, 강남쩜오도깨비 같은 별칭을 포함해도 문제의 핵심은 같다. 환경 의존성과 탐지 회피, 계정 안정성, 스케줄러 정확도, 리소스 관리가 성패를 가른다.

먼저 짚어야 할 전제: 이름보다 동작을 본다

두 사람이 같은 “쩜오도깨비”를 말해도 내부는 전혀 다를 수 있다. Python 셀레늄 기반인지, Playwright와 자바스크립트를 쓰는지, 모바일 에뮬레이터를 거치는지에 따라 원인과 해법이 크게 갈린다. 같은 증상이라도 하나는 DNS 캐시 문제, 다른 하나는 TLS 핸드셰이크 실패일 때가 있다. 트러블슈팅은 이름이 아니라 동작 경로를 따라가야 한다. 실행 파일이 하나뿐인 배포라도, 내부적으로는 프로세스가 3개 이상일 수 있고, 외부 서비스 호출과 메시지 큐, 브라우저 컨트롤러까지 얽혀 있다. 그러니 증상을 기능 경계에 맞춰 나눠 보는 습관이 유리하다.

10분 만에 하는 1차 진단

아래는 초기 확인을 위한 짧은 점검표다. 어디가 부러졌는지 대략의 위치만 잡아도 수리 시간은 줄어든다.

    시각 동기화 확인: 시스템 시간이 2분 이상 틀어지면 OTP, 쿠키 만료, TLS 검증에 연쇄 오류가 난다. NTP 동기화를 강제한다. DNS와 프록시 체인: systemd-resolved나 로컬 DNS 캐시를 비활성화했다면 도메인 재해석이 지연될 수 있다. 프록시 체인은 hop 수와 헬스 체크 주기를 로그로 남긴다. 스토리지 여유: 디스크가 95% 이상이면 크래시와 임시 파일 삭제가 뒤엉킨다. 특히 헤드리스 브라우저는 세션 디렉터리가 가득 차면 무응답으로 빠진다. 의존 서비스 가용성: 로그인 SMS, CAPTCHA, 외부 API의 SLA를 모니터링한다. 본 서비스가 멀쩡해도 주변 의존성이 흔들리면 전체가 멈춘다. 최근 변경 추적: 버전, 환경 변수, 프록시 목록, 스케줄 주기 변경 내역을 릴리스 노트와 함께 기록한다. 문제가 생긴 시점과 변경 시점을 포개보면 절반은 거기서 답이 나온다.

로그인과 계정이 막힐 때: 탐지 규칙을 넘는 최소한의 위장

자동화를 막기 위한 사업자 측의 패턴 탐지는 예측 가능하면서도 집요하다. 현장에서 자주 본 규칙은 세 가지였다. 첫째, 동일 대역에서 짧은 시간 안에 로그인 실패가 누적되는 패턴. 둘째, 브라우저 핑거프린트가 엔진 디폴트 값에서 거의 변하지 않는 경우. 셋째, 시간대와 언어 설정이 비정상적으로 변동하는 세션이다.

실무에서 통했던 절충안은 과도한 위장 대신 일관성이 높은 사용자 흔적을 만드는 것이다. 예를 들어, 헤드리스 브라우저라도 WebGL, Canvas, AudioContext 지문을 매 실행마다 바꾸지 말고 계정 단위로 고정한다. Accept-Language와 timezone, locale은 계정의 실제 사용 지역과 맞춘다. VPN이나 프록시를 쓴다면 같은 대역 내에서 회전시키되, 로그인, 탐색, 액션까지 한 세션의 출발지는 유지한다. SMS 인증은 지연과 실패가 늘 있으니 두 개 이상의 공급자를 라운드 로빈으로 묶고, 실패율이 10%를 넘으면 자동으로 대기 큐에 넣어 재시도한다.

CAPTCHA와 OTP의 경계

이미지 캡차는 외주 솔버를 붙여 해결하는 경우가 많다. 다만 탐지 단계에서 캡차가 뜨는 빈도가 치솟는 순간이 있다. 이때 보이는 현상은 수집 성능의 급감이 아니라 계정 품질의 급락이다. 무리한 재시도는 계정 소각으로 곧장 이어지니, 큐 대기 시간을 3배 이상 늘리고 세션 쿠키를 폐기한 뒤 IP와 지문을 교체한다. OTP는 단말 시간 불일치가 은근히 큰 비중을 차지한다. 컨테이너나 가상 머신을 많이 띄우는 환경일수록 NTP가 중단된 인스턴스가 생긴다. 모니터링 항목에 NTP 오프셋을 추가해 500 ms 초과 시 경고를 울리면 인증 실패의 상당 부분을 사전에 잡을 수 있다.

image

헤드리스 브라우저가 멈추는 이유와 재시동 전략

현장에서 자주 겪는 멈춤 현상은 세 가지 결로 설명된다. 렌더링 파이프라인 메모리 누수, 파일 디스크립터 고갈, 그리고 Xvfb 같은 가상 디스플레이와 드라이버 버전 불일치다. 첫 증상은 프로세스가 살아 있으나 이벤트 루프가 반응하지 않는 상태다. CPU는 낮고 RSS는 꾸준히 증가한다. 이 경우 스냅샷 메모리 덤프를 떠서 누수를 찾는 데 시간을 쓰기보다, 워커의 수명 제한을 먼저 건다. 탭 기준 200회 내외, 혹은 90분마다 재기동처럼 보수적으로 잡으면 처리율이 안정된다.

파일 디스크립터 문제는 로그에서 EMFILE, too many open files가 안 보여도 빈번하다. 소켓, 임시 파일, 세션 디렉터리가 누적되면 프로세스가 조용히 굳는다. 시스템 한도와 프로세스 강남쩜오도깨비 한도를 분리해서 올리고, 임시 디렉터리를 주기적으로 청소한다. 마지막으로 드라이버 불일치는 배포판 업데이트 직후 튄다. Playwright나 Chromium의 내장 바이너리 버전과 호스트 라이브러리의 미묘한 차이가 원인일 때가 많다. 컨테이너 이미지를 고정하고, 베이스 이미지를 바꿀 때는 일괄 교체한다. 반쯤만 업데이트한 상태가 제일 위험하다.

스케줄러가 새벽에만 삐끗한다면

야간 시간대에만 실패율이 튄다고 문의를 받으면, 대부분은 두 갈래다. 백업 작업과 로그 로테이션이 I/O를 잡아먹거나, 크론과 시스템 타이머가 중복으로 잡혀서 경합을 만든다. 백업은 블록 디바이스 IOPS를 잠깐이라도 10% 넘게 쓰면 헤드리스 브라우저 세션이 느려지고 타임아웃이 난다. 스케줄 분산으로 해결하는 경우가 많다. 일감을 같은 시점에 몰지 말고, 초 단위로 지연을 두어 균등하게 배분한다. 크론 대신 작업 큐를 쓰는 것도 도움이 된다. 실패한 잡은 지수 백오프로 밀어내고, 중복 실행을 막기 위해 분산락을 건다. 락은 Redis보다는 DB의 고유 제약을 쓰는 편이 간결할 때가 있다. 단일 행을 만들어두고 UPDATE로 소유권을 잡는 식이다.

네트워크가 문제인지, 원격 서비스가 문제인지

프록시 체인을 쓰면 문제의 위치를 가늠하기 어려워진다. 첫 홉에서 TLS 핸드셰이크가 실패했는지, 목적지에서 RST를 보냈는지, 중간 프록시가 연결을 닫았는지 로그로 구분해야 한다. 각 단계에 타임스탬프와 원격 주소, 지연 시간을 찍는 습관이 중요하다. 한 현장에서는 강남쩜오도깨비라고 부르던 작업이 특정 시간대에만 느려졌다. 추적 끝에 첫 홉 프록시 풀에서 같은 서브넷 IP가 과도하게 재사용되고 있었고, 목적지 쪽 레이트 리밋이 대역 단위로 걸려 있었다. 두 개의 독립 프록시 풀을 번갈아 쓰도록 바꾸고, 세션 내부에서는 IP 고정을 적용하니 실패율이 3분의 1로 떨어졌다.

DNS도 가끔 범인이다. 도메인이 짧은 주기로 A 레코드를 회전시키는데 로컬 캐시가 갱신을 누락하는 경우다. 해결은 단순하다. TTL을 존중하고, 캐시 서버가 다중화되어 있다면 동일 요청의 분배 전략을 바꾼다. 일부 캐시가 오래된 값을 고집하면 트래픽이 한쪽으로 쏠린다. 시스템 레벨의 캐시를 끄고 애플리케이션 레벨에서 짧은 TTL로 재해석하는 것도 방법인데, 호출 빈도가 높으면 오히려 지연을 키운다. 트래픽 패턴을 본 뒤 선택해야 한다.

데이터가 엉키는 방식과, 되돌리는 법

빠르게 움직이는 자동화는 데이터 정합성에서 상처를 받기 쉽다. 흔한 실수는 멱등성을 무시하는 것이다. 같은 작업이 재시도될 수 있다는 전제를 놓치면, 중복 삽입과 삭제가 꼬리를 문다. 수집 파이프라인이라면 레코드의 자연키를 찾아 고유 제약으로 박아두고, 충돌 시 업데이트하도록 일관된 정책을 정한다. 자연키를 찾기 어렵다면 해시를 쓴다. 다만 해시 입력에 불안정한 필드를 섞지 않는다. 공백이나 순서가 바뀌어도 동일 레코드로 인식되게 전처리를 맞춘다.

복구는 신중해야 한다. 대량 롤백은 시스템을 한 번 더 흔든다. 현장에서는 타임박스된 리플레이가 효과적이었다. 오류가 발생한 기간만큼 큐에서 레코드를 다시 흘려보내고, 다운스트림 시스템에는 복구 모드로 전환 신호를 넘겨 중복 처리를 허용한다. 이때 관측 지표를 고정해야 한다. 성공률, 처리 지연, 중복 비율을 1분, 5분, 15분 창으로 본다. 정상으로 보이는 1분 지표에 속아선 안 된다.

로그, 많다고 좋은 게 아니다

트러블슈팅 때마다 “로그를 더 남기자”로 끝나면 금세 자기 발목을 잡는다. 디스크를 가득 채우고, PII가 섞이고, 읽는 사람도 지친다. 로그는 질문에 답하도록 설계한다. 계정 단위로 추적이 가능한 식별자, 외부 호출의 왕복 시간과 상태 코드, 재시도 횟수, 브라우저 크래시의 마지막 유의미한 이벤트 정도면 충분하다. 반대로 브라우저 DOM 스냅샷, 전체 HTML 덤프는 필요할 때만 샘플링한다. 100건 중 1건이면 충분하다. 샘플을 뽑을 때는 오류 코드가 같은 사건을 묶지 말고, 시간대를 넓게 퍼뜨린다. 같은 원인만 반복 채집해선 해결책이 나오지 않는다.

리소스 관리의 감각: CPU보다 메모리, 메모리보다 파일

현장 체감으로는 CPU는 잘 터지지 않는다. 병렬도가 높아도 코어를 맞춰서 늘리면 된다. 반면 메모리와 파일 디스크립터는 과소평가된다. 헤드리스 브라우저 하나가 평균 200에서 400 MB를 먹는다. 녹화나 스크린샷을 켜면 2배로 뛴다. 워커 20개만 띄워도 8 GB가 가볍다. 거기에 프록시 터널과 큐 클라이언트, 이미지를 다루는 라이브러리까지 합치면 남는 메모리가 생각보다 없다. 운영자는 워커 수를 늘리기보다 처리량을 높이는 튜닝을 먼저 고민해야 한다. 세션 재사용, 요청 합치기, 백그라운드 리소스 해제 주기 단축이 성과를 낸다.

파일 디스크립터는 눈에 잘 안 보인다. 소켓, 파이프, 임시 파일이 전부 디스크립터를 잡아먹는다. 프로세스 한도를 4096에서 65535로 올려도 문제는 남는다. 소비 패턴이 바뀌지 않으면 한도만 키운 효과는 금세 사라진다. 해결은 정리다. 세션 종료 시 핸들 정리를 명시적으로 호출하고, 실패 분기에서도 동일 루틴을 태운다. 브라우저 크래시 같은 비정상 종료를 대비해 프로세스를 감싸는 워처를 두고, exit 코드를 분류해 재기동과 격리를 구분한다.

버전 업데이트는 한 번에, 롤백은 더 빠르게

머리로는 알면서도 자주 어기는 원칙이다. 브라우저 엔진, 드라이버, 런타임, 외부 라이브러리의 버전을 들쭉날쭉 올리면 문제를 재현하기 어렵다. 강남도깨비나 쩜오도깨비 같은 별칭으로 묶인 배포에서도, 안쪽 구성 요소는 같아야 관리가 된다. 이미지 빌드를 원자적으로 만들고, 해시로 추적하며, 한 배포 단위에서 버전 세트를 고정한다. 롤백은 더 빨라야 한다. 두 개의 안전 그물을 깔아 둔다. 첫째, 마지막 성공 빌드 이미지를 레지스트리에서 즉시 풀 수 있게 태그를 유지한다. 둘째, 데이터 마이그레이션은 역방향 스크립트를 준비한다. 마이그레이션이 단방향이면 배포 자체가 도박이 된다.

알림과 대기열의 균형

문제가 생길 때 알림이 늦으면 손해가 커진다. 반대로 과도한 알림은 무시된다. 기준을 숫자로 정한다. 예를 들어 로그인 실패율이 3분 창에서 8%를 넘으면 경고, 15%를 넘으면 장애로 판단한다. 큐 적체는 대기 시간으로 본다. 평균 대기 30초 초과는 경고, 2분 초과는 장애. 이 기준은 팀과 합의해 문서로 남긴다. 모바일 푸시와 메신저 알림은 담당자와 교대자에게 선별적으로 보낸다. 모든 채널에 전파하면 누구도 책임감을 갖지 않는다. 대기열은 지수 백오프로 재시도하지만, 상한을 넘기면 휴면 큐로 옮긴다. 휴면 큐는 다음 새벽 시간대에 배치 복구를 시도한다. 이렇게 나누면 실시간 경로는 가벼워지고, 야간에 시스템이 스스로 회복할 기회를 가지게 된다.

프라이버시와 규정 준수

자동화가 불가피한 영역이라도 법과 약관을 어기면 결국 시스템은 중단된다. 특히 계정 공유, 비인가 API 호출, 개인정보의 비식별화 실패가 문제를 만든다. 계정 자격 증명은 KMS나 시크릿 매니저를 통해 주입한다. 평문 환경 변수, 로그 삽입, 저장소 내 하드코딩 같은 편법은 언젠가 유출로 돌아온다. 수집 데이터 중 개인을 식별할 수 있는 조각은 저장 전에 해시나 토큰으로 바꾼다. 해시 입력에 솔트를 추가하고, 운영자라도 원문을 복원할 수 없게 설계한다. 만약 강남도깨비라는 이름으로 묶인 작업이 외부 고객을 상대한다면, 데이터 처리 동의와 보관 기간을 계약서에 박아두는 수고를 아끼지 않는다.

현장에서 자주 본 네 가지 증상과 해법

한동안 상담을 받으며 반복해서 만난 사례를 정리한다. 각각의 상황은 이름만 다르고 본질은 닮았다.

첫째, 새벽 특정 시간대에만 예약 시도가 실패한다. 원인은 예약 대상 서비스의 내부 락과 캐시 갱신 주기, 그리고 그 시간대에 몰리는 자동화 경쟁 때문이다. 해결은 두 갈래였다. 시도 시점을 500에서 1200 ms 랜덤 오프셋으로 퍼뜨리고, 실패 시 즉시 재시도 대신 지수 백오프로 3회만 시도한다. 더 중요한 건 상태를 관찰하는 것이다. 대상 페이지의 카운트다운이나 요청 큐 대기열 길이를 스니핑해 혼잡도를 추정하고, 혼잡도가 임계값 이상이면 대기 모드로 전환한다.

둘째, 셀레늄 기반 자동화에서만 로그인 폼이 보이지 않는다. 보통 사용자 스크립트가 실행되기 전 CSP, Feature-Policy 변화나 Lazy load 타이밍 문제다. 해결은 DOMContentLoaded가 아니라 networkidle과 프레임 안정 이벤트를 기준으로 전환하는 것. 필요하다면 request interception으로 폰트와 비주요 리소스를 막아 로딩 변동성을 낮춘다. 헤드리스 플래그가 켜져 있는 흔적을 없애는 패치가 필요할 때도 있다. Navigator.webdriver 제거, WebGL 벤더 스푸핑 같은 익숙한 기법 말이다. 다만 계정별로 지문을 고정하는 원칙은 잊지 않는다.

셋째, 수집 데이터에서 특정 필드가 간헐적으로 빈 값이다. 페이지 구조가 AB 테스트로 갈린 상황이고, 셀렉터가 한쪽 변형을 놓친 경우다. 해법은 복수 셀렉터 전략이다. CSS 셀렉터와 XPath를 혼용하고, 실패 시 대체 경로를 순차 적용한다. 동시에 페이지 스냅샷을 전량 저장하지는 말고, 필드 누락이 발생한 케이스만 샘플 스냅샷을 남긴다. 그래야 비용과 개인정보 리스크를 줄인다.

image

넷째, 트래픽이 늘자마자 계정이 대거 정지됐다. 흔한 실수는 IP 풀을 늘리면서 세션 일관성을 깨뜨린 것이다. 최초 로그인은 A 대역, 주요 액션은 B 대역, 콜백은 C 대역에서 발생하면 의심 점수가 높아진다. 프록시 라우팅 정책을 계정 단위로 묶고, 하나의 시나리오 안에서는 출발지를 유지한다. 2주 정도 품질을 지켜보면 안정화 여부가 보인다.

사람의 시간과 기계의 시간

기계는 정확하고 빠르지만, 사람은 맥락을 만든다. 스케줄러가 아무리 촘촘해도 담당자가 잠깐 화면을 보며 “오늘은 페이지가 다르게 떴다”를 알아채는 눈치가 필요하다. 강남도깨비니 쩜오도깨비니 이름을 무엇으로 부르든, 마지막 성패는 같은 지점에서 갈린다. 다르게 느껴지는 변화를 포착하고, 그 변화를 설명하는 로그와 계측을 차분히 쌓는 힘 말이다. 경험 많은 운영자는 노이즈와 신호를 가르는 기준을 스스로 만든다. 실패율 1% 상승을 보고도 웃고 넘길 때가 있고, 성공률 99%를 보면서도 불편함을 느낀다. 그 차이가 결국 장애를 줄인다.

보안과 계정 운영을 위한 짧은 체크리스트

    지문 일관성: 계정별 핑거프린트를 고정하고, 브라우저 버전과 플러그인을 빈번히 바꾸지 않는다. 자격 증명 관리: KMS나 시크릿 매니저에서 세션 시 주입. 평문 저장 금지. 재시도 상한: 로그인 실패는 3회 이하, OTP는 2회 이하로 제한하고 지연을 둔다. IP 정책: 세션 내부 IP는 고정, 세션 간에는 제한적 회전. 대역 중복 사용 비율 모니터링. 로그 보존: 개인정보를 토큰화하고, 보존 기간을 30일 등 짧게 유지. 감사 목적의 샘플만 장기 보관.

마이그레이션과 환경 차이에서 오는 미묘한 버그

운영체제나 컨테이너 런타임을 바꾸면 처음엔 멀쩡하다가, 일주일 뒤에야 문제가 보일 때가 있다. 타임존 기본값, 폰트 렌더러의 차이, ICU 라이브러리 버전이 문자열 정렬 순서를 바꾸면서 결과 셋이 바뀐다. PDF 렌더가 조금 다른 모습으로 나오고, 그걸후처리하는 파이프라인이 깨진다. 이런 종류는 사전 테스트 케이스로 걸러내기 어렵다. 해결은 관측이다. 마이그레이션 직후 일주일, 하루 단위의 샘플 비교를 자동화한다. 동일 입력에 대한 출력의 해시를 비교하고, 차이가 발생한 건만 사람 검토로 넘긴다. 수작업이지만, 가장 덜 아프게 넘어가는 방법이었다.

비용의 감각: 빨리 가는 데 드는 값

자동화가 빠르면 좋다. 그런데 빠름에는 비용이 붙는다. 프록시 비용, 문자 인증 비용, 브라우저 리소스 비용이 합쳐지면 사용자가 체감하는 가치는 줄어든다. 무작정 병렬도를 높이기보다, 응답의 가치가 높은 구간에만 속도를 집중하는 게 이득이다. 예를 들어 예약 오픈 직전 2분만 워커를 3배로 늘리고, 그 외 시간대에는 절반으로 낮춘다. 알림은 단일 채널이 아닌 다중 채널 중의 한두 개로 압축한다. 텔레그램과 푸시를 동시에 쏘는 것보다, 사용자가 선호 채널을 고르게 하고 그쪽만 최적화하는 편이 낫다. 숫자는 솔직하다. 비용 지표를 대시보드 첫 화면에 올려두면, 과한 욕심이 줄어든다.

교육과 문서, 그리고 호흡

트러블슈팅 노하우는 문장으로 남겨야 다음 사람이 빠르게 이어받는다. 이 글처럼 결과만 남기기보다는, 증상, 추정, 실험, 반례, 결론의 순서로 메모를 쌓는다. 강남도깨비나 강남쩜오도깨비 같은 이름으로 팀 내부에서 부르더라도, 외부 기준으로 읽어도 이해되는 어휘를 쓴다. 신입은 이름보다 동작을 학습해야 한다. 겉보기 명칭에 매달리면 문제를 늘 외부 탓으로 돌리게 된다. 반대로 동작을 이해한 사람은 환경을 바꾸고도 금세 적응한다. 문서는 살아 있어야 한다. 버전이 움직일 때마다 갱신되고, 실패가 있을 때마다 더 정교해진다.

마무리 아닌 마무리

자동화는 살아 있는 생물과 비슷하다. 계절을 타고, 환경과 상호작용하고, 때로는 예측을 비웃는다. 그래서 트러블슈팅은 지식의 나열이 아니라 태도에 가깝다. 작은 단서에서 원인을 추적하고, 복구와 예방을 동시에 설계한다. 쩜오도깨비라는 별칭이 무엇을 가리키든, 원리는 변하지 않는다. 시간 동기화, 네트워크 경로, 계정 일관성, 리소스 한계, 관측과 기록. 이 다섯 가지만 흔들리지 않으면 문제는 풀린다. 그리고 풀리지 않는 문제라도, 손실을 통제하며 배울 수 있다. 그게 오래 가는 운영의 얼굴이다.