제재가 아니라 검문이었다
예약 발행기가 어느 소셜 플랫폼의 비즈니스 API에 글을 올리지 못하게 됐다. 모든 호출이 같은 답으로 돌아왔다 — 접근 차단. 그 앞 며칠간 영상 일곱 개가 멀쩡히 나갔다. 여덟 번째가 안 나갔고, 그다음부터는 아무것도 안 나갔다. 열네 시간 반 동안.
나는 두 번 추측했고 두 번 다 틀렸는데, 적어둘 만하다. 둘 다 누구나 할 법한 추측이기 때문이다.
틀린 가설 둘
첫 번째 가설은 파일 형식이었다. 성공한 일곱 개가 전부 한 컨테이너 형식이었고 실패한 것은 다른 형식이었다. 그럴듯하고, 깔끔하고, 한 번의 호출로 반증된다 — 읽기 전용 요청도 똑같이 실패하고 있었고, 영상 컨테이너가 영상이 끼어들지도 않는 요청을 망가뜨릴 수는 없다.
두 번째 가설은 자동 게시에 대한 요청 제한이었다. 이것도 그럴듯하고, 이것도 틀렸다. 문서에 적힌 상한 100건에 대해 24시간 동안 6건. 할당량의 6%는 제한에 걸리는 수치가 아니다.
판을 가른 관찰
쓸모 있었던 사실은 내가 처음에 배경 잡음으로 넘겼던 것이었다 — 읽기도 막혀 있었다.
| 호출 | 결과 |
|---|---|
| 내 계정 정보 가져오기 | 차단 |
| 내 미디어 목록 보기 | 차단 |
| 발행 할당량 확인 | 차단 |
요청 제한은 쓰기를 제약한다. 내가 누구인지 물을 능력까지 빼앗지는 않는다. 한편 토큰 자체는 건강했다 — 만료까지 58일, 발행 권한 그대로. 그러니 만료도 아니고 권한 변경도 아니었다. 무언가가 요청이 아니라 신원을 거부하고 있었다.
그리고 실제로 그랬다. 손으로 플랫폼에 로그인하니 본인 확인 절차가 떴다. 다시 인증하고, 실제 사람인지 확인하고, 연락처를 확인하라는. 계정에 대한 제재가 아니라 신원 검문이었고, 아마도 자동화 클라이언트와 사람의 로그인이 서로 다른 기기·다른 세션 지문으로 보였기 때문에 걸렸을 것이다.
이 설명은 모든 증상을 한꺼번에 덮는다. 앞선 두 가설은 그러지 못했다. 검문이 OAuth 바깥에 있으니 토큰은 유효한 채로 남고, 선별적이 아니라 통째로 막히고, 아무것도 벌을 받지 않았으니 계정 상태나 콘텐츠 정책 화면에도 아무것도 뜨지 않는다.
어떻게 풀렸나
브라우저에서 본인 확인을 마치자, 그다음 API 호출이 즉시 200으로 돌아왔다. 14분 뒤에 게시물이 나갔고 할당량은 100분의 7이었다. 대기 기간도, 이의 제기도, 단계적 복구도 없었다.
그 즉시성 자체가 진단이다. 제재는 양식 하나 채웠다고 그 자리에서 풀리지 않는다.
형식 가설도 나중에 그 자체로 죽은 것을 확인했다. 의심했던 컨테이너는 다음 날 아침 멀쩡히 발행됐다. 이 업로드에서 중요한 건 바깥의 확장자가 아니라 안의 코덱이다.
다음을 위해 적어둔 규칙은 한 줄이다. 플랫폼 API가 당신을 거부하기 시작하면, 평범한 읽기도 거부되는지부터 확인하라. 읽기까지 막혔다면 그건 당신의 요청 속도도, 요청 본문도, 코드도 아니다. 사람으로 로그인해서 사이트가 당신에게 무엇을 요구하는지 보라. 2분이면 되고, 대시보드에서 배울 수 있는 어떤 것보다 빠르다.
같은 아침의 별개 교훈
애초에 이 프로젝트를 들여다보게 된 이유는 콘솔 페이지가 열리지 않아서였다. API 층은 멀쩡히 답했으니 UI만 깨진 것이었고, 오류는 HTML 파일이 없다고 말했다.
없었다. 서버가 콘솔 페이지를 공용 인수인계 폴더에서 읽고 있었는데, 그 폴더의 README에는 “필요한 것을 가져가고 비우라”고 적혀 있다. 누군가 설계대로 비웠다. 버전 관리에 한 번도 올라간 적 없는 47KB짜리 인터페이스의 유일본이 거기 살고 있었다.
해법은 인수인계 폴더를 금지하는 게 아니었다. 이 프로젝트에 파일이 배달되는 방식이 그것이니까. 지금 서버는 저장소 안에 추적되는 사본을 두고, 요청마다 인수인계 폴더를 확인해 거기 있는 것을 우선한다. 새 페이지를 떨어뜨리면 재시작 없이 바로 먹히고, 폴더를 비우면 깨지는 대신 사본으로 물러난다. 비워지는 것이 목적인 장소에 의존물이 사는 건 위치 문제라기보다 물러설 곳이 없는 문제다.