Amy 앱의 최악의 한 주 — Supabase 5시간 다운, Sonar 15분 다운, 그리고 프리미엄 뚫린 이야기

이 영상은 Chris가 자기 생산성 앱 Amy(Apple Notes 스타일 칼로리 트래커)출시 이후 최악의 한 주를 그대로 공개한 13분 짜리 실전 후기이다. 그는 이 채널의 원칙을 투명성이라고 정의한다. 수치가 오르는 모습만 보여주는 대신, 서비스 2번 다운 + 보안 사고 1건을 어떻게 겪고 어떻게 고쳤는지 그대로 공개한다. 이 리포트는 원본을 보지 않아도 그가 겪은 사고와 그 이후에 구축한 방어책을 그대로 자기 앱에 이식할 수 있도록 재조립한 것이다.

핵심 요지 미리보기
· 출시 3개월, 현재 MRR $1,700 · 지난 30일 매출 $2,200 · 유료 사용자 166명. 1주차 리텐션 10% → 12.9%.
· 사고 1: Supabase 5시간 다운. Amy는 오프라인 모드가 있었지만 인증 예외 케이스로 5시간 동안 흰 화면만 표시.
· 사고 2: Perplexity Sonar 15분 다운. AI 콜이 안 됨. OpenRouter fallback을 수동 구현으로 옮긴 이유가 있다.
· 사고 3: Supabase RLS의 함정. 유저가 자기 row 전체를 수정 가능 → 프리미엄 자가 부여 + rate limit 조작으로 API 청구 폭탄 위험.
· 상태 페이지를 다운된 인프라 위에 올리지 말라. 커스텀 메시지도 같은 Supabase에 저장하면 다운 시 안 보임.
· 보안 스택 4개: rate limit(계정+IP), kill switch, 서비스별 예산 상한선, 환경변수 백엔드 격리.

목차

  1. 1. 투명성이 채널의 원칙 — 왜 이 영상을 만들었나
  2. 2. 3개월 성적표 — MRR $1,700과 리텐션 12.9%
  3. 3. 사고 1: Supabase 5시간 다운 — 인증 예외의 함정
  4. 4. 5시간 흰 화면과 웹사이트 이메일로 몰려온 유저들
  5. 5. 다시는 흰 화면이 안 뜨도록 — 상단 상태 배지 설계
  6. 6. 상태 페이지의 재귀적 함정 — 다운된 곳에 저장하지 마라
  7. 7. 사고 2: Perplexity Sonar 15분 다운 — 왜 fallback을 수동으로 만들었나
  8. 8. 사고 3: Supabase RLS의 프리미엄 & rate limit 노출
  9. 9. 48시간 vibe coding의 후과 — 별도 테이블이 정답이었다
  10. 10. 보안 4단 스택: Rate Limit · Kill Switch · Budget Cap · Env Vars
  11. 11. Whisperflow와 음성 코딩 워크플로우
  12. 12. 다크모드로 숨쉬기 — Cecilia의 일러스트 재작업
  13. 핵심 요약 (Takeaways)

오프닝 — 최악의 한 주

1. 투명성이 채널의 원칙 — 왜 이 영상을 만들었나

Chris는 2주 전에 이미 Amy 업데이트 영상을 올렸다. 하지만 그 사이 출시 이후 최악의 한 주가 지나갔다. 그는 "수치가 오르고 모든 게 순조로운 모습만 보여주는 것보다, 힘든 한 주가 어떤 모습인지 그리고 그 문제를 어떻게 해결하는지 보여주는 것이 더 중요하다"고 판단했다. 이 태도가 이 영상 전체의 축이다.

2. 3개월 성적표 — MRR $1,700과 리텐션 12.9%

먼저 좋은 소식. Amy 출시 3개월 시점의 지표:

Amy의 3개월 지표
  • 월간 반복 수익(MRR): 약 $1,700
  • 지난 30일 매출: 약 $2,200
  • 지난 2주 동안 MRR +$200 증가
  • 1주차 리텐션: 10% → 12.9% (100명 가입 → 13명이 1주 후에도 사용)
  • 유료 사용자: 166명

Chris가 진짜 신경 쓰는 지표는 매출이 아니라 1주차 리텐션이다. 2주 만에 3%p 증가는 통계적으로 의미 있게 다루기엔 짧지만, 방향이 위인 것만으로 그는 만족한다.

3개월 지표

3. 사고 1: Supabase 5시간 다운 — 인증 예외의 함정

