배포와 인프라11 분 읽기

"게시됨"이라고 적혀 있었는데, 검색엔 3일째 안 나왔습니다

마켓플레이스 API가 total 4를 주면서 items는 빈 배열을 줬습니다. 저는 그걸 '아직 게시가 안 됐다'로 읽고 로고를 만들고 있었습니다. 실제로는 이미 색인돼 있었고, 기본 응답에서 조용히 걸러지고 있었을 뿐입니다.

#api#gotchas#reality-check#measurement
왼쪽: 기본 스토어 API 응답이 total 4에 items 빈 배열이라 미게시처럼 보인다. 오른쪽: includeUnrunnableActors 플래그를 붙이면 같은 질의가 4개를 전부 돌려준다.
total은 4인데 목록은 비어 있었습니다. 그 간격이 3일치 오진이었습니다.

데이터 액터 4종을 마켓플레이스에 올렸습니다. 한국 부동산 실거래가를 정부 공개 API에서 받아 정규화해 주는 것들입니다. 사람이 아니라 기계가 고객인 축을 하나 실측해보려고 만든 자산입니다.

공개한 다음 날부터 추적 스크립트가 매일 한 줄씩 원장에 적습니다. 사흘 내내 같은 줄이었습니다.

{"listed": false, "u30": null, "gate": "D-27 · ?/5 · 색인 0/4"}

listed: false. 스토어 검색에 안 나온다는 뜻입니다. 저는 이걸 "신규 액터라 색인이 늦나 보다"로 읽고 사흘을 기다렸습니다.

원장이 "안 나온다"고 할 때, 무엇이 안 나오는 겁니까?

나흘째에 직접 API를 두드려봤습니다. 스크립트가 쓰는 것과 같은 공개 엔드포인트입니다.

curl "https://api.apify.com/v2/store?username=ootssu&limit=50"

응답이 이상했습니다.

{"data": {"total": 4, "items": []}}

total은 4인데 items는 비어 있습니다. 없는 게 아니라, 세기는 세는데 안 주는 겁니다.

문서를 다시 읽으니 파라미터 하나가 있었습니다.

curl "https://api.apify.com/v2/store?username=ootssu&limit=50&includeUnrunnableActors=true"
{"data": {"total": 4, "items": [
  "korea-apartment-transaction-prices",
  "korea-commercial-property-prices",
  "korea-officetel-prices",
  "korea-apartment-rent-prices"
]}}

네 개 다 있었습니다. 처음부터 색인돼 있었던 겁니다.

문서에는 이렇게 적혀 있습니다. 기본 검색 결과는 "자동으로 실행하기에 안전하지 않은 액터" 를 제외한다 — 예를 들어 KYC를 통과하지 않은 개발자의 액터, 또는 사용자 기반이 크지 않은 전체 권한 액터. 제 액터는 제한 권한이었으니, 남는 사유는 계정 인증이었습니다.

제가 사흘 동안 고친 것들

여기가 부끄러운 부분입니다. listed: false를 "미게시"로 읽은 뒤 저는 이런 일을 했습니다.

  • 콘솔의 공개 API 응답을 이미 잘 나가는 남의 액터와 필드 단위로 비교
  • 제 액터엔 pictureUrl이 없고 남의 것엔 있다는 걸 발견
  • 로고 4개를 디자인해서 만들고 업로드
  • 그리고 다시 검색 → 여전히 0/4

로고는 있어야 하는 게 맞습니다. 그건 낭비가 아니었습니다. 다만 그게 원인이 아니었습니다. 저는 증상 옆에 있는 다른 결함을 고치고 있었고, 그동안 진짜 원인은 계정 설정 화면에 문장으로 떠 있었습니다.

Billing details and payment method not set

콘솔이 계속 알려주고 있었는데, 저는 API 응답만 노려보고 있었습니다.

여기서 당신이라면 어디를 보겠습니까

목록에 내 것이 안 보일 때, 선택지는 대개 셋입니다.

  1. 내 물건이 부실하다 — 메타데이터, 이미지, 설명을 채운다
  2. 아직 안 올라갔다 — 게시 절차를 다시 밟는다
  3. 올라갔는데 안 보여준다 — 필터·권한·인증 상태를 본다

저는 1번과 2번을 오가며 사흘을 썼습니다. 3번을 먼저 확인했다면 10분이었습니다. 그리고 3번을 확인하는 방법은 대개 "카운트와 목록이 일치하는가" 한 줄입니다. totallen(items)가 어긋나면 그건 부재가 아니라 필터입니다.

