Sarebbe possibile avere più pool di connessione al database nei binari per passare da uno all'altro?


12

Un piccolo sfondo

Da anni utilizzo la gemma dell'appartamento per eseguire un'app multi-tenancy. Ora recentemente è arrivata la necessità di ridimensionare il database in host separati, il server db semplicemente non riesce più a tenere il passo (sia le letture che le scritture stanno diventando troppo) - e sì, ho ridimensionato l'hardware al massimo (dedicato hardware, 64 core, 12 unità Nvm-e nel raid 10, ram da 384 Gb ecc.).

Stavo pensando di fare questo per-tenant (1 tenant = 1 configurazione / pool di connessione al database) in quanto sarebbe stato un modo "semplice" ed efficiente per ottenere fino a number-of-tenantsvolte più capacità senza fare un sacco di modifiche al codice dell'applicazione.

Ora, sto correndo su rotaie 4.2 atm., Presto aggiornando a 5.2. Vedo che rails 6 aggiunge il supporto per le definizioni di connessione per modello, tuttavia non è proprio quello di cui ho bisogno, in quanto ho uno schema di database completamente rispecchiato per ciascuno dei miei 20 tenant. In genere cambio "database" per richiesta (nel middleware) o per lavoro in background (middleware sidekiq), tuttavia questo è attualmente banale e gestito dalla gemma Apartment, in quanto imposta semplicemente search_pathPostgresql e non cambia realmente la connessione effettiva. Quando si passa a una strategia di hosting per tenant, sarà necessario cambiare l'intera connessione per richiesta.

Domande:

  1. Capisco che potrei fare un lavoro ActiveRecord::Base.establish_connection(config)per richiesta / in background - tuttavia, come ho anche capito, che fa scattare una stretta di mano di connessione al database completamente nuova e un nuovo pool db da generare in rotaie - giusto? Immagino che sarebbe un suicidio da prestazione fare quel tipo di spese generali su ogni singola richiesta alla mia domanda.
  2. Mi chiedo quindi se qualcuno può vedere l'opzione con le rotaie, ad esempio, di pre-stabilire connessioni multiple / pool (20 in totale) di database dall'inizio (ad esempio all'avvio dell'applicazione), e quindi passare da un pool all'altro per richiesta? In modo che le connessioni db siano già fatte e pronte per essere utilizzate.
  3. Tutto questo è solo una cattiva idea povera e dovrei invece cercare un approccio diverso? Ad esempio 1 istanza dell'app = una connessione specifica a un tenant specifico. O qualcos'altro.


1
Potresti essere interessato a questo PR nel repository GitHub di Rails che recentemente ha aggiunto esattamente la funzione di cui hai bisogno al masterramo Rails corrente . L'esecuzione di Rails Egde rappresenterebbe un'opzione o eseguirà il backstamping di quella funzione alla tua attuale versione di Rails?
spickermann

@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endsignifica che il pool verrà (ri) utilizzato, invece di creare una connessione completamente nuova ogni volta?
Ben

Risposte:


4

Come ho capito, ci sono 4 modelli per l'app multi-tenancy:

1. Modello dedicato / Ambienti di produzione multipli

Ogni istanza o istanza di database ospita interamente diverse applicazioni tenant e nulla è condiviso tra i tenant.

Questa è 1 app di istanza e 1 database per 1 tenant. Lo sviluppo sarebbe facile come se servissi solo 1 inquilino. Ma sarà un incubo per i devops se hai, diciamo, 100 inquilini.

2. Segregazione fisica degli inquilini

1 app di istanza per tutti i tenant ma 1 database per 1 tenant. Questo è ciò che stai cercando. Puoi usareActiveRecord::Base.establish_connection(config) , o usare gemme, o aggiornare a Rails 6 come altri suggeriscono. Vedere la risposta per (2) di seguito.

3. Modello di schema isolato / Segregazioni logiche

In uno schema isolato, le tabelle dei titolari o i componenti del database sono raggruppati in uno schema logico o in uno spazio dei nomi e separati da altri schemi di titolari, tuttavia lo schema è ospitato nella stessa istanza del database.

1 app di istanza e 1 database per tutti gli inquilini, come fai con gemma dell'appartamento.

4. Componente parzialmente isolato

In questo modello, i componenti con funzionalità comuni sono condivisi tra i tenant mentre i componenti con funzioni uniche o non correlate sono isolati. A livello di dati, i dati comuni come i dati che identificano i titolari sono raggruppati o mantenuti in una singola tabella mentre i dati specifici del titolare sono isolati a livello di tabella o istanza.


Per quanto riguarda (1), ActiveRecord::Base.establish_connection(config)non eseguire l'handshaking su db per richiesta se lo si utilizza correttamente. Puoi controllare qui e leggere tutti i commenti qui .

