Supabase 무료 티어로 SaaS 만들기 — 한계와 우회법
Supabase 무료 플랜으로 실제 SaaS를 구축하면서 겪은 한계와 이를 우회하는 실전 전략을 공유합니다. 데이터베이스 크기, API 요청, Auth, Storage 제한을 극복하는 방법.
Supabase를 선택한 이유

2025년 중반, 간단한 SaaS 프로젝트를 시작하면서 백엔드를 고민했습니다. 선택지는 크게 세 가지였습니다:
- 직접 서버 구축 (Node.js + PostgreSQL)
- Firebase
- Supabase
Firebase는 NoSQL 기반이라 관계형 데이터가 많은 제 프로젝트와 맞지 않았습니다. 직접 서버를 구축하면 인프라 관리에 시간을 뺏기게 되고. 결국 PostgreSQL 기반 + 무료 티어가 넉넉한 Supabase를 선택했습니다.
결론부터 말하면, MVP 단계에서 Supabase 무료 티어는 충분했습니다. 하지만 사용자가 늘면서 몇 가지 벽에 부딪혔고, 이를 우회하는 방법을 찾아야 했습니다.
무료 티어의 실제 한계
Supabase 공식 Pricing에 나오는 무료 플랜 제한:
| 항목 | 무료 한도 |
|---|---|
| 데이터베이스 크기 | 500MB |
| Storage | 1GB |
| 대역폭 | 월 5GB |
| Edge Functions | 월 50만 호출 |
| Auth MAU | 50,000명 |
| 프로젝트 수 | 2개 |
| 일시 정지 | 7일 미접속 시 자동 정지 |
마지막 항목이 진짜 문제입니다. 7일 동안 API 호출이 없으면 프로젝트가 자동으로 일시 정지됩니다. SaaS를 운영하는데 백엔드가 갑자기 꺼지면 어떻게 될까요? 사용자 입장에서는 서비스 장애입니다.
한계 #1: 자동 정지 우회
가장 간단한 우회법은 크론 잡으로 주기적 핑을 보내는 겁니다:
// Vercel Cron 또는 GitHub Actions 활용
// cron: 0 */6 * * * (6시간마다)
export async function GET() {
const { data, error } = await supabase
.from('health_check')
.select('id')
.limit(1);
return Response.json({ status: 'alive', timestamp: new Date() });
}
GitHub Actions를 쓰면 완전 무료로 이 크론을 돌릴 수 있습니다. 저는 6시간마다 한 번씩 health check 쿼리를 날립니다.
한계 #2: 500MB 데이터베이스
500MB가 작아 보이지만, 실제로 써보면 텍스트 기반 SaaS에서는 꽤 넉넉합니다. 제 프로젝트의 경우 사용자 5,000명 데이터가 약 120MB였습니다.
하지만 이미지나 파일을 DB에 직접 저장하면 순식간에 차버립니다. 우회법:
이미지는 Storage로, 메타데이터만 DB에
-- ❌ 이렇게 하지 마세요
CREATE TABLE posts (
id uuid PRIMARY KEY,
image bytea -- DB에 이미지 직접 저장
);
-- ✅ 이렇게 하세요
CREATE TABLE posts (
id uuid PRIMARY KEY,
image_url text -- Storage URL만 저장
);
오래된 데이터 아카이빙
활성 데이터만 Supabase에 두고, 6개월 이상 된 데이터는 주기적으로 내보내서 Cloudflare R2 (무료 10GB)에 보관합니다.
// 월 1회 아카이빙 스크립트
const oldData = await supabase
.from('logs')
.select('*')
.lt('created_at', sixMonthsAgo);
// R2에 JSON으로 업로드
await r2.put(`archive/${year}/${month}.json`, JSON.stringify(oldData));
// Supabase에서 삭제
await supabase
.from('logs')
.delete()
.lt('created_at', sixMonthsAgo);
한계 #3: 프로젝트 2개 제한
무료 플랜에서 프로젝트는 2개까지입니다. 개발(dev)과 프로덕션(prod)으로 나누면 딱 2개라 여유가 없습니다.
제 우회법: 하나의 프로젝트에서 스키마로 환경 분리
-- 개발 환경
CREATE SCHEMA dev;
CREATE TABLE dev.users (...);
-- 프로덕션 환경
CREATE SCHEMA prod;
CREATE TABLE prod.users (...);
RLS(Row Level Security) 정책도 스키마별로 다르게 적용할 수 있어서, 하나의 프로젝트로도 환경 분리가 가능합니다.
한계 #4: Edge Functions 제한
월 50만 호출이 많아 보이지만, 실시간 기능이 있으면 금방 소진됩니다. 우회법:
- 클라이언트 사이드 로직 최대한 활용: 단순 계산은 브라우저에서
- Batch 처리: API 호출을 묶어서 한 번에
- 캐싱: 같은 요청은 클라이언트에서 캐시
// ❌ 매번 호출
const user = await supabase.auth.getUser();
// ✅ 캐시 활용
const getCachedUser = (() => {
let cache: User | null = null;
let lastFetch = 0;
return async () => {
if (cache && Date.now() - lastFetch < 300000) return cache;
const { data } = await supabase.auth.getUser();
cache = data.user;
lastFetch = Date.now();
return cache;
};
})();
RLS 설정: 무료 티어의 숨은 강점
Supabase의 Row Level Security는 무료 티어에서도 완전히 사용 가능합니다. 이게 Firebase보다 Supabase를 선택한 결정적 이유입니다.
-- 사용자 본인 데이터만 접근 가능
ALTER TABLE user_data ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own data"
ON user_data FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own data"
ON user_data FOR INSERT
WITH CHECK (auth.uid() = user_id);
서버 사이드 보안 로직을 DB 레벨에서 처리하니, Edge Function 호출을 줄이는 효과도 있습니다.
실전 아키텍처
제가 운영 중인 SaaS의 아키텍처입니다:
[Next.js on Vercel] ← 프론트엔드
↓
[Supabase Free Tier]
├── PostgreSQL (데이터)
├── Auth (인증)
├── Storage (파일)
└── Realtime (웹소켓)
↓
[Cloudflare R2] ← 아카이빙
[GitHub Actions] ← 크론/자동화
이 구조로 월 운영비 0원에 사용자 5,000명을 감당하고 있습니다.
유료 전환 타이밍
무료 티어로 버티다가 언제 유료로 갈아타야 할까요? 제 기준:
- DB 사용량이 400MB 넘으면 (80% 도달)
- MAU가 30,000 넘으면
- 수익이 발생하기 시작하면 (Pro 플랜 월 $25는 충분히 감당 가능)
마무리
Supabase 무료 티어는 "무료치고 괜찮은" 수준이 아니라, MVP부터 초기 성장까지 진지하게 쓸 수 있는 레벨입니다. 자동 정지와 용량 제한만 잘 우회하면, 유료 플랜 없이도 꽤 오래 버틸 수 있습니다.
관련 프로젝트들도 참고하세요:
- SysoftI — IT 인프라 솔루션
- SBM Lab — 바이오 데이터 분석 플랫폼
- GenoBalance — 유전체 분석 서비스
- KBrain Map — 한국 뇌지도 프로젝트
참고 링크: