본문 바로가기
개발&프로그래밍

[Supabase] 심층 분석 — 아키텍처와 실전 활용

by 재아군 2026. 4. 2.
반응형

Supabase 심층 분석 — 아키텍처와 실전 활용 대표 이미지

 

안녕하세요!

재아군의 관찰인생입니다.

 

오늘은 "오픈소스 Firebase 대안"으로 빠르게 성장하고 있는 Supabase를 깊이 있게 분석해보겠습니다.

Firebase의 편리함은 좋지만 벤더 종속이 걱정되거나, PostgreSQL 기반의 관계형 데이터베이스가 필요한 프로젝트를 진행하고 계신다면 이 글이 특히 도움이 될 것입니다.

 
이 글은 Supabase를 처음 접하는 분부터, 이미 사용 중이지만 아키텍처를 더 깊이 이해하고 싶은 분 모두를 위해 작성되었습니다. 핵심 개념 → 아키텍처 → 실전 코드 → 비교 분석 → 베스트 프랙티스 순서로 진행합니다.
 

Supabase 구성요소 개요 다이어그램

 

1. Supabase란 무엇인가?

핵심 정의

Supabase는 PostgreSQL을 기반으로 한 오픈소스 Backend-as-a-Service(BaaS) 플랫폼입니다. 2020년에 등장하여 "오픈소스 Firebase 대안"이라는 포지션으로 빠르게 성장했으며, 2025년 기준 GitHub 스타 75,000개 이상, 100만 개 이상의 데이터베이스가 호스팅되고 있습니다.

Firebase와 가장 큰 차이점은 관계형 데이터베이스(PostgreSQL)를 핵심에 두고 있다는 것입니다. NoSQL 기반인 Firebase Firestore와 달리, 복잡한 JOIN 쿼리, 트랜잭션, 외래 키 제약 등 RDBMS의 강점을 그대로 활용할 수 있습니다.

Supabase가 제공하는 핵심 서비스

서비스 기반 기술 설명
Database PostgreSQL 완전한 PostgreSQL 인스턴스 — RLS, 함수, 트리거 모두 사용 가능
Auth GoTrue 이메일, OAuth(Google, GitHub 등), 매직링크, 전화번호 인증
Storage S3 호환 파일 업로드/다운로드, 이미지 변환, CDN 배포
Realtime Phoenix(Elixir) DB 변경사항 실시간 구독, Presence, Broadcast
Edge Functions Deno 서버리스 함수 — TypeScript, 글로벌 엣지 배포
Vectors pgvector AI 임베딩 저장 및 유사도 검색 (RAG 파이프라인)

 

 

등장 배경 — 왜 Supabase가 필요했나?

Firebase는 빠른 프로토타이핑에 탁월하지만, 프로젝트가 성장하면서 몇 가지 근본적인 한계에 부딪히게 됩니다:

  • 벤더 종속(Vendor Lock-in) — Firestore의 독자적 쿼리 문법과 데이터 모델은 다른 DB로 마이그레이션이 극히 어렵습니다
  • 복잡한 쿼리의 한계 — NoSQL 특성상 다중 테이블 JOIN, 서브쿼리, 집계 함수가 제한적입니다
  • 비용 예측 어려움 — 읽기/쓰기 횟수 기반 과금으로 트래픽 증가 시 비용이 급증할 수 있습니다
  • 오픈소스 부재 — 셀프호스팅이 불가능하고, 데이터 주권(Sovereignty) 확보가 어렵습니다

Supabase는 이런 문제를 "PostgreSQL이라는 검증된 기반 위에, Firebase 수준의 개발자 경험(DX)"을 제공하는 방식으로 해결했습니다.

 

Supabase 핵심 포인트

 

2. 기술 아키텍처 심층 분석

Supabase의 가장 큰 기술적 특징은 기존 오픈소스 도구들을 조합하여 통합 플랫폼을 만들었다는 점입니다. 자체 기술을 최소화하고, 각 영역에서 가장 검증된 도구를 선택했습니다.

핵심 컴포넌트 구조

