"우리 회사는 슬랙(Slack) 같은 서비스를 만듭니다. 삼성전자도 쓰고 LG전자도 씁니다. 두 회사의 데이터는 물리적으로 섞이면 안 될까요, 아니면 논리적으로만 구분하면 될까요?"
멀티테넌시란 하나의 애플리케이션 인스턴스가 여러 사용자(Tenant)를 동시에 처리하는 아키텍처를 말합니다. 핵심은 **데이터 격리(Data Isolation)**입니다. 비용을 아끼기 위해 데이터를 합칠수록 개발 난이도와 보안 위험은 올라갑니다.

1. 방식 1: DB 분리 (Database per Tenant) - "최고의 보안, 최고의 비용"
고객사마다 **별도의 데이터베이스(또는 스키마)**를 생성해 주는 방식입니다. 물리적으로 데이터가 섞일 일이 없습니다.
특징
- 완벽한 격리: A 고객사가 해킹당해도 B 고객사의 DB는 안전합니다.
- 성능 보장: 특정 고객사가 헤비 유저라도(Noisy Neighbor), 다른 고객사의 DB 성능에 영향을 주지 않습니다.
- 인프라 종속: 고객사가 1,000개면 DB도 1,000개가 필요합니다.
코드 예시 (Spring Boot + AbstractRoutingDataSource)
Java
// 요청이 들어올 때마다, 헤더나 도메인을 보고 DB를 갈아끼움
public class TenantRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// ThreadLocal에 저장된 현재 Tenant ID 반환
return TenantContext.getCurrentTenant();
}
}
// 설정
@Bean
public DataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("SAMSUNG", samsungDataSource());
targetDataSources.put("LG", lgDataSource());
TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
장단점
- 장점:
- 보안성 최강: 개발자가 실수로 WHERE 조건을 빼먹어도 남의 데이터가 보일 일이 없습니다.
- 관리 용이성: 특정 고객사의 데이터만 백업하거나 복구하기가 매우 쉽습니다.
- 단점:
- 비용 폭발: DB 인스턴스 비용이 고객 수에 비례해서 늘어납니다.
- 배포 지옥: DB 스키마를 변경(Migration)하려면 1,000개의 DB에 스크립트를 다 돌려야 합니다.
2. 방식 2: 공유 DB (Shared Database / Discriminator) - "최고의 효율, 보안 위험"
하나의 데이터베이스에 모든 고객사의 데이터를 다 넣고, 테이블마다 tenant_id 컬럼을 추가해서 구분하는 방식입니다. 대부분의 B2C 서비스나 저가형 SaaS가 이 방식을 씁니다.
특징
- 논리적 격리: 물리적으로는 같은 테이블에 있지만, 쿼리할 때 반드시 WHERE tenant_id = ? 조건을 붙여서 구분합니다.
- 경제성: 고객이 100만 명이어도 DB는 1대면 됩니다.
코드 예시 (JPA / Hibernate)
Hibernate 6부터는 @TenantId 어노테이션을 통해 실수 방지를 지원합니다.
Java
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
private String productName;
// 이 필드를 기준으로 자동으로 WHERE 조건이 붙음
@TenantId
@Column(name = "tenant_id", nullable = false)
private String tenantId;
}
// 개발자가 작성하는 쿼리
orderRepository.findAll();
// 실제 실행되는 SQL (Hibernate가 자동으로 붙여줌)
// SELECT * FROM order WHERE tenant_id = 'CURRENT_USER_TENANT'
장단점
- 장점:
- 비용 절감: 인프라 비용이 획기적으로 낮습니다.
- 운영 용이: 스키마 변경 시 한 번만 DDL을 실행하면 모든 고객사에게 반영됩니다.
- 단점:
- 개발자 실수: 만약 개발자가 실수로 tenant_id 조건을 빼먹는 순간, 모든 고객사의 데이터가 유출되는 대형 보안 사고가 터집니다. (Hibernate Filter 등으로 방어 필수)
- 백업 난해: 특정 고객사의 데이터만 따로 백업하거나 삭제하기가 매우 까다롭습니다.
3. 실무 비교: 언제 무엇을 쓰는가?
| 구분 | Database per Tenant (DB 분리) | Shared Database (공유 DB) |
| 격리 수준 | 물리적 격리 (안전) | 논리적 격리 (위험 존재) |
| 인프라 비용 | 높음 (고객 수 비례) | 낮음 (고정 비용) |
| 확장성 | DB 개수 제한에 걸릴 수 있음 | 단일 DB 성능 한계에 걸릴 수 있음 |
| 구현 난이도 | 인프라 설정 복잡 (Routing) | 애플리케이션 코드 복잡 (Filter) |
| 추천 상황 | 금융/의료 B2B, 엔터프라이즈급 고가 SaaS | 일반 B2C, 무료/저가형 SaaS, 스타트업 |
결론
"고객사가 보안에 민감한 대기업이고, 돈을 많이 낸다"면 주저 없이 DB 분리(Database per Tenant) 방식을 선택하세요. 비용보다 신뢰가 중요합니다.
하지만 "수만 명의 사용자를 받는 일반적인 웹 서비스"라면 공유 DB(Shared Database) 방식이 정답입니다. 대신, 개발자의 실수를 막기 위해 Hibernate Filter나 Multi-tenancy Library를 사용하여 애플리케이션 레벨에서 철벽 방어를 해야 합니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 메시징 패턴(Messaging Pattern) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| 이벤트 소싱 패턴(Event Sourcing Pattern) 완벽 정리 (0) | 2025.12.07 |
| API 버저닝 패턴(API Versioning Pattern) 완벽 정리 (0) | 2025.12.07 |
| 인증 상태 관리 패턴: 세션(Session) vs 토큰(JWT) 완벽 비교 (0) | 2025.12.07 |
| 샤딩 패턴(Sharding Pattern) 완벽 정리 (0) | 2025.12.07 |