본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 23. 10:10

SaaS 데이터베이스 아키텍처 설계: 사용자, 조직, 구독 및 결제

요약

SaaS 제품 개발을 위한 멀티 테넌시 데이터베이스 아키텍처 설계 가이드를 제공합니다. 사용자, 조직, 구독 및 결제 시스템을 위한 검증된 스키마 설계와 PostgreSQL 등 관계형 DB에 적용 가능한 실무 패턴을 다룹니다.

핵심 포인트

  • 운영 복잡도를 낮추기 위해 행 수준 멀티 테넌시(Row-level multi-tenancy) 전략 권장
  • 애플리케이션 계층에서 organization_id를 통한 엄격한 테넌트 격리 강제 필요
  • RBAC 구현 및 유연한 결제 전환을 고려한 엔티티 관계 설계 중요
  • UUID 사용을 통한 보안 강화 및 확장 가능한 스키마 구축

첫 주에 내린 SaaS 데이터베이스 아키텍처 결정은 수년 동안 당신을 괴롭힐 것입니다. 이 가이드는 사용자, 조직, 역할 기반 멤버십(role-based memberships), 구독 상태 머신(subscription state machines), 그리고 결제 기록(billing records)을 다루는 검증된 멀티 테넌시(multi-tenant) 스키마 설계를 안내합니다. 각 섹션에는 PostgreSQL, D1 또는 기타 관계형 데이터베이스(relational database)에 직접 적용할 수 있는 프로덕션 준비 완료된 SQL, 인덱싱 전략(indexing strategies), 그리고 마이그레이션 패턴(migration patterns)이 포함되어 있습니다.

서론 (Introduction)

모든 SaaS 창업자는 단순한 비전에서 시작합니다: 가입하고, 결제하고, 제품을 사용하는 것. 하지만 내부적으로 그 "단순한" 흐름은 멀티 테넌시 격리(multi-tenancy isolation), 결제 정확성, 그리고 미래의 유연성 사이의 균형을 맞추는 데이터베이스 스키마를 필요로 합니다. 설계를 잘못하면, 고객이 인보이스(invoices)를 기다리는 동안 6개월 만에 마이그레이션(migrations)을 다시 작성하게 될 것입니다.

오늘 당신이 설계하는 스키마는 다음과 같은 작업들을 얼마나 쉽게 수행할 수 있는지를 결정합니다:

  • 사용자별 기능을 깨뜨리지 않고 조직 수준의 결제(organization-level billing) 추가
  • 팀 전체에 걸친 역할 기반 액세스 제어(RBAC, role-based access control) 구현
  • 수동 데이터 수정 없이 체험판에서 유료로의 전환(trial-to-paid conversions) 실행
  • 정확한 인보이스(invoices) 및 수익 보고서 생성

멀티 테넌시 전략 (Multi-Tenancy Strategy)

단 하나의 CREATE TABLE을 작성하기 전에, 테넌트(tenants) 간의 데이터를 어떻게 격리할지 결정해야 합니다:

전략격리 수준복잡도적합한 사례
행 수준 (Row-level, 공유 DB)낮음낮음초기 단계, B2C
...

오늘 시작하는 SaaS 제품의 90%에게는 **공유 데이터베이스를 사용하는 행 수준 멀티 테넌시(row-level multi-tenancy with a shared database)**가 올바른 선택입니다. 이는 운영 복잡도를 낮게 유지하며, 간단한 WHERE organization_id = ?를 통해 테넌트 간 분석(cross-tenant analytics)을 가능하게 합니다.

핵심은 **애플리케이션 계층에서 테넌트 격리를 강제(enforce tenant isolation at the application layer)**하는 것입니다. 모든 쿼리는 반드시 organization_id로 필터링해야 합니다. 클라이언트가 제공하는 ID를 절대 신뢰하지 마세요.

핵심 엔티티 및 관계 (Core Entities and Relationships)

User ── MemberOf ── Organization
                         │
                    Subscription
...

1. 사용자 (Users)

사용자는 인증(authenticate)을 수행하는 개인입니다. 이 테이블은 식별 데이터(identity data)만 포함하여 가볍게 유지하세요.