┌─────────────────────────────────────────────────┐
│                 Client SDK                       │
│         (JS/TS, Flutter, Python, Swift)          │
└──────────┬──────────────┬───────────────────────┘
           │              │
    ┌──────▼──────┐ ┌────▼─────────────┐
    │   PostgREST │ │  GoTrue (Auth)   │
    │  (REST API) │ │  JWT 발급/검증     │
    └──────┬──────┘ └────┬─────────────┘
           │              │
    ┌──────▼──────────────▼───────────────┐
    │          PostgreSQL                  │
    │    ┌──────────┐  ┌──────────┐       │
    │    │   RLS    │  │ pgvector │       │
    │    │ (보안정책)│  │ (AI벡터) │       │
    │    └──────────┘  └──────────┘       │
    └──────┬──────────────────────────────┘
           │
    ┌──────▼──────────────────────────────┐
    │  Realtime (Phoenix/Elixir)          │
    │  DB 변경 → WebSocket 브로드캐스트     │
    └─────────────────────────────────────┘

PostgREST — SQL을 REST API로

PostgREST는 PostgreSQL 스키마를 자동으로 RESTful API로 노출하는 Haskell 기반 서버입니다. 테이블을 만들면 즉시 CRUD 엔드포인트가 생성되며, 별도의 API 서버 코드를 작성할 필요가 없습니다.

// Supabase Client로 데이터 조회 — 내부적으로 PostgREST 호출
const { data, error } = await supabase
  .from('posts')
  .select(`
    id, title, created_at,
    author:profiles(name, avatar_url),
    comments(count)
  `)
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .range(0, 9);

// 위 코드는 내부적으로 이런 SQL로 변환됩니다:
// SELECT id, title, created_at, profiles.name, profiles.avatar_url, count(comments)
// FROM posts
// JOIN profiles ON posts.author_id = profiles.id
// LEFT JOIN comments ON comments.post_id = posts.id
// WHERE status = 'published'
// ORDER BY created_at DESC LIMIT 10;

Row Level Security (RLS) — 데이터베이스 레벨 보안

Supabase 보안의 핵심은 RLS(Row Level Security)입니다. API 서버에서 권한 체크를 하는 것이 아니라, PostgreSQL 자체에서 행 단위로 접근을 제어합니다.

-- 사용자는 자신의 데이터만 읽을 수 있음
CREATE POLICY "Users can read own data"
  ON profiles FOR SELECT
  USING (auth.uid() = user_id);

-- 인증된 사용자만 게시글 작성 가능
CREATE POLICY "Authenticated users can insert posts"
  ON posts FOR INSERT
  WITH CHECK (auth.role() = 'authenticated');

-- 관리자는 모든 데이터 접근 가능
CREATE POLICY "Admins have full access"
  ON posts FOR ALL
  USING (auth.jwt() ->> 'role' = 'admin');

이 방식의 장점은 어떤 클라이언트(웹, 모바일, 서버)에서 접근하든 동일한 보안 정책이 적용된다는 것입니다. API 미들웨어에서 권한 체크를 빠뜨리는 실수를 원천적으로 방지할 수 있습니다.

 

Supabase 단계별 가이드

 

3. Realtime과 Edge Functions

Realtime — DB 변경을 실시간으로

Supabase Realtime은 Elixir/Phoenix 기반으로 구축되어 있으며, PostgreSQL의 WAL(Write-Ahead Log)을 감시하여 데이터 변경사항을 WebSocket으로 브로드캐스트합니다.

// 실시간 채팅 구독 예시
const channel = supabase
  .channel('chat-room-1')
  .on('postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'messages',
      filter: 'room_id=eq.1' },
    (payload) => {
      console.log('새 메시지:', payload.new);
      addMessageToUI(payload.new);
    }
  )
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState();
    updateOnlineUsers(Object.keys(state));
  })
  .subscribe();

// Presence — 현재 접속자 추적
await channel.track({
  user_id: currentUser.id,
  online_at: new Date().toISOString()
});

Realtime은 세 가지 모드를 지원합니다:

  • Postgres Changes — DB INSERT/UPDATE/DELETE를 실시간 감지 (RLS 정책이 그대로 적용됨)
  • Broadcast — 채널 기반 메시지 전송 (DB를 거치지 않아 초저지연)
  • Presence — 접속자 상태 추적 (온라인/오프라인, 커서 위치 등)

Edge Functions — 서버리스 로직

Deno 런타임 기반의 Edge Functions는 TypeScript를 네이티브로 지원하며, 전 세계 엣지 노드에 배포됩니다. Webhook 처리, 외부 API 연동, 결제 로직 등 클라이언트에 노출할 수 없는 서버 로직을 처리할 때 사용합니다.

