Tenemos una situación en la que tengo que lidiar con una afluencia masiva de eventos que llegan a nuestro servidor, en promedio, alrededor de 1000 eventos por segundo (el pico podría ser de ~ 2000).
El problema
Nuestro sistema está alojado en Heroku y utiliza un Heroku Postgres DB , que permite un máximo de 500 conexiones DB. Utilizamos la agrupación de conexiones para conectar desde el servidor a la base de datos.
Los eventos llegan más rápido de lo que la agrupación de conexiones de DB puede manejar
El problema que tenemos es que los eventos son más rápidos de lo que puede manejar el grupo de conexiones. En el momento en que una conexión finaliza el viaje de ida y vuelta de la red desde el servidor a la base de datos, para que pueda volver a liberarse en la agrupación, entran más de n
de eventos adicionales.
Eventualmente, los eventos se acumulan, en espera de ser guardados y debido a que no hay conexiones disponibles en el grupo, expiran y todo el sistema se vuelve inoperante.
Hemos resuelto la emergencia al emitir los eventos ofensivos de alta frecuencia a un ritmo más lento de los clientes, pero aún queremos saber cómo manejar estos escenarios en caso de que necesitemos manejar esos eventos de alta frecuencia.
Restricciones
Otros clientes pueden querer leer eventos al mismo tiempo
Otros clientes solicitan continuamente leer todos los eventos con una clave en particular, incluso si aún no están guardados en la base de datos.
Un cliente puede consultar GET api/v1/events?clientId=1
y obtener todos los eventos enviados por el cliente 1, incluso si esos eventos todavía no se han guardado en la base de datos.
¿Hay ejemplos de "aula" sobre cómo lidiar con esto?
Posibles soluciones
Encolar los eventos en nuestro servidor
Podríamos poner en cola los eventos en el servidor (con la cola teniendo una concurrencia máxima de 400 para que el grupo de conexiones no se agote).
Esto es mala idea porque:
- Se consumirá la memoria del servidor disponible. Los eventos en cola apilados consumirán enormes cantidades de RAM.
- Nuestros servidores se reinician una vez cada 24 horas . Este es un límite máximo impuesto por Heroku. El servidor puede reiniciarse mientras los eventos se ponen en cola, lo que hace que perdamos los eventos en cola.
- Introduce el estado en el servidor, dañando así la escalabilidad. Si tenemos una configuración de varios servidores y un cliente desea leer todos los eventos en cola + guardados, no sabremos en qué servidor se encuentran los eventos en cola.
Usar una cola de mensajes separada
Supongo que podríamos usar una cola de mensajes, (como RabbitMQ ?), donde bombeamos los mensajes en él y en el otro Al final hay otro servidor que solo se ocupa de guardar los eventos en la base de datos.
No estoy seguro de si las colas de mensajes permiten consultar eventos en cola (que aún no se guardaron), por lo que si otro cliente quiere leer los mensajes de otro cliente, puedo obtener los mensajes guardados de la base de datos y los mensajes pendientes. de la cola y concatenarlos juntos para que pueda enviarlos de vuelta al cliente de solicitud de lectura.
Utilice varias bases de datos, cada una de ellas guarda una parte de los mensajes con un servidor central de coordinación de base de datos para gestionarlos
Otra solución que hemos implementado es utilizar varias bases de datos, con un "coordinador de DB / equilibrador de carga" central. Al recibir un evento se este coordinador elegiría una de las bases de datos para escribir el mensaje. Esto debería permitirnos utilizar múltiples bases de datos Heroku, por lo tanto, aumentar el límite de conexión a 500 x número de bases de datos.
En una consulta de lectura, este coordinador podría emitir consultas SELECT
a cada base de datos, fusionar todos los resultados y enviarlos de vuelta al cliente que solicitó la lectura.
Esto es mala idea porque:
- Esta idea suena como ... ejem ... ¿sobre ingeniería? Sería una pesadilla para gestionar también (copias de seguridad, etc.). Es complicado de construir y mantener, y a menos que sea absolutamente necesario, suena como una violación de KISS .
- Se sacrifica Consistencia . Hacer transacciones a través de múltiples bases de datos es un no-go si vamos con esta idea.