¿Sería posible tener varios grupos de conexiones de bases de datos en rieles para cambiar?


12

Un poco de historia

He estado usando la gema del apartamento para ejecutar una aplicación de arrendamiento múltiple durante años. Recientemente, ha llegado la necesidad de escalar la base de datos en hosts separados, el servidor de db simplemente no puede seguir más (tanto las lecturas como las escrituras se están volviendo demasiado), y sí, amplié el hardware al máximo (dedicado hardware, 64 núcleos, 12 unidades Nvm-e en raid 10, 384 Gb de ram, etc.).

Estaba considerando hacer esto por inquilino (1 inquilino = 1 configuración / grupo de conexión de base de datos) ya que esa sería una forma "simple" y eficiente de obtener hasta number-of-tenantsveces más capacidad sin hacer un montón de cambios en el código de la aplicación.

Ahora estoy ejecutando rails 4.2 atm., Pronto actualizo a 5.2. Puedo ver que rails 6 agrega soporte para definiciones de conexión por modelo, sin embargo, eso no es realmente lo que necesito, ya que tengo un esquema de base de datos completamente reflejado para cada uno de mis 20 inquilinos. Por lo general, cambio la "base de datos" por solicitud (en middleware) o por trabajo en segundo plano (middleware sidekiq), sin embargo, esto es actualmente trivial y se maneja en la gema del apartamento, ya que solo establece el search_pathPostgresql y realmente no cambia la conexión real. Al cambiar a una estrategia de alojamiento por inquilino, tendré que cambiar toda la conexión por solicitud.

Preguntas:

  1. Entiendo que podría hacer un trabajo ActiveRecord::Base.establish_connection(config)por solicitud / en segundo plano; sin embargo, como también entiendo, eso desencadena un protocolo de enlace de conexión de base de datos completamente nuevo y un nuevo grupo de bases de datos para generar en rieles, ¿verdad? Supongo que sería un suicidio de rendimiento hacer ese tipo de sobrecarga en cada solicitud a mi solicitud.
  2. Por lo tanto, me pregunto si alguien puede ver la opción con rieles de, por ejemplo, preestablecer múltiples (un total de 20) conexiones / grupos de bases de datos desde el principio (por ejemplo, al iniciar la aplicación), y luego simplemente cambiar entre esos grupos por solicitud. Para que las conexiones db ya estén hechas y listas para ser utilizadas.
  3. ¿Es todo esto solo una pobre y pobre idea, y debería buscar un enfoque diferente? Por ejemplo, 1 instancia de aplicación = una conexión específica a un inquilino específico. O algo mas.


1
Es posible que le interese este PR en el repositorio GitHub de Rails que recientemente agregó exactamente la función que necesita a la masterrama actual de Rails . ¿Ejecutar Rails Egde sería una opción o retroceder esa característica a su versión actual de Rails?
spickermann

@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endsignifica que el grupo será (re) utilizado, en lugar de crear una conexión completamente nueva cada vez?
Ben

Respuestas:


4

Según tengo entendido, hay 4 patrones para la aplicación multicliente:

1. Modelo dedicado / entornos de producción múltiple

Cada instancia o instancia de base de datos aloja completamente una aplicación de inquilino diferente y nada se comparte entre los inquilinos.

Esta es 1 aplicación de instancia y 1 base de datos para 1 inquilino. El desarrollo sería fácil como si sirviera solo a 1 inquilino. Pero será una pesadilla para los devops si tienes, por ejemplo, 100 inquilinos.

2. Segregación física de inquilinos

1 aplicación de instancia para todos los inquilinos pero 1 base de datos para 1 inquilino. Esto es lo que estás buscando. Puede usar ActiveRecord::Base.establish_connection(config), o usar gemas, o actualizar a Rails 6 como otros sugieren. Vea la respuesta para (2) a continuación.

3. Modelo de esquema aislado / segregaciones lógicas

En un esquema aislado, las tablas de inquilinos o los componentes de la base de datos están agrupados bajo un esquema lógico o espacio de nombres y separados de otros esquemas de inquilinos, sin embargo, el esquema está alojado en la misma instancia de la base de datos.

1 aplicación de instancia y 1 base de datos para todos los inquilinos, como lo hace con la joya de apartamentos.

4. Componente parcialmente aislado

En este modelo, los componentes que tienen funcionalidades comunes se comparten entre los inquilinos, mientras que los componentes con funciones únicas o no relacionadas están aislados. En la capa de datos, los datos comunes, como los datos que identifican a los inquilinos, se agrupan o mantienen en una sola tabla, mientras que los datos específicos del inquilino se aíslan en la tabla o en la capa de instancia.