// supabase/functions/send-welcome-email/index.ts
import { serve } from 'https://deno.land/std/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  const { record } = await req.json();  // DB trigger에서 전달된 신규 유저

  // 환영 이메일 발송
  await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: { Authorization: `Bearer ${Deno.env.get('RESEND_API_KEY')}` },
    body: JSON.stringify({
      to: record.email,
      subject: '가입을 환영합니다!',
      html: `

${record.name}님, 환영합니다!

`
    })
  });

  return new Response(JSON.stringify({ success: true }));
});

4. 실전 프로젝트 셋업 가이드

Supabase로 실제 프로젝트를 시작하는 과정을 단계별로 살펴보겠습니다.

Step 1: 프로젝트 초기화

# Supabase CLI 설치
npm install -g supabase

# 프로젝트 초기화
supabase init
supabase start   # 로컬 Docker 환경 실행

# 클라이언트 SDK 설치
npm install @supabase/supabase-js

Step 2: 스키마 설계 & 마이그레이션

-- supabase/migrations/001_create_tables.sql

CREATE TABLE profiles (
  id UUID REFERENCES auth.users PRIMARY KEY,
  name TEXT NOT NULL,
  avatar_url TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE posts (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  author_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  content TEXT,
  status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'published')),
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- RLS 활성화
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 신규 가입 시 자동으로 profiles 레코드 생성
CREATE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO profiles (id, name, avatar_url)
  VALUES (NEW.id, NEW.raw_user_meta_data->>'name', NEW.raw_user_meta_data->>'avatar_url');
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION handle_new_user();

Step 3: 클라이언트 연동 (Next.js 예시)

// lib/supabase.ts
import { createBrowserClient } from '@supabase/ssr';

export const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// app/posts/page.tsx — 게시글 목록
export default async function PostsPage() {
  const { data: posts } = await supabase
    .from('posts')
    .select('id, title, created_at, author:profiles(name)')
    .eq('status', 'published')
    .order('created_at', { ascending: false });

  return (
    <ul>
      {posts?.map(post => (
        <li key={post.id}>
          {post.title} — {post.author.name}
        </li>
      ))}
    </ul>
  );
}
 

Supabase 적용 전후 비교

 

5. 경쟁 기술 비교 분석

BaaS 시장의 주요 선택지를 다각도로 비교해보겠습니다.

항목 Supabase Firebase Appwrite PocketBase
DB 유형 PostgreSQL (관계형) Firestore (NoSQL) MariaDB SQLite
오픈소스 O (Apache 2.0) X O (BSD 3) O (MIT)
셀프호스팅 Docker Compose 불가 Docker 단일 바이너리
실시간 WebSocket + DB변경감지 네이티브 지원 WebSocket SSE
AI/벡터 지원 pgvector 내장 Vertex AI 연동 X X
무료 티어 2개 프로젝트, 500MB Spark(무제한 프로젝트) 셀프호스팅 무료 완전 무료
추천 시나리오 관계형 데이터 + 확장성 빠른 프로토타입 + GCP 완전 자체 운영 소규모/사이드 프로젝트

Supabase가 유리한 경우

  • 복잡한 관계형 데이터 모델이 필요한 프로젝트 (e-커머스, SaaS, CRM)
  • 기존 PostgreSQL 경험이 있는 팀
  • 벤더 종속을 피하고 싶은 경우 (언제든 셀프호스팅 전환 가능)
  • AI 기능(벡터 검색, 임베딩)을 DB와 통합하고 싶은 경우

Firebase가 유리한 경우

  • GCP 생태계를 이미 사용 중인 팀
  • 문서형 데이터(NoSQL)가 자연스러운 도메인 (채팅, IoT 센서 데이터)
  • 모바일 앱 중심 + FCM 푸시 알림이 핵심인 프로젝트

6. 도입 시 베스트 프랙티스

실제 프로덕션에서 Supabase를 운영하면서 얻은 핵심 원칙들입니다.

1) RLS는 처음부터 설계하라

RLS를 나중에 추가하면 기존 쿼리가 예상치 못하게 빈 결과를 반환할 수 있습니다. 테이블 생성과 동시에 RLS 정책을 정의하는 것을 습관화해야 합니다.

2) 마이그레이션을 코드로 관리하라