계측이 두 상태를 한 값으로 뭉개고 있었습니다

진짜 결함은 API가 아니라 제 추적 스크립트에 있었습니다. 그 스크립트는 이렇게만 물었습니다. "검색 결과에 내 것이 있나?" 그리고 없으면 listed: false를 적었습니다.

그 한 값에 서로 완전히 다른 두 상태가 뭉개져 있었습니다.

  • 게시가 안 됐다 → 할 일: 게시 절차를 밟는다
  • 게시됐는데 필터에 가려졌다 → 할 일: 계정 인증을 끝낸다

원인이 다르면 할 일도 다릅니다. 같은 값으로 적으면 사흘 동안 엉뚱한 일을 하게 됩니다. 실제로 그랬습니다.

두 갈래로 나눴습니다.

found   = store_rows()                      # 사람이 검색해서 발견 가능한 것
indexed = store_rows(include_hidden=True)   # 색인엔 있으나 필터로 가려진 것 포함

그리고 원장에 적히는 문장을 바꿨습니다.

gate: "미시작 · ?/5 · 노출 0/4(색인 4)"
note: "색인 4/4인데 기본 검색 노출 0 — 안전필터(KYC 미통과 추정)로 가려짐"

이제 이 줄만 읽어도 다음에 뭘 해야 하는지 알 수 있습니다.

그리고 판정일을 옮겼습니다

부수 효과가 하나 더 있었습니다. 이 액터들에는 "공개 30일 뒤 타인 사용자 5명 이상"이라는 판정이 붙어 있었습니다. 공개일은 08-19, 판정일은 09-18.

그런데 방금 확인한 대로, 공개일부터 지금까지 이것들은 검색으로 발견될 수 없는 상태였습니다. 관찰창의 앞부분 전체가 "유입이 생길 수 없는 기간"입니다. 그대로 09-18에 사용자 수를 세면, 재는 것은 "기계가 이 데이터를 쓰는가"가 아니라 "인증이 언제 끝났는가"가 됩니다.

판정일을 고정 날짜에서 걷어내고 이렇게 바꿨습니다.

# 판정일은 공개일이 아니라 **노출 시작일 + 30일**이다.
# 노출 첫날은 원장에서 listed=true인 가장 오래된 행으로 자동 확정한다.
judge_on = _add_days(exposure_started_on(), OBSERVE_DAYS)

노출이 시작되기 전에는 판정일이 null이고, 게이트 표시는 미시작입니다. 날짜 계산은 월말·연말·윤년 넘김까지 자체검증을 붙였습니다 — 판정일이 하루만 밀려도 그건 다른 실험이 되니까요.

자가진단 3줄

외부 플랫폼에 뭔가를 올려두고 "왜 안 보이지"를 겪고 있다면, 코드 고치기 전에 이 셋부터 보세요.

  1. 응답의 카운트와 목록 길이가 일치합니까? total: 4, items: []는 부재가 아니라 필터입니다.
  2. 그 목록 API에 "숨은 것도 포함" 파라미터가 있습니까? 문서의 파라미터 표를 끝까지 읽어보세요. 대개 기본값이 조용히 뭔가를 빼고 있습니다.
  3. 당신의 추적 스크립트는 "없다"와 "가려졌다"를 구분해 적습니까? 한 값으로 뭉개면, 원인이 다른데 같은 대응을 하게 됩니다.

솔직한 부분

이 오진의 비용은 사흘과 로고 4개였습니다. 로고는 어차피 필요했으니 실제 손실은 사흘입니다. 크지 않습니다.

무서운 건 그 사흘이 아니라, 제가 원장을 믿고 그 위에서 계속 판단하고 있었다는 점입니다. listed: false는 거짓말이 아니었습니다. 정확히 자기가 물어본 것에 정확히 답하고 있었습니다. 다만 그게 제가 알고 싶었던 질문이 아니었을 뿐입니다.

계측을 만들 때는 "이 값이 무엇을 말하는가"보다 "이 값이 서로 다른 두 상태를 같은 값으로 적고 있지 않은가" 를 먼저 물어야 한다는 걸 또 배웠습니다. 실패를 빈 데이터로 치환하지 말라는 규칙과 정확히 같은 병입니다 — 그때는 exit 0이 성공처럼 보였고, 이번엔 빈 배열이 부재처럼 보였습니다.

혹시 지금 "안 나온다"고 적고 있는 계측이 있나요? 그 값이 부재인지 필터인지, 오늘 curl 한 번으로 확인해보세요.

관련 글