배포와 인프라9 분 읽기

리뷰가 사라졌다고 두 번 보고했다. 둘 다 틀렸다

리뷰 DB가 7주 멈춰 있었습니다. 수집기 고장이라고 보고했다가 틀렸고, 애플이 27건을 제거했다고 보고했다가 또 틀렸습니다. 브라우저를 한 번 열어보니 리뷰는 전부 그대로 있었습니다. API가 자기 데이터를 안 돌려주고 있었을 뿐입니다.

#app-store#api#supabase#reality-check
개념 도식: 왼쪽 터미널에 total 0이 다섯 줄, 오른쪽 브라우저에 리뷰 카드 3장. 가운데에 '같은 리뷰'.
API는 다섯 번 0을 돌려줬고, 같은 리뷰가 브라우저에는 멀쩡히 있었다.

App Store 리뷰를 API로 폴링해서 DB에 넣고, LLM으로 답글 초안을 만들고, 별점 기준으로 자동 발송하거나 승인 큐에 넣는 봇을 돌리고 있습니다. 앱 43개가 등록돼 있습니다.

플릿 전체를 점검하다가 리뷰 테이블이 2026-06-22에 멈춰 있는 것을 발견했습니다. 총 62건, 그 뒤로 한 건도 없습니다. 7주입니다.

이 글은 그 뒤로 제가 두 번 틀린 기록입니다.

먼저 하나 여쭙겠습니다. 당신의 파이프라인이 "데이터가 없다"고 말할 때, 그게 정말로 없는 것인지 소스가 안 주는 것인지 구분할 수단이 있습니까? 저는 없었습니다.

1차 오진 — "수집기가 죽었다"

pg_cron이 6시간마다 Edge Function을 부르는 구조입니다. 실패 응답을 찾으려고 net._http_response를 뒤졌습니다.

select status_code, count(*), max(created)
from net._http_response
where created > now() - interval '14 days' and (status_code is null or status_code >= 400)
group by 1;

실패 2건. "2주에 2건이면 정상이네, 그럼 왜 안 들어오지"로 갔습니다.

틀렸습니다. 이 테이블 보존은 약 6시간입니다.

select min(created), max(created), count(*) from net._http_response;
-- 2026-08-10 00:20 ~ 06:15, 24건

interval '14 days'를 써도 6시간짜리 데이터에 걸립니다. 쿼리는 성공하고 결과도 나오는데 창 자체가 존재하지 않습니다. 에러가 없는 종류의 오독입니다.

같은 자리에 함정이 하나 더 있습니다. pg_cron의 job_run_details.status = 'succeeded'net.http_post큐에 들어갔다는 뜻입니다. HTTP 응답과는 무관합니다. cron이 전부 succeeded인데 함수는 한 번도 성공하지 않은 상태가 아무 모순 없이 성립합니다.

2차 오진 — "리뷰가 제거됐다"

수집기를 못 믿게 되니 원천을 직접 봤습니다. 제 API 키로 43개 앱의 customerReviews를 전부 읽었습니다.

ASC가 돌려준 총 리뷰      35건
DB에 있는 리뷰            62건
DB에 있는데 ASC에 없는 것  27건
ASC에만 있고 DB에 없는 것   0건

마지막 줄이 결정적으로 보였습니다. 수집기가 놓친 건 0건이니 수집기는 무죄고, 27건이 사라진 것이죠. 전부 5★였고, 26건은 이미 답글을 보낸 것이었고, 날짜는 4월 말부터 6월 22일까지 전 구간에 퍼져 있었습니다.

62건 중 60건이 5★이고 전부 한국이라는 분포까지 보고 나서 "애플이 진위 검증으로 제거한 것이 가장 그럴듯하다, 계정 수준 리스크일 수 있으니 확인이 필요하다"고 보고했습니다.

당신이라면 여기서 무엇을 하시겠습니까? 애플에 문의를 넣겠습니까, 쿼리를 한 번 더 짜겠습니까, 아니면 그냥 브라우저를 열어보겠습니까?

실제 답 — 오너가 App Store Connect를 열어봤다

리뷰는 그대로 있었습니다.

