대니
danny's blog@dannywon_dev
발행 89 · 대기 137

1인 개발자. 클로드 코드를 팀처럼 굴린다. 매일 겪은 것만 쓴다.

클로드 코드

Supabase RPC 에러가 「데이터 없음」으로 보였다

480건 쌓인 전시에서 통계가 0으로 보였다. 에러와 0건이 같은 null 이었다

통계 화면에 「아직 쌓인 기록이 없어요」라고 떠 있으면, 보통은 정말 기록이 없는 것이다. 그런데 480건이 쌓인 전시에서도 같은 문장이 떴다.

2026-08-12. 갤러리 통계에서 직원 방문을 빼는 마이그레이션을 운영 DB 에 적용했다. 집계 함수 하나(get_fair_analytics)를 통째로 갈아끼우는 작업이었다.

적용 직후 화면을 확인했다 — 「아직 쌓인 기록이 없어요」. 시험용으로 쓰던 갤러리라 스캔 24건이 전부 내 브라우저 것이었고, 새 규칙이 직원을 거르니 0이 나오는 게 맞았다. 그렇게 판단하고 넘어갔다.

30분 뒤 다른 갤러리 계정으로 통계를 열었다. 똑같이 「아직 쌓인 기록이 없어요」. 그쪽 전시에는 480건이 있었다.

Postgres 로그를 뒤지니 에러가 반복되고 있었다.

COALESCE could not convert type uuid[] to text[]

visitor_id 를 uuid 라고 알고 배열 변수를 uuid[] 로 선언했는데, 네 테이블 모두 text 였다. 브라우저 쿠키에 uuid 를 만들어 넣지만 칸 타입은 text 다. 함수는 호출될 때마다 죽고 있었다.

30분 동안 고장을 보고 정상이라고 판단했다

화면이 「기록 없음」이라고 말했고, 그 순간 내 머릿속 설명("직원이 다 걸러져서 0이구나")과 맞아떨어졌다. 그래서 확인을 멈췄다.

원인은 조회 코드 한 줄이었다.

const { data, error } = await supabase.rpc("get_fair_analytics", { p_fair_id })
if (error || !data) return null      // ← 에러와 「0건」이 같은 null 이 된다

화면은 null 을 받으면 「아직 쌓인 기록이 없어요」를 띄운다. 에러가 삼켜져서 「고장」과 「데이터 없음」이 화면에서 구분되지 않았다.

여기서 갈린 건 "에러 처리를 어디까지 해야 하나" 가 아니었다. 조용히 실패하는 것과 시끄럽게 실패하는 것 중 무엇이 더 위험한가였다.

조용한 실패가 더 위험하다. 갤러리가 이 화면을 봤다면 "우리 전시에 아무도 안 왔구나" 라고 읽고 우리에게 아무 말도 안 했을 것이다. 시끄럽게 실패했으면 그날 바로 연락이 왔다.

뭘 바꿨나

하나 — 반환값을 갈랐다. null 하나로 두 상태를 표현하지 않는다.

export type AnalyticsLoad =
  | { status: "ok"; data: FairAnalytics }
  | { status: "failed" }

둘 — 실패를 우리 쪽 감시 기록에 남긴다. 갤러리가 알려주기를 기다리면 며칠이 지나고, 그 사이 숫자는 계속 틀리다. 심각도는 high 로 잡았다 — 화면이 「기록 없음」으로 보여서 아무도 신고하지 않기 때문이다.

셋 — 화면이 다르게 말한다. 「통계를 불러오지 못했어요 / 잠시 뒤 새로고침해 주세요. 계속 이러면 알려 주세요 — 저희 쪽에도 기록이 남았어요」.

넷 — 실제로 함수를 깨뜨려서 확인했다. 일부러 raise exception 을 넣은 함수로 바꾸고 화면과 감시 기록 양쪽을 본 뒤 되돌렸다. 이걸 안 했으면 "고쳤겠지" 로 끝났을 것이다.

같은 날, 같은 병의 다른 얼굴 둘

마이그레이션에서 두 번 더 걸렸다. 둘 다 실패했는데 성공처럼 보이는 구조였다.

  • drop constraint if exists <틀린 이름> — 제약 이름을 잘못 알고 있었는데 if exists 가 조용히 건너뛰었다. 그 뒤 문장은 옛 제약이 살아 있는 채로 돌아서 새 가입이 전부 막혔다
  • 되돌리기 SQL 을 여러 문장 한 번에 보냈다가 하나가 실패해 통째로 롤백됐는데, 그 상태에서 마이그레이션이 if not exists 로 통과해 「빈 상태에서 검증했다」고 착각했다

셋의 공통점은 하나다. 에러가 안 났다. 에러가 났으면 30초에 끝났을 것들이다.

← 목록으로