[ruby-on-rails] 레일간에 여러 데이터베이스 연결 풀을 사용하여 전환 할 수 있습니까?

작은 배경

나는 수년간 멀티 테넌시 앱을 실행하기 위해 Apartment gem 을 사용해 왔습니다. 이제는 데이터베이스를 별도의 호스트로 확장해야 할 필요성에 도달했으며 DB 서버는 더 이상 유지할 수 없습니다 (읽기와 쓰기가 너무 많이 걸리는 경우). 그렇습니다. 하드웨어를 최대로 확장했습니다 (전용 하드웨어, 64 코어, RAID 10의 12 Nvm-e 드라이브, 384Gb 램 등).

이 테넌트 별 (1 테넌트 = 1 데이터베이스 연결 구성 / 풀)을 수행하는 것을 고려하고 number-of-tenants있었습니다. 이는 애플리케이션 코드 변경 작업을 수행하지 않고도 용량을 최대로 늘릴 수있는 “간단하고”효율적인 방법이기 때문 입니다.

이제 레일 4.2 atm.을 실행 중이며 곧 5.2로 업그레이드하고 있습니다. Rails 6은 모델 별 연결 정의에 대한 지원을 추가하지만 20 개 테넌트 각각에 대해 완전히 미러링 된 데이터베이스 스키마를 가지고 있기 때문에 실제로 필요한 것은 아닙니다. 일반적으로 요청 (미들웨어) 또는 백그라운드 작업 (sidekiq 미들웨어) 당 “데이터베이스”를 전환하지만, 이는 search_pathPostgresql에서 설정 하고 실제 연결을 실제로 변경하지 않기 때문에 현재는 사소하고 처리 되지 않습니다. 테넌트 호스팅 전략으로 전환 할 때 요청마다 전체 연결을 전환해야합니다.

질문 :

  1. ActiveRecord::Base.establish_connection(config)요청 / 백그라운드 작업을 수행 할 수 있다는 것을 알고 있습니다. 그러나 또한 완전히 새로운 데이터베이스 연결 핸드 셰이크가 발생하고 새로운 DB 풀이 레일에 생성됩니다. 내 응용 프로그램에 대한 모든 단일 요청에서 이러한 종류의 오버 헤드를 만드는 성능 자살이 될 것입니다.
  2. 따라서 누구나 처음부터 (예 : 응용 프로그램 부팅시) 여러 (총 20 개의) 데이터베이스 연결 / 풀을 사전 설정 한 레일을 사용하여 옵션을 볼 수 있는지 궁금한 경우 요청 당 해당 풀 사이를 전환하면됩니까? 따라서 db 연결이 이미 작성되었으며 사용할 준비가되었습니다.
  3. 이 모든 것이 나쁜 나쁜 생각일까요? 대신 다른 접근법을 찾아야합니까? 예 : 하나의 앱 인스턴스 = 하나의 특정 테넌트에 대한 하나의 특정 연결. 또는 다른 것.


답변

내가 이해하는 것처럼 멀티 테넌시 앱에는 4 가지 패턴이 있습니다.

1. 전용 모델 / 다중 프로덕션 환경

각 인스턴스 또는 데이터베이스 인스턴스는 완전히 다른 테넌트 애플리케이션을 호스팅하며 테넌트간에 공유되는 것은 없습니다.

1 명의 테넌트를위한 1 개의 인스턴스 앱과 1 개의 데이터베이스입니다. 1 명의 테넌트 만 서비스하는 것처럼 개발이 쉬울 것입니다. 그러나 100 명의 임차인이 있다면 devops에게는 악몽이 될 것입니다.

2. 임차인의 물리적 분리

모든 테넌트 용 인스턴스 앱 1 개, 테넌트 1 개용 데이터베이스 1 개 이것은 당신이 찾고있는 것입니다. 당신은 사용할 수 있습니다 ActiveRecord::Base.establish_connection(config), 또는 기타 알 수 있듯이 레일 6 보석, 또는 업데이트를 사용. 아래 (2)에 대한 답변을 참조하십시오.

3. 분리 된 스키마 모델 / 논리적 분리