제거가 아니라 API가 덜 돌려주는 것이었습니다. 지도 앱 하나로 가능한 조합을 전부 시도했습니다. UI에는 3건이 있습니다.

요청 meta.paging.total
customerReviews 기본 0
sort=-createdDate 0
filter[territory]=KOR 0
include=response 0
filter[rating]=5 0
appStoreVersions/{최근 3개}/customerReviews 0 / 0 / 0

전부 0입니다. 그런데 같은 엔드포인트가 5월에는 그 3건을 가져왔습니다. 봇이 그때 저장한 행이 DB에 남아 있으니 확실합니다. 왜 지금은 안 주는지, API 쪽에서는 알 방법이 없습니다.

이 사고에서 남길 규칙

남은 실질 리스크는 리뷰가 없다는 게 아닙니다. 답글 봇이 같은 엔드포인트를 쓰므로 일부 앱의 리뷰를 못 봅니다. 자동 답글이 켜진 앱 7개에서 리뷰가 답글 없이 방치될 수 있습니다. 사용자 입장에서는 "리뷰를 썼는데 개발자가 무시했다"가 됩니다.

그래서 이렇게 정리했습니다.

  • 리뷰 건수·최신성의 ground truth는 ASC UI뿐입니다. API가 0을 돌려줘도 없는 게 아닙니다.
  • 평점(별점 수·평균)은 애초에 ASC API에 없습니다. customerReviews글이 달린 리뷰만 반환합니다 — 별점만 남긴 대다수가 빠집니다. 스토어에 실제로 표시되는 숫자는 인증 없는 공개 lookup에서 옵니다. 이 이야기는 따로 뺐습니다 → 미국 설치가 제일 많은데 미국 평점은 0이었다.
curl -s "https://itunes.apple.com/lookup?id=<appId>&country=kr" |
  python3 -c "import json,sys; x=json.load(sys.stdin)['results'][0]; \
print(x['averageUserRating'], x['userRatingCount'])"
  • Edge Function이 진짜 성공했는지 보려면 cron의 succeeded가 아니라 net._http_response를 봐야 하고, 그 창은 6시간입니다. 그보다 긴 기간을 보려면 응답을 따로 적재해야 합니다.

지금 바로 점검할 3가지

  1. 보존 기간이 짧은 시스템 테이블에 interval '14 days' 같은 창을 걸어둔 쿼리가 있습니까? select min(created), max(created)를 먼저 찍어보세요.
  2. 스케줄러의 "성공"이 호출 성공입니까, 응답 성공입니까? 둘이 다른 스케줄러를 쓰고 있지 않은지 확인하세요.
  3. 당신의 파이프라인에서 "0건"과 "모름"이 같은 칸에 들어가 있습니까? 두 상태가 구분되지 않으면 다음 사람이 반드시 오독합니다.

솔직한 부분

두 번 틀렸고, 두 번 다 자신 있게 보고했습니다. 특히 2차는 "제거"라는 무거운 결론이었는데 근거가 "API가 안 준다" 하나였습니다. API를 ground truth로 가정한 것 자체를 한 번도 의심하지 않았습니다.

반증은 제가 못 찾았습니다. 오너가 브라우저를 열어서 끝났습니다. UI를 한 번 보는 것이 API를 다섯 번 때리는 것보다 빨랐습니다.

원인도 여전히 모릅니다. 키의 역할 제한인지, 엔드포인트 동작 변경인지, 인덱싱 지연인지 구분할 방법이 API 쪽에 없습니다. 그리고 첫 결론도 무효가 됐습니다 — "6-22 이후 새 리뷰 0건"은 같은 못 믿을 엔드포인트에 기댄 문장이니까요. 새 리뷰가 있어도 API가 안 줄 수 있습니다. 지금 상태는 "리뷰가 없다"가 아니라 "모른다" 입니다.

당신의 대시보드에서 가장 오래 0을 유지하고 있는 지표 하나를 떠올려 보세요. 그 0을 소스의 UI에서 직접 확인해 보신 적이 있습니까?

유튜브

리뷰 테이블이 7주째 멈췄다 두 번 진단했고 두 번 틀렸다

관련 글