이번 주 첫 번째 사고는 백엔드 제공업체 Supabase의 5시간 다운이다. Chris는 안심하고 있었다. Amy에는 이미 오프라인 모드가 있었기 때문이다. Subway 같은 곳에서 인터넷이 끊기면 오프라인 표시가 뜨고, 주문한 항목은 큐에 쌓였다가 재접속 시 처리된다. 그런데:

Chris가 미처 몰랐던 것
Supabase가 완전히 오프라인 상태가 되면 인증(auth)이 제대로 작동하지 않는 예외 케이스가 있다.
결과: 앱이 5시간 동안 빈 흰색 화면만 표시.

4. 5시간 흰 화면과 웹사이트 이메일로 몰려온 유저들

흰 화면 자체도 끔찍하지만 더 심각한 문제는 고객 지원 버튼에도 접근할 수 없다는 것이었다. 원래 유저는 앱 설정에서 지원 버튼을 눌러 연락하는데, 앱 자체가 안 뜨니 불가능.

결국 유저들은 웹사이트로 가서 지원 이메일을 찾아 이메일·인스타그램·트위터로 연락을 보냈다. Chris는 유저 이메일로 자기 앱이 다운됐다는 사실을 처음 알았다. 다행히 이 앱의 유저 상당수가 개발자·기술 전문가라서 Supabase 다운이라고 설명하니 다들 이해해 주었다. "아, 그렇군요, 알려주셔서 감사합니다."

유저 대응과 이메일

5. 다시는 흰 화면이 안 뜨도록 — 상단 상태 배지 설계

Chris가 사고 이후 즉시 만든 방어책:

상단 상태 배지 (Amy에 추가된 것)
  1. 흰 화면 완전 제거. Supabase가 다운돼도 앱은 뜬다.
  2. 상단에 작은 상태 표시. 클릭하면 상세 정보가 열린다.
  3. Chris가 원격으로 커스텀 메시지를 넣을 수 있는 섹션. "저희 DB 제공업체가 다운됐어요, 몇 시간 후 확인해 주세요."
  4. 아래쪽에는 서비스별 이용 가능·불가능을 보여주는 창.
  5. 30초마다 상태 재확인. 유저가 앱을 껐다 켰다 하지 않아도 자동 복구.

Chris의 통찰: "내가 다른 앱들에서 오프라인 상태일 때 있었으면 하는 기능이었다." 사고 뒤 자기 백엔드 엔드포인트가 다운될 경우를 대비한 두 번째 상태 배지도 추가했다. 시뮬레이션 토글도 설정에 넣어 실기기에서 테스트할 수 있게 했다.

상단 상태 배지

6. 상태 페이지의 재귀적 함정 — 다운된 곳에 저장하지 마라

Chris가 배지를 구현한 지 하루 만에 문득 든 생각이 이 영상에서 가장 중요한 교훈 중 하나다.

재귀적 함정
"Supabase에서 커스텀 메시지를 가져오고 있는데, Supabase가 오프라인이면 그 메시지는 어떻게 되지?"
큰 회사들의 상태 페이지가 다운된 인프라 위에 올려져 있어서 함께 죽는 걸 여러 번 목격했다.
저는 그럴 때마다 어리석다고 생각했는데, 정확히 같은 실수를 저지르고 있었다.

교훈: 상태 페이지·커스텀 메시지는 지금 모니터링하는 인프라 밖에 저장하라. 이걸 놓치면 사고가 났을 때 사고 알림 자체가 실패한다.

7. 사고 2: Perplexity Sonar 15분 다운 — 왜 fallback을 수동으로 만들었나

이틀 후 두 번째 사고. Amy가 칼로리 계산에 쓰는 Perplexity Sonar가 15분 다운. Perplexity 팀이 15분 만에 복구했지만, 그 15분 동안 이메일이 폭주했다.

Chris는 OpenRouter를 AI 인프라 프로바이더로 쓴다. OpenRouter에는 fallback model 기능이 있다. 첫 번째 모델이 실패하면 자동으로 두 번째로 넘어간다. 그런데 Chris는 이 기능을 일부러 안 쓴다. 이유가 결정적이다.

OpenRouter 자동 fallback을 안 쓰는 이유
자동 fallback으로 넘어간 두 번째 모델이 JSON 지침을 제대로 따르지 않는 경우가 있다.
그래서 Chris는 fallback을 수동으로 구현했다.
Sonar 실패 시 → Gemini 2.5 Flash Lite + Exa로 넘어간다.
가격은 Sonar보다 약간 비싸지만 결과는 거의 동등.
유저는 fallback 발동을 전혀 눈치채지 못한다.