Per quanto riguarda (2), se non si desidera utilizzare establish_connection, è possibile utilizzare gemma multiverso (funziona per le rotaie 4.2) o altre gemme. Oppure, come altri suggeriscono, puoi aggiornare a Rails 6.

Modifica: la gemma multiverso sta usando establish_connection. Aggiungerà database.ymle creerà una classe base in modo che ogni sottoclasse condivida la stessa connessione / pool. Fondamentalmente, riduce il nostro sforzo di utilizzoestablish_connection .

Per quanto riguarda (3), la risposta:

Se non hai così tanti inquilini e la tua applicazione è piuttosto complessa, ti suggerisco di usare il modello Modello dedicato. Quindi scegli 1 istanza dell'app = una connessione specifica a un tenant specifico. Non è necessario rendere le app più complesse aggiungendo più connessioni al database.

Ma se hai molti inquilini, ti suggerisco di utilizzare la segregazione fisica degli inquilini o un componente parzialmente isolato in base al tuo processo aziendale.

In entrambi i casi, è necessario aggiornare / riscrivere l'applicazione per conformarsi alla nuova architettura.


Ciao grazie per la risposta Avrò bisogno di un po 'di tempo per testare effettivamente il suggerimento prima di poter premiare una delle risposte alla ricompensa se sono buone soluzioni.
Niels Kristian

Ho un paio di domande per quanto riguarda 1 e 2. 1: Non sono sicuro di aver compreso i tuoi riferimenti. Cosa stai dicendo, che posso chiamare .establish_connection (config) senza eseguire l'handshake / ricreare il db poll? In tal caso, non sono sicuro di come i due link lo spieghino? 2: Per il multiverso, non è un cambio di database per modello piuttosto che un intero switch db per l'intera app? Penso che la loro documentazione sia piuttosto vaga
Niels Kristian il

Penso di avere incomprensioni. Ti dispiace elaborare queste frasi? Capisco che avrei potuto fare un ActiveRecord :: Base.establish_connection (config) per richiesta / processo in background - tuttavia, come capisco anche, che innesca una serie completamente nuova stretta di mano di connessione al database a essere disponibili e una nuova piscina db per deporre le uova in rotaie E ' suggerire che una richiesta crei un pool db?
KSD Putra

Voglio dire: (1) Sono preoccupato per il sovraccarico di prestazioni / rete quando devo chiamare ActiveRecord :: Base.establish_connection (config) su ogni richiesta, solo per passare tra i diversi database / paesi
Niels Kristian

Non devi preoccuparti del sovraccarico. Ora, se si utilizza un singolo DB, si dispone di un pool di connessioni (è possibile controllare il collegamento relativo alla connessione nella risposta di (1) sopra). Se si utilizza establish_connectionnel modello come questo class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); end:, e si dice che si dispone di 5 modelli, si creano 5 pool di connessioni al DB_SECOND_TENANT. E ogni pool è trattato allo stesso modo. Quindi, non si crea un pool per richiesta, ma per establish_connection.
KSD Putra il


3

Solo un paio di giorni fa lo sharding orizzontale è stato aggiunto al masterramo di Ruby on Rails su GitHub. Attualmente, questa funzione non è ufficialmente rilasciata, ma a seconda della versione di Rails della tua applicazione potresti prendere in considerazione l'utilizzo di Rails masteraggiungendolo a Gemfile:

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

Con questa nuova funzionalità, è possibile sfruttare il pool di connessioni al database di Rails e cambiare il database in base alle condizioni.

Non ho usato questa nuova funzionalità, ma sembra piuttosto semplice:

# 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

Non sono stati aggiunti molti dettagli su come determinare il numero di inquilino o su come viene eseguita l'autorizzazione nella propria applicazione. Ma proverei a determinare il numero dell'inquilino il prima possibile in application_controllerin un around_action. Qualcosa del genere potrebbe essere un punto di partenza:

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

Avrebbe lo stesso senso tornare anche alla connessione predefinita anche in quel caso? github.com/influitive/apartment#middleware-considerations
Ben

1
Una volta lasciato il ActiveRecord::Base.connected_to ... doblocco, utilizzerà nuovamente la connessione predefinita.
spickermann il

@spickermann stavo leggendo ab questo gioiello, non è solo per rails6?
7urkm3n

@ 7urkm3n È incluso nell'attuale masterramo Rails .
spickermann

Ciao grazie per la risposta Avrò bisogno di un po 'di tempo per testare effettivamente il suggerimento prima di poter premiare una delle risposte alla ricompensa se sono buone soluzioni.
Niels Kristian
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.