active_record_tenanted Gem은 기존 다중 테넌시 솔루션인 apartment의 한계, 특히 Rails 6.1 이후 개선된 연결 관리 기능과의 비호환성 및 Rails의 다른 구성 요소와의 통합 부족 문제를 해결하고자 개발되었습니다.
다중 테넌시 접근 방식 비교
- 데이터 혼합(Co-mingled Data): 하나의 데이터베이스에 모든 고객 데이터를 저장하고, 모든 테이블에
tenant_id컬럼을 추가하여 쿼리 시 필터링하는 방식입니다.access_tenantGem이 이 방식을 사용하며, 개발자가 모든 쿼리에tenant_id를 명시해야 하는 부담과 데이터 유출의 잠재적 위험이 있습니다. - 별도 데이터베이스(Separate Databases): 각 테넌트별로 물리적으로 분리된 데이터베이스(SQLite 파일, PostgreSQL 스키마, MySQL 데이터베이스 등)를 사용하는 방식입니다.
active_record_tenantedGem은 이 방식을 채택하여 연결 자체가 특정 테넌트 데이터에만 접근하도록 하여 강력한 데이터 격리 및 보안을 제공합니다.
active_record_tenanted Gem의 핵심 기능
- 스레드 안전한 연결 관리: Rails 6.1에서 도입된 수평 샤딩 및 다중 데이터베이스 지원 기능을 활용하여 스레드 안전성을 보장합니다. 기존
apartmentGem이 스레드 안전성 문제를 겪었던 것과 달리, 이 Gem은 Rails의 최신 연결 관리 아키텍처 위에서 동작합니다. - 동적 테넌트 생성 및 관리: 애플리케이션 부팅 시 모든 테넌트를 정적으로 선언할 필요 없이, 테넌트가 처음 참조될 때 동적으로 데이터베이스 연결 풀을 생성합니다. 이는 수많은 테넌트를 가진 시스템에서 부팅 시간과 메모리 사용량을 크게 줄입니다.
- 간단한 설정:
database.yml파일에%tenant형식 지정자를 사용하여 동적으로 데이터베이스 경로를 지정하고,ApplicationRecord클래스에tenanted를 추가하는 것만으로 다중 테넌시를 활성화할 수 있습니다. - 포괄적인 Rails 통합:
- Rails 레코드 지원: Action Text, Active Storage, Action Mailbox 등 Rails 내부 라이브러리가 생성하는 모델들도
subtenant_of기능을 통해 테넌트 데이터베이스를 사용하도록 합니다. 이를 통해 테넌트 내에서 모델 간의 조인(join)이 가능해집니다. - 캐시 키 관리:
Rails.cache의 프래그먼트 캐시 키에 테넌트 ID를 추가하여 캐시 충돌을 방지하고, 테넌트 간 데이터 노출을 막습니다. - Active Storage Blob 경로: S3 또는 디스크 스토어에 저장되는 Active Storage Blob의 키에 테넌트 ID를 접두사로 추가하여 각 테넌트의 파일이 별도의 폴더에 저장되도록 합니다.
- Active Job 지원: Job 인스턴스 생성 시 현재 테넌트 컨텍스트를 저장하고, Job이 실행될 때 해당 테넌트 컨텍스트를 복원하여 올바른 데이터베이스에서 작업이 수행되도록 합니다.
- Global ID 통합: Rails 객체 직렬화에 사용되는 Global ID에 테넌트 ID를 포함시켜, 잘못된 테넌트 컨텍스트에서 객체를 로드하거나 조작하는 것을 방지하는 강력한 안전 장치를 제공합니다.
- Action Cable:
tenant_resolverproc을 사용하여 Action Cable 연결에도 테넌트 컨텍스트를 적용합니다. - 로그 및 테스트: SQL 쿼리 로그에 테넌트 정보를 포함하고, 테스트 프레임워크도 테넌시를 인지하도록 설계되어 개발 편의성을 높입니다.
- Rails 레코드 지원: Action Text, Active Storage, Action Mailbox 등 Rails 내부 라이브러리가 생성하는 모델들도
안전 메커니즘
- 올바른 테넌트 컨텍스트(
with_tenant블록) 없이 쿼리를 시도하면 예외를 발생시킵니다. - 잘못된 테넌트 컨텍스트에서 데이터를 업데이트하거나 연관 관계를 호출하려 하면 오류를 발생시켜 데이터 유출을 강력히 방지합니다.
- 테넌트 간의 임의적인 전환을 기본적으로 금지하여 데이터 무결성을 유지합니다.