Chris의 회고: "사실 이 두 번의 사고가 지금 일어난 게 다행이다. 유료 사용자 166명일 때 겪었으니. 1,000명이었으면 훨씬 스트레스였을 것."

Perplexity Sonar 다운과 fallback

8. 사고 3: Supabase RLS의 프리미엄 & rate limit 노출

세 번째는 보안 사고였다. 다행히 데이터 유출은 없었지만, 누군가 취약점을 발견하고 제보했다. Chris는 이 이야기를 공개하는 이유를 명시한다. "제 실수에서 배우실 수 있게."

기본 세팅은 표준적이었다. Supabase의 Row Level Security (RLS) — 유저가 자기 데이터만 접근할 수 있게. 그리고 스팸 요청을 막기 위한 rate limit — Amy는 유저가 음식을 입력할 때마다 Perplexity Sonar를 호출하는데 1건당 약 $0.005가 든다. 1,000건 스팸이면 상당한 청구서.

Chris의 실수는 이 두 값을 users 테이블에 저장한 것이었다.

실수의 구조
  • users 테이블에 구독 상태 + rate limit을 저장.
  • RLS 정책: 유저가 자기 row 전체를 수정 가능.
  • 이름·선호도 수정은 문제없음.
  • 그러나 구독 상태를 프리미엄으로 바꾸는 것도 가능.
  • 더 심각한 것: rate limit을 100만 건으로 올려서 Chris의 엔드포인트에 스팸 공격 가능 → API 청구서 폭탄.

Chris의 반응이 흥미롭다. 프리미엄 자가 부여에는 크게 신경 안 썼다. "15살 때 나도 폰을 탈옥해서 프리미엄 앱을 다 썼다. 스스로 알아냈으면 자격 있는 거다." 한 유저가 프리미엄을 뚫었다고 알려왔을 때도 "걱정 마세요, 계속 가지세요"라고 답했다.

그러나 rate limit 수정 가능성은 완전히 다른 문제였다. 이건 그의 계좌에서 돈이 나가는 문제다.

RLS와 프리미엄 뚫기

9. 48시간 vibe coding의 후과 — 별도 테이블이 정답이었다

Chris의 해법:

  1. 임시 해법: 특정 컬럼은 읽기만 허용. 쓰기는 백엔드 같은 신뢰 소스에서만.
  2. 더 나은 해법: 별도 테이블에 구독 상태·rate limit을 분리 저장.

그의 회고: "솔직히 너무 서둘렀던 탓이다. 이 앱 전체를 48시간에 만들었다. 이건 엄청난 실수였지만 덮고 싶지 않았다. 저도 이런 실수를 한다는 걸 보여주고 싶다."

별도 테이블 해법

10. 보안 4단 스택: Rate Limit · Kill Switch · Budget Cap · Env Vars

Chris가 지금 Amy에 걸어둔 보안 스택 정리. 이 부분이 이 영상의 실용 정수다.

Chris의 4단 보안 스택
  1. Rate Limit (2중)
    • 계정별 사용량 제한 (AI 콜마다 체크, 초과 시 차단 + 지원팀 안내).
    • IP 기반 제한 — 계정을 여러 개 만들어도 같은 IP면 안 됨.
    • 대시보드에서 유저별 제한을 수정·확인 가능.
  2. Kill Switch
    • 특정 유저의 AI 접근을 완전히 차단하는 플래그.
    • 한도 근접·의심 행동 시 사용.
  3. Budget Cap (서비스별 예산 상한)
    • OpenRouter에 hard budget cap 설정.
    • Amy는 월 $300~400 사용, 상한은 $1,000.
    • 상한 초과 시 모든 AI 서비스가 자동 오프. 이메일 알림.
    • OpenAI·OpenRouter·Anthropic 모두 이 기능이 있다. 기본이 아니니 반드시 켜라.
  4. Environment Variables 백엔드 격리
    • 민감 env vars를 코드베이스나 프런트엔드에 저장하지 마라. vibe-coded 앱에서 흔한 실수.
    • Claude/Cursor에게 "보안 검사 좀 해줘"라고 물어라. 대개 잡아준다.
    • 절대 하지 말 것: 프런트엔드에서 AI 프로바이더를 직접 호출. 리버스 엔지니어링으로 키가 뽑힌다.
    • 정답 구조: Frontend → Backend → AI Provider.