En cuanto a (1), ActiveRecord::Base.establish_connection(config)no apretón de manos a db por solicitud si lo usa correctamente. Puedes consultar aquí y leer todos los comentarios aquí .

En cuanto a (2), si no desea usar establish_connection, puede usar gemas multiverso (funciona para rieles 4.2) u otras gemas. O, como otros sugieren, puede actualizar a Rails 6.

Editar: gema multiverso está utilizando establish_connection. Anexará database.ymly creará una clase base para que cada subclase comparta la misma conexión / grupo. Básicamente, reduce nuestro esfuerzo de uso establish_connection.

En cuanto a (3), la respuesta:

Si no tiene tantos inquilinos y su aplicación es bastante compleja, le sugiero que use un patrón de modelo dedicado. Entonces, elige 1 instancia de aplicación = una conexión específica para un inquilino específico. No tiene que hacer que sus aplicaciones sean más complejas agregando múltiples conexiones de base de datos.

Pero si tiene muchos inquilinos, le sugiero que utilice la segregación física de inquilinos o el componente parcialmente aislado depende de su proceso comercial.

De cualquier manera, debe actualizar / reescribir su aplicación para cumplir con la nueva arquitectura.


Hola gracias por la respuesta Necesitaré un poco de tiempo para probar la sugerencia antes de poder recompensar una de las respuestas si son buenas soluciones.
Niels Kristian

Tengo un par de preguntas con respecto a 1 y 2. 1: No estoy seguro de entender sus referencias. ¿Está diciendo que puedo llamar a .establish_connection (config) sin hacer db handshake / recrear la encuesta de db? En ese caso, no estoy seguro de cómo los dos enlaces explican eso. 2: Para multiverso, ¿no es eso un cambio de base de datos por modelo en lugar de un cambio de base de datos completo para toda la aplicación? Siento que su documentación es bastante vaga
Niels Kristian

Creo que tengo un malentendido. ¿Te importaría elaborar estas oraciones? Yo entiendo que yo podría hacer un ActiveRecord :: Base.establish_connection (config) por solicitud de trabajo / fondo - sin embargo, como también entiendo, que desencadena una nueva base de datos de protocolo de enlace de conexión a realizar y una nueva piscina db para desovar en los carriles Es ¿sugiere que una solicitud cree un grupo de db?
KSD Putra

Quiero decir: (1) Me preocupa el rendimiento / la sobrecarga de la red cuando tengo que llamar a ActiveRecord :: Base.establish_connection (config) en cada solicitud, solo para cambiar entre las diferentes bases de datos / países
Niels Kristian

No tiene que preocuparse por los gastos generales. Ahora, si usa una base de datos única, tiene un grupo de conexiones (puede consultar el enlace sobre la conexión en la respuesta de (1) anterior). Si usa establish_connectionun modelo como este: class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); endy dice que tiene 5 modelos, crea 5 grupos de conexiones para DB_SECOND_TENANT. Y cada grupo se trata por igual. Por lo tanto, no crea un grupo por solicitud, sino por establish_connection.
KSD Putra

3

Por lo que entiendo, (2) debería ser posible con el cambio de conexión manual en Rails 6.


Gracias, sin embargo, esto parece bastante lejos de mi caso de uso. Implicaría reescribir toda la aplicación para usar este procedimiento en todas partes.
Niels Kristian

3

Hace solo un par de días se agregó el fragmentación horizontal a la rama de Ruby on Rails masteren GitHub. Actualmente, esta característica no se ha lanzado oficialmente, pero dependiendo de la versión de Rails de su aplicación, puede considerar usar Rails masteragregando esto a su Gemfile:

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

Con esta nueva característica, puede aprovechar el conjunto de conexiones de la base de datos de Rails y cambiar la base de datos según las condiciones.

No he usado esta nueva función, pero parece bastante sencillo:

# 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

No agregó muchos detalles sobre cómo determina el número de inquilino o cómo se realiza la autorización en su solicitud. Pero me gustaría tratar de determinar el número de empresa tan pronto como sea posible en el application_controlleren una around_action. Algo como esto podría ser un punto 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

¿Tendría el mismo sentido volver a la conexión predeterminada en ese caso también? github.com/influitive/apartment#middleware-considerations
Ben

1
Una vez que abandone el ActiveRecord::Base.connected_to ... dobloque, volverá a utilizar la conexión predeterminada.
Spickermann

@spickermann que estaba leyendo sobre esta joya, ¿no es solo para rails6?
7urkm3n

@ 7urkm3n Está incluido en la masterrama actual de Rails .
Spickermann

Hola gracias por la respuesta Necesitaré un poco de tiempo para probar realmente la sugerencia antes de poder recompensar una de las respuestas si son buenas soluciones.
Niels Kristian
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.