--- title: ca-tmpl - Multi-tenancy 결정 (opt-in shared DB + tenant_id) source_type: project status: verified confidence: high tags: [ca-tmpl, multi-tenancy, saas, actually-implemented, locally-verified, documented-only] related_projects: [ca-tmpl] last_reviewed: 2026-07-02 --- # ca-tmpl - Multi-tenancy 결정 (opt-in shared DB + tenant_id) > Layer: `wiki/projects/` — 내 프로젝트 사실. 일반 개념은 [[wiki/concepts/multi-tenancy-isolation-patterns]] 참조. ## 프로젝트 컨텍스트 ca-tmpl(Clean Architecture skeleton)에서 multi-tenancy를 어떻게 다룰지 정한 결정 문서다. baseline은 다음 조합이다. - **opt-in**: `APP_TENANT_ENABLED=true`일 때만 tenant 로직 활성. single-tenant deployment에서는 비활성화하여 skeleton 적용 범위를 넓힘. - **shared DB + `tenant_id` column (ULID)**: AWS Pool 모델 / Hibernate DISCRIMINATOR 전략에 해당. - **Tenant resolution**: JWT claim 우선, `X-Tenant-Id` header는 **admin only(`CROSS_TENANT_ADMIN` capability 보유자)** 에 한해 허용. - B2B 초기 단계(tenant 수 수십~수백 단위) 가정. isolation 비용 대비 운영 단순성 우선. **현재 진행 상태**: tenant-aware registry/runbook/capability/idempotency scope 일부 구현 + repository tenant filter는 planned. 본 문서는 구현된 tenant support surface와 아직 없는 storage isolation enforcement를 분리한다. ## 실제 구현 내용 (`actually-implemented`) - `docs/registries/env-keys.yaml`에 `APP_TENANT_ENABLED`, `docs/registries/headers.yaml`에 `X-Tenant-Id`, `docs/registries/capabilities.yaml`에 `CROSS_TENANT_ADMIN`, `docs/registries/error-codes.yaml`에 tenant error code가 존재한다. - `docs/runbooks/authz-tenant-mismatch.md`, `docs/runbooks/authz-cross-tenant-violation.md`가 cross-tenant incident response stub을 제공한다. - `AuthorizationPrincipal`, `RequiresPermission`, `AuthorizationPort`, role/permission registry와 `AuthorizationContractTest`가 capability 기반 authorization foundation을 제공한다. - `IdempotencyScope`와 `IdempotencyKeySupport`는 tenant-aware scope를 표현할 수 있다. - repository-level tenant predicate 강제, tenant resolver filter, storage isolation은 아직 구현되지 않았다. ## 로컬/dev 검증 (`locally-verified`) - `./gradlew check` 통과(2026-07-02, `BUILD SUCCESSFUL`, 114 tasks). - `AuthorizationContractTest`, `IdempotencyScopeTest`, `IdempotencyKeySupportTest`, registry governance tests가 tenant/capability/registry surface 일부를 검증한다. - repository tenant filter와 cross-tenant E2E isolation은 검증되지 않았다. ## 운영 검증 (`prod-verified`) **없음.** ca-tmpl은 skeleton이며 prod 배포 이력 없음. ## 문서/계획만 존재 (`documented-only` / `planned`) ### Tenant resolution + isolation 정책 (partially-implemented) - JWT claim 우선 → admin only `X-Tenant-Id` header fallback → 해석 실패 시 reject. - repository 진입점에서 tenant filter 강제(`CROSS_TENANT_ADMIN` capability 없이는 모든 query에 `tenant_id` predicate). - **status: partially-implemented** — registry/header/capability/runbook/idempotency scope는 존재하지만 repository filter와 tenant resolver filter는 planned. ### 6종 대안 검토 → Pool 채택 검토한 6가지와 채택/기각 사유: | 대안 | 분류 | 채택 여부 | 사유 | |------|------|-----------|------| | **shared DB + tenant_id** (ULID) | AWS Pool / Hibernate DISCRIMINATOR | **채택** | B2B 초기, tenant 수 수십~수백 예상. 운영 단순성. | | subdomain-based | resolution-only | 기각 | wildcard DNS/TLS·subdomain takeover·local dev 비용. resolution은 isolation을 보장하지 않음. | | JWT claim only (storage 분리 없음) | resolution-only | 기각 | claim 검증 누락 시 cross-tenant leak. storage layer 강제 필요. | | schema-per-tenant | Hibernate SCHEMA | 기각 | catalog bloat·`search_path` 전환 plan cache 무효화·HikariCP 설계 복잡. 초기 단계 ROI 부정. | | db-per-tenant | AWS Silo | 기각 | 운영 비용 폭증(마이그레이션·백업·connection pool 폭발). 규제 요구 부재. | | hybrid (Azure Deployment Stamps / AWS Bridge) | mixed | 기각 | 운영 복잡도 최고. PMF 이후 단계 검토 사항. | 근거: ca-tmpl은 skeleton이며 초기 도입 대상은 B2B 소규모 SaaS. 결정은 verified 되었지만 storage isolation enforcement는 아직 planned다. ### Migration trigger 3가지 정의 shared DB → schema/db-per-tenant로 전환을 검토할 조건: 1. **규제**: 금융·의료(HIPAA·FedRAMP·data residency) isolation 강제. 2. **규모**: tenant 수 hundreds 도달 + 단일 row 수 억대 진입(noisy neighbor·index 비용 임계). 3. **상품 tier**: enterprise tier 등장으로 isolation을 가격에 반영해야 할 때. **status: documented-only** — migration trigger는 아직 관측 지표/자동 경보로 구현되지 않았다. ### `CROSS_TENANT_ADMIN` capability 정의 - admin/support 운영 동선용. 보유자만 `X-Tenant-Id` header로 tenant 전환 가능. - 일반 사용자 경로는 JWT claim 단독, header 무시. - **status: partially-implemented** — capability vocabulary와 authorization foundation은 존재하지만, repository tenant filter와 admin tenant switching E2E는 미구현. ## 면접에서 말할 수 있는 범위 ### 자신 있게 - "Pool / Silo / Bridge의 차이와 각각의 비용·isolation trade-off." - "tenant resolution에서 JWT claim과 `X-Tenant-Id` header의 trust 차이, header를 admin only로 제한하는 이유." - "shared DB → 격리 강화 모델로 가는 **migration trigger 3가지**(규제 / 규모 / enterprise tier)." ### 적당히 - ULID vs UUID 선택 이유(정렬 가능성·index locality·시간 정보 노출 trade-off). - Hibernate multi-tenancy strategy(DATABASE / SCHEMA / DISCRIMINATOR) 차이와 `CurrentTenantIdentifierResolver` 동작 개요. ### 답하면 안 됨 (모른다고 해야 함) - "tenant 격리를 어떻게 **측정**했는가" — 측정·테스트 부재. - "cross-tenant 침해 시도/penetration test 결과" — 수행 안 함. - "schema-per-tenant 운영 경험" — 검토만 했고 운영해 본 적 없음. - "실제 tenant 수, row 수, 성능 지표" — skeleton에 데이터 없음. ## 과장 금지 지점 - "shared DB + tenant_id가 항상 우월하다" → 금지. 규제 산업(HIPAA·금융·data residency)에서는 Silo가 사실상 강제다. 본 선택은 **B2B 초기 단계 가정에 종속된 결정**이라는 점을 함께 말할 것. - **Stripe/Citus schema-per-tenant 한계치 단언 금지** — 정확 인용 wording이 미완(raw 자료 `needs-confirmation`). "수백~수천 단위에서 catalog overhead가 보고된다" 정도로 출처와 함께만 언급. - "ca-tmpl에 multi-tenancy를 **완성했다**" → 금지. tenant-aware registry/capability/scope foundation은 구현됐지만, repository-level tenant filter와 E2E isolation은 planned다. - "JWT claim만 검증하면 안전하다" → 금지. repository 레벨 tenant filter가 별도로 필요하다. - "Atlassian이 그렇게 하니까 best practice" → 금지. company-tech-blog는 관점이지 공식 기준이 아니다. ## 관련 개념 - [[wiki/concepts/multi-tenancy-isolation-patterns]] — Pool/Silo/Bridge, Hibernate strategy, resolution 방식 공식 기준 ## Sources - [[raw/project-notes/ca-skeleton-operational-contract]] — §10 Repository Access Permission Contract, §29 Topic 6 Multi-tenancy Isolation - [[raw/branch-notes/feature-tenant-context-policy]] — tenant resolution(JWT > header admin only) SSOT - [[raw/branch-notes/feature-repository-access-permission-contract]] — `CROSS_TENANT_ADMIN` capability, repository 레벨 tenant filter contract ## Cluster / 묶음 - [[wiki/blog/ca-tmpl-multi-tenancy-isolation-patterns-2026-07-02]]