Seria possível ter vários pools de conexão de banco de dados nos trilhos para alternar entre eles?

12

Um pouco de fundo

Uso a jóia Apartment para executar um aplicativo de multilocação por anos. Agora, recentemente, chegou a necessidade de dimensionar o banco de dados em hosts separados, o servidor db simplesmente não aguenta mais (as leituras e gravações estão ficando demais) - e sim, eu dimensionei o hardware ao máximo (dedicado hardware, 64 núcleos, 12 unidades Nvm-e no RAID 10, 384 GB de RAM etc.).

Eu estava pensando em fazer isso por inquilino (1 inquilino = 1 configuração / pool de conexão com o banco de dados), pois seria uma maneira "simples" e eficiente de obter até number-of-tenantsmais vezes mais capacidade sem fazer muitas alterações no código do aplicativo.

Agora, estou executando o rails 4.2 atm., Em breve atualizando para o 5.2. Eu posso ver que o Rails 6 adiciona suporte para definições de conexão por modelo, no entanto, isso não é realmente o que eu preciso, pois tenho um esquema de banco de dados completamente espelhado para cada um dos meus 20 inquilinos. Normalmente eu alterno "banco de dados" por solicitação (no middleware) ou por trabalho em segundo plano (sidekiq middleware), no entanto, isso atualmente é trivial e tratado pela jóia Apartment, pois apenas define o search_pathPostgresql e realmente não altera a conexão real. Ao mudar para uma estratégia de hospedagem por inquilino, precisarei mudar toda a conexão por solicitação.

Questões:

  1. Entendo que eu poderia fazer um trabalho ActiveRecord::Base.establish_connection(config)por solicitação / plano de fundo - no entanto, como também entendo, isso aciona um handshake de conexão de banco de dados totalmente novo a ser feito e um novo pool de banco de dados a ser gerado nos trilhos - certo? Eu acho que seria um suicídio de desempenho fazer esse tipo de sobrecarga em cada solicitação do meu aplicativo.
  2. Portanto, eu estou querendo saber se alguém pode ver a opção com trilhos, por exemplo, pré-estabelecendo várias (total de 20) conexões / pools de banco de dados desde o início (por exemplo, na inicialização do aplicativo) e depois alternar entre esses pools por solicitação? Para que as conexões db já estejam feitas e prontas para serem usadas.
  3. Tudo isso é apenas uma péssima idéia, e devo procurar uma abordagem diferente? Por exemplo, 1 instância do aplicativo = uma conexão específica com um inquilino específico. Ou alguma outra coisa.
Niels Kristian
fonte
2
Guides.rubyonrails.org/active_record_multiple_databases.html Eu acho que pode ajudá-lo
Alex Golubenko 26/02
11
Você pode estar interessado neste PR no repositório GitHub do Rails que recentemente adicionou exatamente o recurso necessário à masterramificação atual do Rails . A execução do Rails Egde seria uma opção ou back-prting desse recurso para sua versão atual do Rails?
spickermann 03/03
@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endsignifica que o pool será (re) usado, em vez de criar uma conexão totalmente nova toda vez?
Ben

Respostas:

4

Pelo que entendi, existem 4 padrões para aplicativos de multilocação:

1. Modelo dedicado / múltiplos ambientes de produção

Cada instância ou instância de banco de dados hospeda completamente diferentes aplicativos de inquilino e nada é compartilhado entre os inquilinos.

Este é um aplicativo de instância e um banco de dados para 1 inquilino. O desenvolvimento seria fácil como se você atendesse apenas 1 inquilino. Mas será um pesadelo para os devops, se você tiver, digamos, 100 inquilinos.

2. Segregação física de inquilinos

1 aplicativo de instância para todos os inquilinos, mas 1 banco de dados para 1 inquilino. É isso que você está procurando. Você pode usar ActiveRecord::Base.establish_connection(config), ou usar gemas, ou atualizar para o Rails 6, como outro sugere. Veja a resposta para (2) abaixo.

3. Modelo de esquema isolado / Segregações lógicas

Em um esquema isolado, as tabelas de inquilino ou os componentes do banco de dados são agrupados em um esquema lógico ou espaço de nome e separados de outros esquemas de inquilino; no entanto, o esquema é hospedado na mesma instância de banco de dados.

Um aplicativo de instância e um banco de dados para todos os inquilinos, como você faz com a jóia do apartamento.

4. Componente parcialmente isolado

Nesse modelo, os componentes que possuem funcionalidades comuns são compartilhados entre os inquilinos, enquanto os componentes com funções exclusivas ou não relacionadas são isolados. Na camada de dados, dados comuns, como os que identificam os inquilinos, são agrupados ou mantidos em uma única tabela, enquanto os dados específicos do inquilino são isolados na camada da tabela ou da instância.


