피드로 돌아가기
Dev.toBackend
원문 읽기
Row-Level Security in PostgreSQL: Multi-Tenant Data Isolation for Your SaaS Without a Query Change
PostgreSQL RLS를 JWT 테넌트 컨텍스트와 함께 도입해 쿼리 변경 없이 데이터베이스 계층에서 멀티테넌트 격리 구현
AI 요약
Context
멀티테넌트 SaaS 애플리케이션에서 테넌트 ID 필터링이 애플리케이션 코드에 산재되어 있어 누락 위험과 관리 복잡도 증가, 데이터 누출 가능성 존재.
Technical Solution
- PostgreSQL RLS 정책 설정:
ALTER TABLE orders FORCE ROW LEVEL SECURITY로 테이블 소유자도 정책 적용받도록 강제 - 테넌트 컨텍스트 주입: Kotlin Transaction에서
set_config('app.current_tenant', tenantId, true)로 JWT로부터 추출한 테넌트 ID를 트랜잭션 스코프로 세팅 - 비소유자 애플리케이션 역할 생성:
CREATE ROLE app_user로 테이블 생성자가 아닌 역할로 접속해 RLS 정책 적용 - RLS 정책 정의:
CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.current_tenant')::uuid)로 SELECT, UPDATE, DELETE 모두 격리 적용 - pgTAP를 통한 CI 테스팅: GitHub Actions에서
pg_prove로 테넌트 간 데이터 격리 실패 시 빌드 차단
Impact
단일 행 기본키 조회 시 오버헤드 0.42ms → 0.43ms (2.4% 증가), 필터링된 리스트 조회 시 3.1ms → 3.2ms (3.2% 증가), 3개 테이블 조인 시 8.4ms → 8.9ms (5.9% 증가), 복합 인덱스 (tenant_id, ...)를 사용하면 오버헤드가 노이즈 수준.
Key Takeaway
RLS는 애플리케이션 코드의 누락으로 인한 데이터 격리 실패를 방지하는 유일한 방법으로, 데이터베이스 계층에서 강제하는 보안 경계 설정이 필수이며, FORCE 키워드 생략, PgBouncer 트랜잭션 모드에서 set_config 세 번째 파라미터를 false로 설정하는 실수는 정책 적용 우회로 이어진다.
실천 포인트
Kotlin 백엔드로 멀티테넌트 SaaS를 구축할 때 RLS를 도입하면 모든 쿼리에서 `WHERE tenant_id = ?` 절을 제거할 수 있으며, PgBouncer 트랜잭션 모드 환경에서 `set_config(..., true)` 옵션으로 트랜잭션 스코프 설정을 하고 pgTAP 테스트를 CI에 자동화하면 테넌트 데이터 누출 위험을 구조적으로 차단할 수 있다.