격리 된 스키마에서 테넌트 테이블 또는 데이터베이스 구성 요소는 논리적 스키마 또는 네임 스페이스로 그룹화되고 다른 테넌트 스키마와 분리되지만 스키마는 동일한 데이터베이스 인스턴스에서 호스팅됩니다.

아파트 보석과 마찬가지로 모든 테넌트에 대한 1 개의 인스턴스 앱과 1 개의 데이터베이스.

4. 부분적으로 고립 된 구성 요소

이 모델에서 공통 기능을 가진 구성 요소는 테넌트간에 공유되며 고유하거나 관련없는 기능을 가진 구성 요소는 격리됩니다. 데이터 계층에서 테넌트를 식별하는 데이터와 같은 공통 데이터는 단일 테이블로 그룹화되거나 유지되는 반면 테넌트 특정 데이터는 테이블 또는 인스턴스 계층에서 격리됩니다.


(1) ActiveRecord::Base.establish_connection(config)은 요청에 따라 DB별로 핸드 쉐이킹하지 마십시오. 여기에서 확인 하고 모든 의견을 읽을 수 있습니다 .

(2)와 같이 사용하지 않으려면 establish_connectiongem multiverse (rails 4.2에서 작동) 또는 다른 gem을 사용할 수 있습니다. 또는 다른 제안처럼 Rails 6으로 업데이트 할 수 있습니다.

편집 : Multiverse gem이 사용하고 establish_connection있습니다. 를 추가하고 database.yml각 서브 클래스가 동일한 연결 / 풀을 공유하도록 기본 클래스를 작성합니다. 기본적으로 사용 노력이 줄어 듭니다 establish_connection.

(3)에 대한 답은 다음과 같습니다.

테넌트가 많지 않고 응용 프로그램이 매우 복잡한 경우 전용 모델 패턴을 사용하는 것이 좋습니다. 따라서 하나의 앱 인스턴스 = 하나의 특정 테넌트에 대한 하나의 특정 연결로 이동합니다. 여러 데이터베이스 연결을 추가하여 앱을 더 복잡하게 만들 필요는 없습니다.

그러나 테넌트가 많은 경우 테넌트의 물리적 분리 또는 부분적으로 격리 된 구성 요소를 사용하는 것이 비즈니스 프로세스에 따라 다릅니다.

어느 쪽이든, 새로운 아키텍처를 준수하려면 애플리케이션을 업데이트 / 다시 작성해야합니다.


답변

내가 이해 한 바에 따르면 (2) Rails 6의 수동 연결 전환 을 통해 가능해야합니다 .


답변

며칠 전에 GitHub의 Ruby on Rails 지점에 수평 샤딩 이 추가되었습니다 master. 현재이 기능은 공식적으로 출시되지는 않지만 애플리케이션의 Rails 버전에 따라 master다음을 추가 하여 Rails 사용을 고려할 수 있습니다 Gemfile.

gem "rails", github: "rails/rails", branch: "master"

이 새로운 기능을 사용하면 Rails의 데이터베이스 연결 풀을 활용하고 조건에 따라 데이터베이스를 전환 할 수 있습니다.

이 새로운 기능을 사용하지는 않았지만 매우 간단합니다.

# in your config/database.yml
production:
  primary:
    database: my_database
    # other config: user, password, etc
  primary_tenant_1:
    database: tenant_1_database
    # other config: user, password, etc

# in your controller for example when updating a tenant
ActiveRecord::Base.connected_to(shard: "primary_tenant_#{tenant.database_shard_number}") do
  tenant.save
end

테넌트 번호를 결정하는 방법 또는 응용 프로그램에서 권한 부여가 수행되는 방법에 대해서는 자세히 설명하지 않았습니다. 그러나에서 가능한 한 빨리 임차인 번호를 결정하려고 application_controller합니다 around_action. 이와 같은 것이 시작점이 될 수 있습니다.

around_filter :determine_database_connection

private

def determine_database_connection
  # assuming you have a method to determine the current_tenant and that tenant
  # has a method that returns the number of the shard to use or even the 
  # full shard identifier
  shard = current_tenant.database_shard # returns for example `:primary_tenant_1` 

  ActiveRecord::Base.connected_to(shard: shard) do
    yield
  end
end


답변