Quanto ao (1), ActiveRecord::Base.establish_connection(config)não use handshake para db por solicitação, se você usá-lo corretamente. Você pode conferir aqui e ler todos os comentários aqui .

Quanto a (2), se você não quiser usar establish_connection, poderá usar o multiverso gem (funciona para o rails 4.2) ou outras gemas. Ou, como outro sugere, você pode atualizar para o Rails 6.

Edit: Multiverse gem está usando establish_connection. Anexará oe database.ymlcriará uma classe base para que cada subclasse compartilhe a mesma conexão / pool. Basicamente, isso reduz nosso esforço de uso establish_connection.

Quanto a (3), a resposta:

Se você não possui tantos inquilinos e seu aplicativo é bastante complexo, sugiro que você use o padrão Modelo Dedicado. Então você escolhe uma instância de aplicativo = uma conexão específica com um inquilino específico. Você não precisa tornar seus aplicativos mais complexos adicionando várias conexões ao banco de dados.

Mas se você tiver muitos inquilinos, sugiro que você use a Segregação física de inquilinos ou o componente parcialmente isolado depende do seu processo de negócios.

De qualquer forma, você deve atualizar / reescrever seu aplicativo para estar em conformidade com a nova arquitetura.

KSD Putra
fonte
Oi, obrigado pela resposta. Vou precisar de um tempo para testar a sugestão antes de poder recompensar uma das respostas pela recompensa, se forem boas soluções.
Niels Kristian
Eu tenho algumas perguntas em relação a 1 e 2. 1: Não sei se entendi suas referências. É o que você está dizendo, que eu posso chamar .establish_connection (config) sem fazer db handshake / recriar a pesquisa db? Nesse caso, não tenho certeza de como os dois links explicam isso? 2: Para o multiverso, esse não é um banco de dados por modelo alternando em vez de um comutador db inteiro para o aplicativo inteiro? Eu sinto que a documentação deles é bastante vaga
Niels Kristian
Eu acho que tenho um mal-entendido. Você se importa em elaborar essas frases? Eu entendo que eu poderia fazer um ActiveRecord :: Base.establish_connection (config) por solicitação de trabalho / background - no entanto, como eu também entendo, que desencadeia uma inteiramente nova handshake conexão de dados a serem feitas e uma nova piscina db para desovar em trilhos É sugere que uma solicitação crie um pool de banco de dados?
KSD Putra 9/03
Quero dizer: (1) Estou preocupado com a sobrecarga de desempenho / rede ao precisar chamar ActiveRecord :: Base.establish_connection (config) em cada solicitação, apenas para alternar entre os diferentes bancos de dados / países
Niels Kristian
Você não precisa se preocupar com a sobrecarga. Agora, se você usa um único banco de dados, você tem um pool de conexões (você pode verificar o link sobre a conexão na resposta de (1) acima). Se você usar establish_connectionum modelo como este: class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); ende dizer que possui 5 modelo, cria 5 conjuntos de conexões para o DB_SECOND_TENANT. E cada piscina é tratada igualmente. Portanto, você não cria um pool por solicitação, mas por establish_connection.
KSD Putra
3

Pelo que entendi, (2) deve ser possível com a comutação de conexão manual no Rails 6.

claasz
fonte
Obrigado, no entanto, isso parece muito longe do meu caso de uso. Isso implicaria reescrever o aplicativo inteiro para usar esse procedimento em qualquer lugar.
Niels Kristian
3

Apenas alguns dias atrás, o sharding horizontal foi adicionado à masterramificação do Ruby on Rails no GitHub. Atualmente, esse recurso não é lançado oficialmente, mas dependendo da versão do Rails do seu aplicativo, você pode considerar o uso do Rails masteradicionando isso ao seu Gemfile:

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

Com esse novo recurso, você pode tirar proveito do pool de conexão de banco de dados do Rails e alternar o banco de dados com base nas condições.

Não usei esse novo recurso, mas parece bem direto:

# 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

Você não adicionou muitos detalhes sobre como determinar o número do inquilino ou como a autorização é feita no seu aplicativo. Mas eu gostaria de tentar determinar o número inquilino o mais cedo possível na application_controllerem uma around_action. Algo assim pode ser um ponto de partida:

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
spickermann
fonte
Isso faria o mesmo sentido para voltar à conexão padrão também nesse caso? github.com/influitive/apartment#middleware-considerations
Ben
11
Depois de sair do ActiveRecord::Base.connected_to ... dobloco, ele usaria a conexão padrão novamente.
spickermann 5/03
@spickermann eu estava lendo ab esta jóia, não é apenas para rails6?
7urkm3n 6/03
@ 7urkm3n Está incluído no masterramo atual do Rails .
spickermann 7/03
Oi, obrigado pela resposta. Vou precisar de um tempo para testar a sugestão antes de poder recompensar uma das respostas pela recompensa, se forem boas soluções.
Niels Kristian