보안 4단 스택

예산 상한선

환경변수 백엔드 격리

11. Whisperflow와 음성 코딩 워크플로우

Chris가 사용하는 워크플로우: Claude/Cursor에 음성으로 모든 지시. 이렇게 하면 타이핑보다 훨씬 자세한 프롬프트를 낼 수 있다.

도구: Whisperflow. Chris가 특별히 선택한 이유는 개발자 용어 인식이 뛰어나기 때문이다. "supabase useEffect mcp" 같은 조합을 정확히 잡아낸다. Claude Code·Cursor·Windsurf 통합도 우수 — 파일과 변수를 말로 태그할 수 있다. "openrouter.js에 있는 구현부 좀 봐줘"라고 말하면 파일 태그가 자동으로 붙는다.

iOS + Android 모두 지원(안드로이드는 이번 영상 촬영일에 막 출시). Slack·iMessage·WhatsApp에서도 쓰고, Chris는 YouTube 댓글 답변에도 항상 쓴다.

12. 다크모드로 숨쉬기 — Cecilia의 일러스트 재작업

힘든 한 주 끝에 Chris는 재미있는 걸 하기로 했다. Amy에 다크모드를 구현. 색상 조정은 빨랐지만, 일러스트 재작업이 시간을 잡아먹었다. 이 일을 다 해준 약혼자 Cecilia에게 공개 감사.

다크모드는 그에게 보안 작업들 사이의 좋은 숨돌리기였다.

다크모드

클로징: 이번 주에 3건의 주요 사고가 있었지만, 백업 시스템·장애 알림 메시지·강화된 보안 등 처음부터 있었어야 할 것들이 이제 다 갖춰졌다. 수치는 상승 추세. Chris는 지금 이 앱의 상태에 매우 만족한다.

클로징

핵심 요약 (Takeaways)

이 후기에서 챙겨야 할 실질 인사이트
  1. Supabase의 완전 다운은 인증까지 죽인다. 오프라인 모드가 있어도 인증 실패 예외 경로를 실제로 시뮬레이션해서 흰 화면이 뜨지 않는지 확인해라. 상단 상태 배지 + 30초 자동 재시도가 표준.
  2. 상태 페이지·커스텀 메시지를 다운된 인프라 위에 올리지 마라. 같은 Supabase에 저장한 커스텀 메시지는 Supabase 다운 시 함께 죽는다. 재귀적 함정이다.
  3. OpenRouter 자동 fallback은 JSON 응답 앱에서는 조심하라. 두 번째 모델이 JSON 지침을 안 따르는 경우가 있다. 수동 fallback이 안전. Sonar 실패 → Gemini 2.5 Flash Lite + Exa 조합이 Chris의 실전 스택.
  4. Supabase RLS에서 users 테이블에 구독 상태·rate limit을 저장하지 마라. 유저가 자기 row 수정 가능이면 rate limit을 100만으로 올려 API 청구서 폭탄을 유발할 수 있다. 별도 테이블 + 백엔드에서만 쓰기 허용이 정답.
  5. 보안 4단 스택: Rate Limit(계정+IP) · Kill Switch · Budget Cap · Env Vars 백엔드 격리. 이 4개는 vibe coding으로도 반드시 갖춰야 한다.
  6. OpenRouter/OpenAI/Anthropic의 hard budget cap은 기본이 아니다. 반드시 켜라. 월 사용액의 2~3배로 상한을 잡고, 초과 시 자동 오프 + 이메일 알림. 이게 최후의 안전장치다.
  7. 프리미엄 자가 부여는 참을 수 있어도 rate limit 조작은 참을 수 없다. Chris가 15살 자기 자신을 회상하며 프리미엄 뚫기에는 관대했던 반면, rate limit 위반에는 즉시 대응한 이유는 명확하다. 돈이 나가는 위험만이 진짜 위험이다.

참조

  • 원본 영상: How My App Is Doing (Not Good) — Chris (Amy calorie tracker)
  • 대상 앱: Amy — Apple Notes 스타일 칼로리 트래커
  • 사용 스택: Supabase (백엔드·auth·DB), OpenRouter (AI 라우터), Perplexity Sonar (기본 모델), Gemini 2.5 Flash Lite + Exa (fallback), Claude Code · Cursor · Windsurf, Whisperflow (음성-텍스트)