WICHI 백엔드를 Railway와 Supabase 조합으로 운영하며 겪은 DB 커넥션 풀링, 마이그레이션 충돌 등 실전 장애 사례와 인프라 전환 판단 기준 기록
왜 이 스택인가
WICHI 백엔드는 FastAPI + PostgreSQL 조합입니다. 이 스택을 올릴 인프라를 고를 때 기준은 세 가지였습니다.
- 배포가 단순할 것 — GitHub push만으로 배포가 끝나야 합니다. CI/CD 파이프라인을 별도로 구성하는 데 시간을 쓰고 싶지 않았습니다.
- DB + 인증을 한 곳에서 해결할 것 — PostgreSQL, 사용자 인증, Row Level Security(RLS)를 별도 서비스 조합 없이 하나의 플랫폼에서 쓸 수 있어야 했습니다.
- 초기 비용이 최소일 것 — 아직 수익이 없는 단계에서 고정 비용을 최소화해야 했습니다. 무료 티어 또는 사용량 기반 과금이 필수 조건이었습니다.
Railway는 첫 번째와 세 번째 기준을, Supabase는 두 번째와 세 번째 기준을 충족했습니다. 두 서비스를 조합하면 FastAPI 앱 배포부터 PostgreSQL 운영, 인증, RLS까지 외부 서비스 하나 없이 해결됩니다.
이 글에서는 실제 운영 경험을 기반으로 두 서비스를 각각 상세히 다루고, 조합 운영 시 알아야 할 점, 그리고 이 스택을 벗어나야 하는 시점의 판단 기준을 정리합니다.
전체 아키텍처 개요
graph TB
subgraph Client
A[Web App - Vercel]
end
subgraph Railway
B[FastAPI Server]
C[Health Check Endpoint]
end
subgraph Supabase
D[PostgreSQL DB]
E[Connection Pooler - PgBouncer]
F[Auth Service]
G[REST API - PostgREST]
H[Realtime]
I[Storage]
end
subgraph External
J[GitHub Repository]
end
A -->|HTTPS| B
B -->|port 6543 - Transaction Mode| E
E --> D
A -->|Direct| F
J -->|push trigger| B
C -->|DB connection check| E
style E fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
위 다이어그램에서 핵심은 FastAPI 서버가 Supabase DB에 직접 연결하지 않고, Connection Pooler(PgBouncer)를 경유한다는 점입니다. 이 구조가 왜 중요한지는 Supabase 섹션에서 상세히 다룹니다.
Railway 상세
핵심 기능: GitHub 자동 배포
Railway의 가장 큰 장점은 GitHub 연동 자동 배포입니다. 레포지토리를 연결하면 main 브랜치에 push할 때마다 자동으로 빌드와 배포가 실행됩니다. 별도의 GitHub Actions 워크플로우를 작성할 필요가 없습니다.
sequenceDiagram
participant Dev as Developer
participant GH as GitHub
participant RW as Railway
participant Svc as Service
Dev->>GH: git push origin main
GH->>RW: Webhook trigger
RW->>RW: Detect Dockerfile / Nixpacks
RW->>RW: Build image
RW->>Svc: Deploy new version
RW->>RW: Health check pass
RW-->>Svc: Route traffic to new version
Note over RW,Svc: Zero-downtime rolling deploy
배포 과정은 다음과 같이 진행됩니다.
- GitHub에 push하면 Railway가 webhook으로 감지
- Dockerfile이 있으면 Docker 빌드, 없으면 Nixpacks로 자동 빌드 환경 구성
- 이미지 빌드 완료 후 새 컨테이너에 배포
- Health check 통과 시 트래픽을 새 버전으로 전환
일반적인 FastAPI 앱 기준으로 빌드 시간은 의존성 설치 포함 1–3분, 배포 전환은 수십 초 내에 완료됩니다. Dockerfile을 직접 작성하면 레이어 캐싱 덕분에 의존성이 변경되지 않은 경우 빌드 시간이 대폭 줄어듭니다.
Preview Deploy
PR을 열면 해당 브랜치에 대한 Preview Deploy가 자동 생성됩니다. 독립된 URL이 부여되므로 PR 리뷰 시 실제 동작을 확인할 수 있습니다. 이 기능은 Vercel의 Preview Deploy와 유사하지만, 백엔드 서비스에 적용된다는 점에서 유용합니다.
단, Preview Deploy에서 사용하는 DB 연결 문자열을 어떻게 관리할지 사전에 결정해야 합니다. Production DB를 그대로 사용하면 위험하고, 별도의 staging DB를 연결하려면 환경변수 분리가 필요합니다.
사용량 기반 과금 구조
Railway의 과금은 세 가지 축으로 계산됩니다.
| 과금 항목 | 단위 | 비고 |
|---|---|---|
| vCPU | 시간당 | 실제 사용한 CPU 시간 기준 |
| Memory | GB-시간 | 할당이 아닌 사용량 기준 |
| Network Egress | GB | 아웃바운드 트래픽만 과금 |
| Disk | GB-시간 | 영구 스토리지 사용 시 |
이 구조의 장점은 트래픽이 적을 때 비용이 낮다는 것이고, 단점은 월말 청구서를 열기 전까지 정확한 비용을 알기 어렵다는 것입니다. 대시보드에서 실시간 Estimated Cost를 확인할 수 있지만, 이것은 현재까지의 누적이지 월말 예측치가 아닙니다.
Railway의 과금 모델은 “쓴 만큼 내는” 구조입니다. 트래픽이 없으면 비용이 거의 발생하지 않지만, 트래픽 스파이크가 오면 예상치 못한 비용이 발생할 수 있습니다. Spending Alert 설정은 선택이 아닌 필수입니다.
DX (Developer Experience)
Railway의 DX는 경쟁 서비스 대비 상위권입니다. 세부 항목별로 정리합니다.
| DX 항목 | 평가 | 설명 |
|---|---|---|
| 초기 세팅 | 우수 | GitHub 연결 후 5분 내 첫 배포 가능 |
| 대시보드 | 우수 | 서비스 토폴로지를 시각적으로 표현, 직관적 |
| 로그 | 양호 | 실시간 스트리밍 지원, 검색과 필터링은 제한적 |
| 환경변수 관리 | 우수 | 서비스별 분리, 참조 변수($[object Object] 문법) 지원 |
| CLI | 양호 | railway run, railway logs 등 기본 명령 지원 |
| 문서 | 보통 | 핵심 내용은 있으나 edge case 문서가 부족 |
| 커뮤니티 | 양호 | Discord 활성화, 응답 속도 빠른 편 |
로그 관련 보충: Railway의 내장 로그 뷰어는 디버깅에 충분하지만, 구조화된 로그 검색이나 장기 보존에는 부족합니다. 프로덕션 단계에서는 외부 로그 서비스(Axiom, Better Stack 등)를 연동하는 것을 권장합니다. Railway는 log drain 기능을 지원하므로 연동 자체는 어렵지 않습니다.
제한 사항
Railway를 사용하면서 느낀 제한 사항을 정리합니다.
- 콜드 스타트: 트래픽이 없으면 인스턴스가 슬립 상태로 전환됩니다. 첫 요청 시 응답까지 수 초가 걸릴 수 있습니다. FastAPI 앱의 경우 Uvicorn 기동 시간이 포함되므로 체감 지연이 클 수 있습니다.
- 리전 제한: 리전 선택지가 AWS나 GCP 대비 적습니다. 아시아 리전은 있지만, 한국 사용자를 위한 서울 리전은 없습니다. 이 점은 응답 지연(latency)에 직접적으로 영향을 줍니다.
- 스케일링 제어: 수평 스케일링(여러 인스턴스 실행)이 가능하지만, AWS ECS나 Kubernetes 수준의 세밀한 오토스케일링 정책은 설정할 수 없습니다. 트래픽 패턴이 불규칙한 서비스에서는 이 점이 아쉽습니다.
- Cron Job 한계: Railway에서 cron service를 별도로 띄울 수 있지만, 기본 제공되는 스케줄러는 없습니다. APScheduler 같은 라이브러리를 직접 앱에 내장하거나, 별도의 서비스로 분리해야 합니다.
Supabase 상세
PostgreSQL as a Service
Supabase의 핵심은 관리형 PostgreSQL입니다. 인스턴스 프로비저닝, 백업, 업그레이드를 Supabase가 처리하므로 DB 운영에 직접 관여할 일이 거의 없습니다.
Supabase는 PostgreSQL 위에 여러 서비스를 올려놓은 구조입니다. 전부 사용할 필요는 없습니다. 실제로 WICHI에서 사용하는 기능과 사용하지 않는 기능을 구분하면 다음과 같습니다.
| 기능 | 사용 여부 | 용도 / 미사용 이유 |
|---|---|---|
| PostgreSQL DB | 사용 | 핵심 데이터 저장소 |
| Auth | 사용 | 이메일/OAuth 인증, JWT 발급 |
| Row Level Security (RLS) | 사용 | 사용자별 데이터 접근 제어 |
| Connection Pooler | 사용 | Railway→DB 커넥션 관리 |
| SQL Editor | 사용 | 일회성 쿼리, 데이터 확인 |
| REST API (PostgREST) | 미사용 | FastAPI에서 직접 쿼리 작성 |
| Realtime | 미사용 | 현재 실시간 기능 불필요 |
| Storage | 미사용 | 파일 업로드 기능 없음 |
| Edge Functions | 미사용 | 서버 로직은 Railway에서 처리 |
사용하는 기능만 놓고 보면 Supabase는 “관리형 PostgreSQL + Auth + RLS”입니다. 이 조합만으로도 별도의 인증 서비스(Auth0, Firebase Auth)와 DB 서비스(RDS, Cloud SQL)를 각각 운영하는 것 대비 관리 포인트가 크게 줄어듭니다.
Auth 서비스
Supabase Auth는 GoTrue 기반의 인증 서비스입니다. 이메일/비밀번호 인증과 OAuth(Google, GitHub 등) 로그인을 기본 지원하며, JWT 토큰 발급과 갱신을 자동으로 처리합니다.
FastAPI 서버에서는 클라이언트가 전달한 JWT를 검증하고, 토큰에 포함된 사용자 ID를 기반으로 DB 쿼리를 수행합니다. Auth 서비스 자체를 직접 구현할 필요가 없다는 것이 가장 큰 장점입니다.
주의할 점이 있습니다. Supabase Auth의 JWT에는 기본적으로 role과 sub(사용자 ID)이 포함됩니다. RLS 정책에서 auth.uid()로 현재 사용자를 식별할 수 있지만, 이것은 Supabase 클라이언트 라이브러리를 통해 접근할 때만 자동으로 동작합니다. FastAPI에서 직접 PostgreSQL에 쿼리할 때는 RLS가 자동 적용되지 않으므로, 서버 측에서 별도의 접근 제어 로직을 작성해야 합니다.
RLS는 “설정했으니 안전하다”가 아닙니다. 어떤 경로로 DB에 접근하는지에 따라 RLS가 적용되는 범위가 달라집니다. 서버에서
service_role키로 접근하면 RLS를 우회하므로, 이 키의 관리가 곧 보안의 핵심입니다.
Connection Pooling 상세
이 부분은 Supabase 운영에서 가장 중요한 설정이므로 별도로 다룹니다.
graph LR
subgraph Railway
A1[FastAPI Instance 1]
A2[FastAPI Instance 2]
A3[FastAPI Instance 3]
end
subgraph Supabase Connection Pooler
B[PgBouncer<br/>port 6543<br/>Transaction Mode]
end
subgraph PostgreSQL
C[DB Instance<br/>Max Connections Limited]
end
A1 -->|conn 1| B
A1 -->|conn 2| B
A2 -->|conn 3| B
A2 -->|conn 4| B
A3 -->|conn 5| B
B -->|pooled conn A| C
B -->|pooled conn B| C
B -->|pooled conn C| C
style B fill:#f96,stroke:#333
PostgreSQL은 동시 커넥션 수에 물리적 제한이 있습니다. Supabase Free 티어의 경우 이 제한이 더 엄격합니다. FastAPI 서버가 요청마다 새로운 DB 커넥션을 생성하면, 동시 요청이 몰릴 때 커넥션 고갈이 발생합니다.
Connection Pooler(PgBouncer)는 이 문제를 해결합니다. 여러 클라이언트의 커넥션 요청을 받아서 소수의 실제 PostgreSQL 커넥션을 재사용합니다.
Supabase에서 제공하는 Pooler 모드는 두 가지입니다.
| 모드 | 포트 | 동작 방식 | 적합한 환경 |
|---|---|---|---|
| Transaction | 6543 | 트랜잭션 단위로 커넥션 할당/반환 | 서버리스, 단기 쿼리 위주 |
| Session | 5432 (Pooler) | 세션 종료까지 커넥션 유지 | 장기 커넥션이 필요한 경우 |
WICHI 백엔드는 Transaction 모드를 사용합니다. FastAPI의 각 요청은 독립적인 트랜잭션이므로, 쿼리 실행 후 즉시 커넥션을 반환하는 Transaction 모드가 적합합니다.
설정 시 주의사항:
DATABASE_URL환경변수에 반드시 Pooler 주소(port 6543)를 사용해야 합니다. Direct connection(port 5432)을 쓰면 커넥션 풀링 없이 직접 연결되므로 동시 접속 제한에 빠르게 도달합니다.- Transaction 모드에서는
PREPARE문과LISTEN/NOTIFY가 동작하지 않습니다. SQLAlchemy를 사용하는 경우prepared_statement_cache_size=0설정이 필요할 수 있습니다. - 마이그레이션 실행 시에는 Direct connection을 사용해야 합니다. DDL 명령(CREATE TABLE, ALTER TABLE 등)은 Transaction 모드 Pooler를 통해 실행하면 예기치 않은 동작이 발생할 수 있습니다.
무료 티어와 유료 플랜
Supabase의 가격 구조를 공개 정보 기준으로 정리합니다.
| 항목 | Free | Pro ($25/mo) |
|---|---|---|
| DB 크기 | 500 MB | 8 GB (확장 가능) |
| Auth MAU | 50,000 | 100,000 |
| Storage | 1 GB | 100 GB |
| Edge Function 호출 | 500K/month | 2M/month |
| 일일 백업 | 미제공 | 7일 보존 |
| Branching | 미제공 | 제공 |
| Pausing | 7일 미사용 시 자동 일시정지 | 없음 |
무료 티어의 가장 큰 제약은 7일 미사용 시 자동 일시정지입니다. 정지된 프로젝트는 대시보드에서 수동으로 복원해야 하며, 복원까지 수 분이 소요됩니다. 개인 프로젝트나 초기 단계에서는 문제없지만, 실 사용자가 있는 서비스에서는 Pro 플랜 전환이 필수입니다.
무료 티어의 500 MB DB 제한은 생각보다 빠르게 도달합니다. 특히 로그성 데이터나 히스토리 테이블을 DB에 저장하는 경우, 몇 주 만에 한계에 부딪힐 수 있습니다. 초기부터 어떤 데이터를 DB에 저장하고 어떤 데이터를 외부로 분리할지 설계해두는 것이 좋습니다.
마이그레이션 관리
Supabase는 CLI를 통한 마이그레이션 워크플로우를 지원합니다. supabase migration new, supabase db push 같은 명령으로 스키마 변경을 관리할 수 있습니다.
문제는 대시보드의 SQL Editor에서 직접 스키마를 변경하는 유혹입니다. “테이블 하나만 빠르게 추가하자”며 대시보드에서 직접 CREATE TABLE을 실행하면, 로컬의 마이그레이션 파일과 실제 DB 스키마 사이에 차이가 생깁니다. 이 차이는 누적될수록 해결이 어려워집니다.
권장 워크플로우:
- 스키마 변경은 항상 로컬에서 마이그레이션 파일로 작성
supabase db push로 원격 DB에 적용- 대시보드 SQL Editor는 데이터 조회 전용으로 사용
- 긴급한 데이터 수정(DELETE, UPDATE)은 대시보드에서 하되, 스키마 변경(DDL)은 절대 대시보드에서 하지 않음
운영 경험
배포 속도 및 안정성
Railway의 배포는 안정적입니다. GitHub push부터 새 버전이 트래픽을 받기까지 일반적으로 2–4분이 소요됩니다. Zero-downtime deploy를 지원하므로 배포 중 서비스 중단은 없습니다.
다만, 빌드 실패 시 이전 버전이 유지되므로 “배포가 실패한 줄 모르고 지나가는” 경우가 있었습니다. 배포 실패 알림을 별도로 설정하지 않으면, 대시보드를 직접 확인하기 전까지 인지하지 못합니다. Slack 또는 Discord webhook 연동은 초기에 설정해두는 것을 권장합니다.
업타임
Railway와 Supabase 모두 공식적으로 SLA를 제공하는 플랜이 있지만, 무료–Pro 구간에서의 체감 업타임은 99%대 중후반입니다. 대규모 서비스 장애는 경험하지 않았지만, 간헐적인 지연이나 단기 이상은 있었습니다.
인시던트 로그
운영 기간 중 경험한 주요 인시던트를 정리합니다.
| # | 유형 | 증상 | 원인 | 감지 방법 | 해결 시간 | 조치 |
|---|---|---|---|---|---|---|
| 1 | DB 커넥션 고갈 | API 500 에러 급증 | Direct connection 사용 중 동시 접속 초과 | 에러 로그 수동 확인 | ~30분 | Pooler 전환 (port 6543) |
| 2 | 콜드 스타트 지연 | 첫 요청 타임아웃 | 슬립 후 기동 시 대용량 모델 로드 | 사용자 제보 | ~10분 | startup 이벤트 경량화 |
| 3 | 마이그레이션 충돌 | 배포 실패 | 대시보드에서 직접 스키마 변경 후 CLI 마이그레이션과 충돌 | 배포 로그 확인 | ~1시간 | 마이그레이션 파일 수동 동기화 |
| 4 | 환경변수 누락 | 서비스 기동 실패 | Railway 환경변수 설정 시 오타 | 배포 로그 확인 | ~5분 | 환경변수 검증 스크립트 추가 |
| 5 | Supabase 일시정지 | DB 연결 불가 | Free 티어 7일 미사용 자동 정지 | 헬스체크 실패 알림 | ~5분 (수동 복원) | Pro 플랜 전환 |
가장 영향이 컸던 것은 1번(커넥션 고갈)입니다. Pooler를 사용하지 않고 Direct connection으로 운영하다가, 사용자가 늘어나면서 동시 커넥션 제한에 도달했습니다. API가 간헐적으로 500을 반환했고, 원인을 파악하기까지 시간이 걸렸습니다.
인시던트 대부분은 “알고 있었지만 나중에 하려던” 설정을 미루다 발생했습니다. Connection Pooler 설정, 환경변수 검증, 알림 설정 같은 것들은 서비스 첫 배포 시 함께 완료하는 것이 맞습니다.
로그와 모니터링
Railway의 로그 시스템은 디버깅에는 충분하지만, 운영 수준의 모니터링에는 부족합니다.
| 항목 | Railway 기본 제공 | 추가 도구 필요 |
|---|---|---|
| 실시간 로그 스트리밍 | 제공 | - |
| 로그 검색/필터 | 기본 수준 | 구조화된 로그 검색 필요 시 |
| 로그 보존 기간 | 제한적 | 장기 보존 필요 시 |
| CPU/Memory 메트릭 | 대시보드 제공 | 알림 임계값 설정 |
| 커스텀 메트릭 | 미제공 | Prometheus, Datadog 등 |
| 에러 추적 | 미제공 | Sentry 등 |
| APM | 미제공 | 필요 시 별도 도구 |
Supabase 쪽도 마찬가지입니다. 대시보드에서 DB 크기, 커넥션 수, API 요청 수 등 기본 메트릭을 확인할 수 있지만, 쿼리 성능 분석이나 슬로우 쿼리 추적은 별도 설정이 필요합니다.
현실적인 접근은 이렇습니다. 초기에는 Railway와 Supabase 기본 대시보드만으로 운영하고, 사용자가 늘어나면서 Sentry(에러 추적)와 log drain(로그 장기 보존)을 순차적으로 추가합니다. 처음부터 풀 모니터링 스택을 구성하는 것은 과잉입니다.
성능 벤치마크
실제 운영 환경에서 측정한 대략적인 수치입니다. 정확한 수치는 서비스 특성에 따라 달라지므로 범위로 표기합니다.
| 항목 | 범위 | 조건 |
|---|---|---|
| API 응답 시간 (warm) | 50–200 ms | DB 쿼리 포함, Pooler 경유 |
| API 응답 시간 (cold start) | 3–8 sec | 슬립 후 첫 요청 |
| 배포 소요 시간 | 2–4 min | Dockerfile 빌드, 의존성 캐시 히트 시 |
| DB 쿼리 (단순 SELECT) | 5–20 ms | 인덱스 있는 테이블 기준 |
| DB 쿼리 (JOIN 포함) | 20–100 ms | 테이블 크기, 인덱스 설계에 따라 편차 |
| Pooler 오버헤드 | ~5 ms | Direct connection 대비 추가 지연 |
Cold start가 3–8초인 것은 프로덕션 서비스에서는 무시할 수 없는 수치입니다. 다만 이것은 트래픽이 일정 시간 없을 때만 발생하므로, 사용자가 활발한 시간대에는 문제가 되지 않습니다. 야간이나 새벽 시간대에 간헐적으로 접속하는 사용자가 체감할 수 있습니다.
비용 구조
구체적인 금액은 공개하지 않지만, 구조적인 특성을 정리합니다.
Railway 비용 특성
Railway는 사용량 비례 과금입니다. 트래픽이 적은 달에는 비용이 낮고, 트래픽이 많은 달에는 비용이 올라갑니다. 이 예측 불가능성이 Railway 과금의 가장 큰 특징입니다.
비용을 통제하기 위한 방법:
- Spending Alert: 월 예상 비용이 임계값을 넘으면 알림을 받도록 설정
- 슬립 정책 조정: 트래픽이 없는 시간대에 인스턴스가 슬립하도록 허용하면 비용 절감 (단, 콜드 스타트 트레이드오프)
- 리소스 상한 설정: CPU와 메모리 상한을 설정하면 비용 상한도 간접적으로 제한
Supabase 비용 특성
Supabase는 티어 기반 과금입니다. Free → Pro ($25/mo) → Team ($599/mo) → Enterprise(별도 협의)로 나뉘며, 각 티어 내에서 사용량 초과 시 추가 과금이 발생합니다.
Free 티어에서 Pro로 전환하는 시점은 대부분 다음 중 하나입니다.
- DB 500 MB 한도 도달
- 7일 미사용 자동 일시정지 회피 필요
- 일일 백업 필요
- Branching 기능 사용 필요
총 비용 구조 비교
| 구간 | Railway | Supabase | 총 비용 특성 |
|---|---|---|---|
| 개발/테스트 | 무료–소액 | Free ($0) | 거의 무료 |
| 소규모 운영 | 사용량 비례 | Pro ($25/mo) | 고정+변동 혼합 |
| 중규모 운영 | 사용량 비례 (증가) | Pro + 초과 과금 | 변동폭 확대 |
| 대규모 | 예측 어려움 | Team/Enterprise | 전환 검토 구간 |
대안 비교
서버 호스팅: Railway vs Render vs Fly.io
| 항목 | Railway | Render | Fly.io |
|---|---|---|---|
| 배포 방식 | GitHub push 자동 | GitHub push 자동 | CLI (fly deploy) 주력 |
| 빌드 | Dockerfile / Nixpacks | Dockerfile / 자동 감지 | Dockerfile / Buildpacks |
| 콜드 스타트 | 있음 (슬립 시) | 있음 (Free 티어) | 없음 (최소 1 machine 상시) |
| 가격 모델 | 사용량 기반 | 인스턴스 기반 + 사용량 | 인스턴스 기반 + 사용량 |
| Preview Deploy | 지원 | 지원 | 미지원 (수동 구성 필요) |
| 리전 선택 | 제한적 | US/EU | 30+ 리전 |
| DX | 높음 (대시보드 직관적) | 높음 | 중간 (CLI 숙련 필요) |
| Docker 지원 | 네이티브 | 네이티브 | 네이티브 |
| 스케일링 | 수직 + 수평 (제한적) | 수직 + 수평 | 수직 + 수평 (세밀한 제어) |
Railway를 선택한 이유: DX가 가장 높았고, GitHub push만으로 배포가 완결되는 점이 결정적이었습니다. Fly.io는 성능과 리전 면에서 우수하지만 CLI 기반 워크플로우가 초기 세팅 비용을 높입니다. Render는 Railway와 유사하지만, 사용 시점에 Railway의 대시보드 UX가 더 나았습니다.
데이터베이스: Supabase vs PlanetScale vs Neon
| 항목 | Supabase | PlanetScale | Neon |
|---|---|---|---|
| DB 엔진 | PostgreSQL | MySQL (Vitess) | PostgreSQL |
| 인증 내장 | Auth 포함 | 미포함 | 미포함 |
| RLS | 지원 | 미지원 (MySQL 한계) | 지원 (PostgreSQL) |
| Branching | Pro 이상 | 지원 (핵심 기능) | 지원 (핵심 기능) |
| Connection Pooling | PgBouncer 내장 | 자체 프록시 | 자체 프록시 |
| Serverless Driver | supabase-js | @planetscale/database | @neondatabase/serverless |
| 무료 티어 | 500 MB, 50K MAU | 2025년 무료 티어 폐지 | 512 MB, 190 compute hours |
| 추가 서비스 | Realtime, Storage, Edge Functions | 없음 (DB 전용) | 없음 (DB 전용) |
Supabase를 선택한 이유: Auth + RLS + PostgreSQL을 한 곳에서 해결할 수 있다는 점이 결정적이었습니다. Neon은 PostgreSQL 기반으로 Supabase와 유사하지만, Auth가 없어서 별도의 인증 서비스를 조합해야 합니다. PlanetScale은 MySQL 기반이라 PostgreSQL을 선호하는 프로젝트에서는 선택지에서 제외됩니다.
전환 판단 기준
이 스택을 유지해도 되는 조건
- 월간 활성 사용자(MAU)가 수천 명 이하
- 트래픽이 한국/아시아 중심이 아님 (리전 지연이 치명적이지 않음)
- 서비스의 응답 시간 요구가 엄격하지 않음 (간헐적 콜드 스타트 허용)
- 팀 규모가 소규모 (1–3명) — 인프라 관리에 전담 인력을 배치할 수 없는 상황
- 인프라 비용이 월 고정 예산 이내에서 통제됨
전환을 검토해야 하는 시그널
graph TD
A[현재 스택 유지] --> B{콜드 스타트가<br/>비즈니스에 영향?}
B -->|No| C{비용이 예측<br/>가능한 범위?}
B -->|Yes| G[상시 구동 환경 필요]
C -->|Yes| D{멀티 리전<br/>필요?}
C -->|No| H[고정 요금 플랜 또는<br/>예약 인스턴스 검토]
D -->|No| E{DB 규모가<br/>Supabase Pro 한도 내?}
D -->|Yes| I[AWS/GCP 전환 검토]
E -->|Yes| F[현재 스택 유지]
E -->|No| J[Self-hosted PostgreSQL<br/>또는 RDS 검토]
G --> K[Fly.io / AWS ECS 검토]
H --> K
I --> L[풀 클라우드 마이그레이션]
J --> L
style F fill:#9f9,stroke:#333
style K fill:#ff9,stroke:#333
style L fill:#f99,stroke:#333
구체적으로 정리하면 다음 조건 중 하나라도 해당되면 전환을 검토할 시점입니다.
- 콜드 스타트가 비즈니스에 영향을 줄 때: 실시간 응답이 핵심인 기능이 추가되면 상시 구동 환경이 필요합니다.
- 비용이 예측 불가능해질 때: Railway의 사용량 기반 과금이 월별 편차가 크고, 비용 통제가 어려워지면 고정 요금 플랜이 있는 서비스로 전환하는 것이 경영 관점에서 합리적입니다.
- 멀티 리전이 필요할 때: 글로벌 사용자를 대상으로 지연 시간을 줄여야 하면 CDN과 리전 선택이 자유로운 클라우드가 유리합니다.
- DB 규모가 Supabase Pro 한도를 넘을 때: 8 GB를 넘는 데이터를 다루거나, 복잡한 쿼리 최적화가 필요하면 self-managed PostgreSQL이나 AWS RDS를 검토해야 합니다.
- 규정 준수 요구가 생길 때: 데이터 저장 위치, 암호화 수준, 감사 로그 등에 대한 규제가 적용되면 관리형 서비스의 제어 범위로는 부족할 수 있습니다.
Supabase에서 빠져나오기 어려운 이유
Supabase에서 다른 PostgreSQL 서비스로 마이그레이션하는 것은 DB 데이터 이전만의 문제가 아닙니다. Supabase에 의존하고 있는 기능별로 마이그레이션 난이도가 다릅니다.
| 기능 | 마이그레이션 난이도 | 이유 |
|---|---|---|
| PostgreSQL 데이터 | 낮음 | pg_dump / pg_restore로 이전 가능 |
| 스키마 + RLS 정책 | 중간 | RLS 정책은 Supabase 특화 함수(auth.uid()) 의존 |
| Auth | 높음 | 사용자 테이블, JWT 구조, OAuth 설정 모두 재구성 필요 |
| Storage | 중간 | S3 호환이므로 데이터 이전은 가능, URL 참조 변경 필요 |
| Realtime | 높음 | 대체 솔루션 직접 구축 필요 |
| Edge Functions | 중간 | Deno 기반, 다른 서버리스 플랫폼으로 재작성 필요 |
핵심은 Auth입니다. Supabase Auth에 의존하고 있다면, 마이그레이션 시 사용자 인증 체계 전체를 재설계해야 합니다. 이것은 단순한 인프라 전환이 아니라 제품 아키텍처 변경에 가깝습니다. Supabase를 선택할 때 이 Lock-in을 인지하고 시작하는 것이 중요합니다.
스택 전환은 “문제가 생기면 바꾸자”가 아니라, 전환 기준을 미리 정의해두고 주기적으로 확인하는 방식이 맞습니다. 전환 시점이 오면 이미 운영 부하가 높은 상태이므로, 여유가 있을 때 기준을 만들어두는 것입니다.
조합 운영 체크리스트
Railway + Supabase를 처음 세팅하는 경우, 다음 항목을 첫 배포 전에 완료하는 것을 권장합니다.
- Railway 서비스에 Supabase Pooler 주소(port 6543) 설정
- 환경변수에
service_role키와anon키 분리 설정 - Railway Spending Alert 설정
- 배포 실패 알림 (Slack/Discord webhook) 설정
- Health check 엔드포인트 구현 (DB 연결 상태 포함)
- RLS 정책 작성 후 테스트 데이터로 검증
- 마이그레이션 워크플로우 결정 (CLI 전용, 대시보드 DDL 금지)
- Preview Deploy용 DB 연결 전략 결정 (staging DB 분리 여부)
- Supabase Free 티어 자동 일시정지 정책 확인
- 로그 보존 전략 결정 (기본 제공 vs log drain)
정리
Railway + Supabase는 초기 단계 제품에 적합한 스택입니다. 배포 자동화, 관리형 PostgreSQL, 내장 인증이라는 세 가지를 최소한의 설정으로 얻을 수 있습니다. 그 대가로 콜드 스타트, 비용 예측 어려움, 모니터링 한계를 감수해야 합니다.
이 스택의 본질은 “인프라에 시간을 쓰지 않고 제품에 집중하기 위한 선택”입니다. 제품이 검증되고 트래픽이 안정화되면 전환을 검토하되, 그 전까지는 이 스택이 제공하는 개발 속도의 이점을 최대한 활용하는 것이 합리적입니다.
단, 초기에 AWS/GCP로 시작하는 것은 과잉 엔지니어링입니다. 전환 시점을 미리 정해두되, 실제 병목이 나타나기 전까지는 현재 스택을 유지하는 것이 낫습니다.
관련 글

Advisor는 건축이 아니라 가격표다
Anthropic의 Advisor Tool을 두 번 읽고 떠오른 생각. 이건 새 아키텍처가 아니라 2026년 가격 구조에 맞춰진 임시 해법이다. Opus 가격이 내려가면 사라질 패턴이고, 같은 시기 다른 11편의 논문도 결국 같은 방향으로 움직이고 있다. 시리즈의 앵커.

에이전트를 어떻게 조직하는가 — Hierarchy·Graph·Swarm·Routing과 회의론
쪼갠 에이전트를 어떻게 조직할 것인가. 네 가지 구조를 살펴보면서 'LLM 스웜은 사실 스웜이 아니다'라는 회의적 논문 한 편을 함께 본다. 결국 구조 선택은 가격에 끌려간다는 게 결론. 시리즈 2편.

에이전트를 어떻게 쪼개는가 — 2026년 다섯 갈래
2026년 논문들이 에이전트를 쪼개는 다섯 가지 방식을 한자리에 놓고 본다. Role·Skill·Judge가 같은 개념을 다른 이름으로 부르고 있다는 점, 그리고 시간축 연구가 거의 비어 있다는 점이 따라 나오는 결론. 시리즈 1편.