Introducción básica:
Cadena de conexión: Host=IP;Port=somePort;Username=someUser;Password=somePass;Database=someDb;Maximum Pool Size=100
Mi aplicación web tiene varias docenas de puntos finales disponibles a través de WS y HTTP. Cada uno de estos puntos finales abre una nueva conexión NPGSQL (todos usan la misma cadena de conexión que la anterior), procesa los datos y luego los cierra a través de la declaración de using .
Problema: cuando la aplicación se reinicia para una actualización, normalmente hay de 2 a 3000 usuarios que se vuelven a conectar. Esto generalmente conduce a errores relacionados con el conjunto de conexiones lleno y el rechazo de nuevas conexiones debido a que ya hay demasiados clientes. Sin embargo, una vez que finalmente puede conectarse, generalmente solo usa entre 5 y 10 conexiones en un momento dado.
Pregunta: ¿Es la lógica a continuación la forma correcta de usar la agrupación de conexiones? ¿Con cada punto final creando una nueva conexión NPGSQL usando la misma cadena de conexión especificando un grupo de conexiones de 100?
Parece que el grupo de conexiones a menudo se dispara hasta 100, pero aproximadamente 80/100 de esas conexiones se muestran como inactivas en un visor de base de datos y se niegan nuevas solicitudes de conexión debido al desbordamiento del grupo.
¿Mejor opción? También podría intentar forzar un inicio más "elegante" al permitir que los nuevos usuarios se vuelvan a conectar lentamente, pero no estoy seguro de si la lógica para crear una nueva conexión con cada punto final es correcta.
// DB Connection String - Used for all NPGSQL connections const string connectionStr "Host=IP;Port=somePort;Username=someUser;Password=somePass;Database=someDb;Maximum Pool Size=100"; // Endpoint 1 available via Websocket public async Task someRequest(someClass someArg) { /* Create a new SQL connection for this user's request using the same global connections string */ using var conn = new NpgsqlConnection(connectionStr); conn.Open(); /* Call functions and pass this SQL connection for any queries to process this user request */ somefunction(conn, someArg); anotherFunction(conn, someArg); /* Request processing is done */ /* conn is closed automatically by the "using" statement above */ } // Endpoint 2 available via Websocket public async Task someOtherRequest(someClass someArg) { /* Create a new SQL connection for this user's request using the same global connections string */ using var conn = new NpgsqlConnection(connectionStr); conn.Open(); /* Call functions and pass this SQL connection for any queries to process this user request */ somefunction(conn, someArg); anotherFunction(conn, someArg); /* Request processing is done */ /* conn is closed automatically by the "using" statement above */ } // endpoint3(); // endpoint4(); // endpoint5(); // endpoint6(); // etc.EDITAR: hice el cambio sugerido, cerrando las conexiones y enviándolas de vuelta al grupo durante el procesamiento. Sin embargo, el problema aún persiste en el inicio.
Inicio de la aplicación: 100 conexiones reclamadas para la agrupación. Casi todos ellos están inactivos. La aplicación recibe errores de agotamiento del grupo de conexiones, incluso se procesan pocas o ninguna transacción.
Las transacciones de repente comienzan a agitarse, ¿no está seguro de por qué? ¿Es esto después de algún tipo de tiempo de espera tal vez? Sé que hubo algún tipo de tiempo de espera predeterminado de 300 segundos en la documentación en alguna parte... esto podría coincidir aquí.
Las transacciones se bloquean de nuevo, se reanudan los errores de agotamiento de la agrupación.
Las transacciones aumentan y se reanudan, las solicitudes de los usuarios comienzan a llegar nuevamente.
La aplicación se nivela normalmente.
EDICIÓN 2: este problema de inicio parece estar tomando constantemente 5 minutos desde el inicio para eliminar un punto muerto de transacciones inactivas y comenzar a ejecutar todas las consultas.
Sé que 5 minutos es el valor predeterminado para idle_in_transaction_session_timeout . Sin embargo, intenté ejecutar SET SESSION idle_in_transaction_session_timeout = '30s'; y 10s durante el inicio esta vez y no pareció afectarlo en absoluto.
No estoy seguro de por qué esas 100 conexiones agrupadas estarían inactivas de esa manera en el inicio, tardando 5 minutos en borrarse y permitir que se ejecuten consultas si ese es el caso...
Se libera una conexión al grupo una vez que la cierra en su código. Por lo que escribió, lo mantiene abierto durante todo el tiempo de una solicitud, por lo que básicamente 1 usuario = 1 conexión y la agrupación solo se usa como una sala de timeout (configuración de tiempo de espera, 15 segundos por defecto). Abra o cierre la conexión cada vez que necesite acceder a la base de datos, de modo que la conexión se devuelva al grupo y pueda ser utilizada por otro usuario cuando pase tiempo en el código .net.
Ejemplo, en pseudocódigo:
Enter function Do some computations in .net, like input validation Open connection (grab it from the pool) Fetch info#1 Close connection (return it to the pool) Do some computations in .net, like ordering the result, computing an age from a date etc Open connection (grab it from the pool) Fetch info #2 Close connection (return it to the pool) Do some computations in .net ReturnHabía olvidado actualizar esta publicación con la información más reciente. Hubo algunas otras optimizaciones internas que hice en el código.
Uno de los más importantes fue simplemente cambiar conn.Open(); para await conn.OpenAsync(); y conn.Close(); a conn.CloseAsync(); .
Todo lo demás que tenía estaba correctamente sincronizado, pero aún había bloqueo de E/S para todas las conexiones nuevas en NPGSQL, lo que provocaba un peor rendimiento con grandes ráfagas.
Un cambio muy obvio, pero ni siquiera pensé en buscar un método asíncrono para abrir y cerrar las conexiones.