CREATE TABLE users (
    id            TEXT PRIMARY KEY,
    email         TEXT NOT NULL UNIQUE,
...

설계 노트 (Design notes): 열거 공격 (enumeration attacks)을 방지하기 위해 TEXT ID (UUIDs)를 사용하세요. 타임존에 구애받지 않는 비교를 위해 타임스탬프는 Unix 정수 (Unix integers)로 저장하세요.

2. 조직 (Organizations)

조직 (Organizations)은 테넌트 경계 (tenant boundary)입니다. 모든 구독 (subscription)은 사용자가 아닌 조직에 속합니다.

CREATE TABLE organizations (
    id          TEXT PRIMARY KEY,
    name        TEXT NOT NULL,
...

조직이 중요한 이유: 5명으로 구성된 팀이 하나의 공유 구독을 원할 때 사용자에게 직접 청구하는 방식은 작동하지 않습니다. 항상 조직에 청구하세요. 사용자는 오고 가지만, 조직은 지속됩니다.

3. 멤버십 (Memberships) (사용자 ↔ 조직)

역할 (roles)을 포함하는 조인 테이블 (join table)입니다. users 테이블에 역할을 직접 저장하지 마세요.

CREATE TABLE memberships (
    id              TEXT PRIMARY KEY,
    user_id         TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
...

역할 계층 (Role hierarchy): owner (전체 제어) -> admin (멤버 관리) -> member (표준 액세스) -> billing (송장 조회 전용).

4. 구독 (Subscriptions)

이것은 결제 시스템 (billing system)의 핵심입니다. 구독은 조직을 플랜 (plan)에 연결하고, **상태 머신 (state machine)**을 통해 수명 주기 (lifecycle)를 추적합니다.

CREATE TABLE subscriptions (
    id                TEXT PRIMARY KEY,
    organization_id   TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
...

구독 상태 머신 (Subscription State Machine)

  trialing ──→ active ──→ past_due ──→ canceled/expired
                  ↑
                  └─── (갱신 실패)
  • trialing → 체험 기간이 끝나고 결제에 성공하면 자동으로 active로 전환됩니다.
  • active → 정상적인 경로 (happy path). 결제 실패 시 더닝 (Dunning, 미납 관리)이 시작됩니다.
  • past_due → 결제 실패. 유예 기간 (Grace period, 3~14일) 후 canceled 또는 expired 상태가 됩니다.
  • canceled → 사용자 또는 시스템에 의해 취소됨. 기간 종료 전까지는 액세스 가능합니다.
  • expired → 최종 상태. 액세스 불가.
  • incomplete → 초기 결제 실패 (첫 시도에서 카드 거절됨)

불리언(boolean) 타입의 is_active 컬럼을 절대 사용하지 마세요. 명시적인 전이(transition)를 가진 상태 머신(state machine)을 사용하는 것이 모호한 상태를 방지합니다.

5. 인보이스 (Invoices)

CREATE TABLE invoices (
    id                TEXT PRIMARY KEY,
    organization_id   TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
...

6. 인보이스 라인 아이템 (Invoice Line Items)

CREATE TABLE invoice_line_items (
    id           TEXT PRIMARY KEY,
    invoice_id   TEXT NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
...

개별 라인 아이템(line items)을 저장하는 것은 일할 계산된 환불(prorated refunds), 세무 보고, 그리고 사용량 기반 과금(usage-based billing) 내역 산출을 위해 매우 중요합니다.

과금 및 인보이스 설계 (Billing and Invoicing Design)

정액제 구독 (Flat-Rate Subscription)

기간당 고정된 금액을 청구합니다. 단순하고 예측 가능합니다.

인당 과금 (Per-Seat / Per-User)

단가를 활성 멤버 수로 곱합니다:

SELECT m.organization_id, COUNT(*) AS member_count
FROM memberships m
WHERE m.organization_id = ?
...

사용량 기반 과금 (Usage-Based)

사용량 이벤트(usage events)를 별도의 테이블에 저장합니다:

CREATE TABLE usage_events (
    id              TEXT PRIMARY KEY,
    organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
...

흔한 실수 (Common Pitfalls)

과금 로직에서의 N+1 쿼리 (N+1 Queries in Billing)

-- 나쁜 예: N+1 — 조직을 위한 쿼리 1개, 그 후 구독당 N개의 쿼리
SELECT * FROM organizations;

...

current_period_end 인덱스 누락

구독 만료를 위해 매일 크론(cron) 작업을 실행한다면, 인덱스가 없을 경우 다음 쿼리는 전체 테이블 스캔(full table scan)을 수행하게 됩니다:

SELECT id FROM subscriptions
WHERE status = 'active' AND current_period_end < unixepoch();

과금 기록을 파괴하는 연쇄 삭제 (Cascading Deletes)

실수로 조직(org)을 삭제하면 모든 인보이스가 사라집니다. 재무 기록의 경우 ON DELETE RESTRICT를 사용하거나 소프트 삭제(deleted_at 컬럼)를 고려하세요.

통화 값을 부동 소수점(Float)으로 저장하기

돈을 다룰 때 FLOATREAL을 절대 사용하지 마세요. 항상 **정수 센트(integer cents)**를 사용해야 합니다:

amount_due INTEGER NOT NULL  -- $29.99 -> 2999

결론 (Conclusion)

잘 설계된 SaaS 데이터베이스 스키마 (Schema)는 제대로 작동할 때는 눈에 보이지 않지만, 제대로 작동하지 않을 때는 재앙이 됩니다. 이 가이드에서 다룬 엔티티 (Entities)와 패턴들 — 사용자 (Users), 조직 (Organizations), 멤버십 (Memberships), 상태 머신 (State machines)을 활용한 구독 (Subscriptions), 그리고 정규화된 인보이싱 (Normalized invoicing) — 은 수천 개의 테넌트 (Tenants)를 처리하는 프로덕션 시스템 (Production systems)을 통해 정제되었습니다.

행 단위 멀티 테넌시 (Row-level multi-tenancy)부터 시작하세요. 사용자가 아닌 조직에 비용을 청구하세요. 구독 상태를 명시적인 상태 머신 (State machine)으로 모델링하세요. 돈은 정수 (Integers)로 저장하세요. 쿼리 패턴에 맞는 인덱스 (Indexes)를 추가하세요. 그리고 언제나, 항상, 되돌릴 수 있는 마이그레이션 (Reversible migrations)을 작성하세요.

관련 리소스 (Related Resources)

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0