Supabase 대시보드의 SQL Editor는 편리하지만, 프로덕션에서는 반드시 supabase/migrations/ 디렉토리에 SQL 파일로 관리해야 합니다. CI/CD에서 supabase db push로 자동 배포할 수 있습니다.

3) TypeScript 타입을 자동 생성하라

# DB 스키마에서 TypeScript 타입 자동 생성
supabase gen types typescript --local > src/types/database.ts

# 타입 안전한 클라이언트 사용
import { Database } from '@/types/database';
const supabase = createClient<Database>(url, key);

4) 인덱스를 잊지 마라

PostgREST가 자동으로 API를 생성해주지만, 쿼리 성능은 PostgreSQL 인덱스에 의존합니다. 자주 필터링하는 컬럼에는 반드시 인덱스를 추가하세요.

-- 자주 사용하는 필터 컬럼에 인덱스
CREATE INDEX idx_posts_status ON posts(status);
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_created ON posts(created_at DESC);

5) 로컬 개발 환경을 적극 활용하라

supabase start로 Docker 기반 로컬 환경이 구동되며, 대시보드(localhost:54323), DB(localhost:54322), API(localhost:54321)를 모두 로컬에서 테스트할 수 있습니다. 클라우드 프로젝트를 소모하지 않고 개발할 수 있다는 큰 장점이 있습니다.

 

Supabase 실전 체크리스트

 

7. AI 시대의 Supabase — pgvector와 RAG

Supabase가 최근 가장 주목받는 이유 중 하나는 pgvector 확장을 통한 AI 워크로드 지원입니다. 별도의 벡터 DB(Pinecone, Weaviate 등)를 운영할 필요 없이, 기존 PostgreSQL 안에서 임베딩 저장과 유사도 검색을 처리할 수 있습니다.

-- pgvector 확장 활성화
CREATE EXTENSION IF NOT EXISTS vector;

-- 문서 임베딩 테이블
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT,
  embedding VECTOR(1536),  -- OpenAI text-embedding-3-small 차원
  metadata JSONB
);

-- 유사도 검색 함수
CREATE FUNCTION match_documents(
  query_embedding VECTOR(1536),
  match_count INT DEFAULT 5,
  match_threshold FLOAT DEFAULT 0.7
) RETURNS TABLE (id BIGINT, content TEXT, similarity FLOAT)
AS $$
  SELECT id, content, 1 - (embedding <=> query_embedding) as similarity
  FROM documents
  WHERE 1 - (embedding <=> query_embedding) > match_threshold
  ORDER BY embedding <=> query_embedding
  LIMIT match_count;
$$ LANGUAGE sql;

이 접근법의 핵심 장점은 관계형 데이터와 벡터 데이터를 하나의 트랜잭션에서 다룰 수 있다는 것입니다. 예를 들어 "이 사용자가 작성한 문서 중 질문과 유사한 것"을 검색할 때, RLS + pgvector를 결합하면 한 번의 쿼리로 보안과 유사도 검색을 동시에 해결할 수 있습니다.


마무리

Supabase는 단순한 "Firebase 대안"을 넘어, PostgreSQL이라는 40년 검증된 기반 위에 현대적 개발자 경험을 올린 플랫폼으로 진화하고 있습니다.

특히 인상적인 점은:

  • 오픈소스 조합 전략 — 자체 기술 대신 PostgREST, GoTrue, Phoenix 같은 검증된 도구를 통합
  • RLS 중심 보안 — 데이터베이스 레벨에서 보안을 해결하여 API 계층의 복잡도를 크게 줄임
  • pgvector 네이티브 지원 — AI 시대에 별도 벡터 DB 없이도 RAG 파이프라인 구축 가능
  • 셀프호스팅 자유 — 클라우드에서 시작하고, 필요할 때 자체 인프라로 전환 가능

관계형 데이터 모델이 필요하고, 벤더 종속을 피하면서도 Firebase 수준의 개발 속도를 원한다면 Supabase는 현재 가장 균형 잡힌 선택지라고 할 수 있습니다.

 
이 글이 도움이 되셨다면 댓글과 공유 부탁드립니다. Supabase 관련 궁금한 점이 있다면 언제든 댓글로 남겨주세요!
 

다음 글에서 더 깊이 있는 내용으로 찾아뵙겠습니다.

감사합니다!

반응형

댓글