Multi-Tenant Rails와 active_record_tenanted Gem 소개

Mike Dalessio - Multi-Tenant Rails: Everybody Gets a Database!

작성자
Ruby on Rails Youtube
발행일
2025년 09월 16일

핵심 요약

  • 1 37signals가 오픈소스화한 `active_record_tenanted` Gem은 Rails 애플리케이션에 각 고객별 데이터베이스를 제공하여 강력한 다중 테넌시를 구현합니다.
  • 2 이 Gem은 Rails 6.1+의 다중 데이터베이스 지원을 활용하여 스레드 안전성을 보장하며, 동적 테넌트 생성을 통해 기존 솔루션의 한계를 극복합니다.
  • 3 Active Record뿐만 아니라 Action Text, Active Storage, Active Job 등 Rails의 모든 핵심 구성 요소와 긴밀하게 통합되어 개발자가 테넌시를 의식하지 않고 작업할 수 있도록 지원합니다.

도입

이 발표는 Rails 애플리케이션에서 다중 테넌시(Multi-tenancy)를 구현하는 새로운 접근 방식과 이를 지원하는 `active_record_tenanted` Gem을 소개합니다. 다중 테넌시란 하나의 애플리케이션 인스턴스가 여러 고객(테넌트)에게 서비스를 제공하면서 각 고객의 데이터를 안전하게 격리하는 것을 의미합니다. 기존 Rails 생태계에는 `access_tenant`와 같이 데이터를 한 데이터베이스에 혼합(co-mingled data)하여 테넌트 ID로 필터링하는 방식과 `apartment`처럼 각 테넌트별로 별도의 데이터베이스를 사용하는 방식이 존재했습니다. 발표자는 이 중 "별도의 데이터베이스" 방식이 더 강력한 데이터 격리 및 보안을 제공한다고 강조하며, 37signals에서 개발하고 오픈소스화한 `active_record_tenanted` Gem이 이러한 요구사항을 현대적인 Rails 환경에 맞춰 어떻게 충족시키는지 설명합니다.

active_record_tenanted Gem은 기존 다중 테넌시 솔루션인 apartment의 한계, 특히 Rails 6.1 이후 개선된 연결 관리 기능과의 비호환성 및 Rails의 다른 구성 요소와의 통합 부족 문제를 해결하고자 개발되었습니다.

다중 테넌시 접근 방식 비교

  • 데이터 혼합(Co-mingled Data): 하나의 데이터베이스에 모든 고객 데이터를 저장하고, 모든 테이블에 tenant_id 컬럼을 추가하여 쿼리 시 필터링하는 방식입니다. access_tenant Gem이 이 방식을 사용하며, 개발자가 모든 쿼리에 tenant_id를 명시해야 하는 부담과 데이터 유출의 잠재적 위험이 있습니다.
  • 별도 데이터베이스(Separate Databases): 각 테넌트별로 물리적으로 분리된 데이터베이스(SQLite 파일, PostgreSQL 스키마, MySQL 데이터베이스 등)를 사용하는 방식입니다. active_record_tenanted Gem은 이 방식을 채택하여 연결 자체가 특정 테넌트 데이터에만 접근하도록 하여 강력한 데이터 격리 및 보안을 제공합니다.

active_record_tenanted Gem의 핵심 기능

  • 스레드 안전한 연결 관리: Rails 6.1에서 도입된 수평 샤딩 및 다중 데이터베이스 지원 기능을 활용하여 스레드 안전성을 보장합니다. 기존 apartment Gem이 스레드 안전성 문제를 겪었던 것과 달리, 이 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_resolver proc을 사용하여 Action Cable 연결에도 테넌트 컨텍스트를 적용합니다.
    • 로그 및 테스트: SQL 쿼리 로그에 테넌트 정보를 포함하고, 테스트 프레임워크도 테넌시를 인지하도록 설계되어 개발 편의성을 높입니다.

안전 메커니즘

  • 올바른 테넌트 컨텍스트(with_tenant 블록) 없이 쿼리를 시도하면 예외를 발생시킵니다.
  • 잘못된 테넌트 컨텍스트에서 데이터를 업데이트하거나 연관 관계를 호출하려 하면 오류를 발생시켜 데이터 유출을 강력히 방지합니다.
  • 테넌트 간의 임의적인 전환을 기본적으로 금지하여 데이터 무결성을 유지합니다.

결론

`active_record_tenanted` Gem은 Rails 애플리케이션에 다중 테넌시를 구현하는 데 있어 강력하고 안전하며 개발자 친화적인 솔루션을 제공합니다. 이 Gem은 Rails의 최신 기능을 활용하여 스레드 안전한 연결 관리를 구현하고, Active Record를 넘어 Rails의 다양한 구성 요소와 긴밀하게 통합됨으로써 개발자가 테넌시 관련 복잡성을 크게 줄일 수 있도록 돕습니다. 이를 통해 개발팀은 마치 일반적인 Rails 애플리케이션을 개발하는 것처럼 생산성을 유지하면서도, 각 고객의 데이터를 완벽하게 격리하고 보호하는 시스템을 구축할 수 있습니다. 향후 연결 풀 관리 최적화 및 데이터베이스 태스크 지원 강화 등을 통해 더욱 완성도 높은 솔루션으로 발전할 것으로 기대됩니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!