Elegir DB pool_size para una aplicación Flask-SQLAlchemy que se ejecuta en Gunicorn

8

Tengo una aplicación Flask-SQLAlchmey ejecutándose en Gunicorn conectada a una base de datos PostgreSQL, y tengo problemas para averiguar cuál pool_sizedebería ser el valor y cuántas conexiones de base de datos debería esperar.

Esta es mi comprensión de cómo funcionan las cosas:

  • Los procesos en Python 3.7 NO comparten memoria
  • Cada trabajador de Gunicorn es su propio proceso.
  • Por lo tanto, cada trabajador de Gunicorn obtendrá su propia copia del grupo de conexiones de la base de datos y no se compartirá con ningún otro trabajador
  • Los hilos en Python comparten memoria
  • Por lo tanto, cualquier subproceso dentro de un trabajador de Gunicorn compartirá un grupo de conexiones de base de datos

¿Es eso correcto hasta ahora? Si eso es correcto, entonces para una aplicación Flask sincrónica que se ejecuta en Gunicorn:

  • ¿Es el número máximo de conexiones de base de datos = (número de trabajadores) * (número de subprocesos por trabajador)?
  • Y dentro de un trabajador, ¿usará alguna vez más conexiones de un grupo de las que hay trabajadores?

¿Hay alguna razón por la cual pool_sizedebería ser mayor que la cantidad de hilos? Entonces, para una aplicación gunicorn lanzada con gunicorn --workers=5 --threads=2 main:appdebería pool_sizeser 2? Y si solo estoy usando trabajadores, y no estoy usando hilos, ¿hay alguna razón para tener pool_sizemás de 1?

Joshmaker
fuente

Respuestas:

3

Agregando mis 2 centavos. Su comprensión es correcta, pero algunos pensamientos a tener en cuenta:

  • en caso de que su aplicación esté vinculada a IO (por ejemplo, hablando con la base de datos) realmente desea tener más de 1 hilo. De lo contrario, su CPU nunca alcanzará el 100% de la utilización. Necesita experimentar con el número de subprocesos para obtener la cantidad correcta, generalmente con la herramienta de prueba de carga y la comparación de solicitudes por segundo y la utilización de la CPU.

  • Teniendo en cuenta la relación entre el número de trabajadores y las conexiones, puede ver que al cambiar el número de trabajadores, deberá ajustar el tamaño máximo de la agrupación. Esto puede ser fácil de olvidar, por lo que tal vez una buena idea es establecer el tamaño del grupo un poco por encima del número de trabajadores, por ejemplo, el doble de ese número.

  • postgresql crea un proceso por conexión y podría no escalar bien, cuando tendrá muchos procesos gunicorn. Iría con un grupo de conexiones que se encuentra entre su aplicación y la base de datos (supongo que pgbouncer es el más popular).

matino
fuente
3

Solo agrego algo de mi propia experiencia reciente a la respuesta de @ matino . Las aplicaciones WSGI también pueden beneficiarse de los trabajadores asíncronos. Agregaré algunos puntos sobre async workersy connection poolsaquí.

Recientemente enfrentamos algunos problemas similares en nuestra producción. Nuestro tráfico se disparó en 1-2 días y todas las solicitudes se estaban obstruyendo por alguna razón. Estábamos usando gunicorn con geventtrabajadores asíncronos para nuestra djangoaplicación. Las conexiones psql resultaron ser la razón por la cual muchas de las solicitudes se estancaron (y eventualmente se agotaron).

El número sugerido de solicitudes concurrentes es (2*CPU)+1. Entonces, en un escenario de sincronización, sus cálculos serían como:(workers_num * threads_num) <= (2 * cores_num) + 1

Y obtendrá (workers_num * threads_num)conexiones máximas a su base de datos. (por ejemplo, todas las solicitudes tienen consultas db). Por lo tanto, deberá establecer su pool_sizeconfiguración de psql en algo mayor que este número. Pero cuando usa trabajadores asíncronos, los cálculos serán un poco diferentes. Mira este comando gunicorn:

gunicorn --worker-class=gevent --worker-connections=1000 --workers=3 django:app

En este caso, el número máximo de solicitudes simultáneas puede llegar hasta las 3000solicitudes. Por lo tanto, debe configurarlo pool_sizeen algo mayor que 3000. Si su aplicación está vinculada a IO, obtendrá un mejor rendimiento con los trabajadores asíncronos. De esta manera, podrá utilizar su CPU de manera más eficiente.

Y sobre la agrupación de conexiones, cuando utiliza una solución como PgBouncer, se está deshaciendo de la sobrecarga de abrir y cerrar conexiones todo el tiempo. Por lo tanto, no afectará su decisión sobre la configuración de su pool_size. Los efectos pueden no ser notables en los tráficos bajos, pero será una necesidad para manejar mayores tasas de tráfico.

nima
fuente
2

Diría que tu comprensión es bastante buena. Los hilos dentro de un único trabajador WSGI compartirán un grupo de conexiones; Entonces, teóricamente, el número máximo de conexiones de bases de datos es (number of workers) * Ndónde N = pool_size + max_overflow. (No estoy seguro de lo que Flask-SQLAlchemy establece max_overflow, pero es una parte importante de la ecuación aquí; consulte la documentación de QueuePool para saber qué significa).

En la práctica, si solo utiliza la sesión de ámbito de subproceso que le proporcionó Flask-SQLAlchemy, tendrá un máximo de una conexión por subproceso; así que si su recuento de hilos es menor que Nsu límite superior, de hecho será (number of workers) * (number of threads per worker).

Anthony Carapetis
fuente