Guardar eventos de alta frecuencia en una base de datos restringida por límite de conexión

Tenemos una situación en la que tengo que lidiar con una afluencia masiva de eventos que llegan a nuestro servidor, en un promedio de 1000 eventos por segundo (el pico podría ser ~ 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 el grupo de conexiones de DB puede manejar

El problema we have is that events come faster than the connection pool can handle. By the time one connection has finished the network roundtrip from the server to the DB, so it can get released back to the pool, more than n additional events come in.

Finalmente, los eventos se acumulan, en espera de que se guarden y debido a que no hay conexiones disponibles en el grupo, expiran y todo el sistema se vuelve inoperante.

Resolvimos 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 continuamente solicitan leer todos los eventos con una clave 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 aún no se han guardado en la base de datos.

¿Hay ejemplos de "clase" sobre cómo lidiar con esto?

Soluciones posibles

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.

Use una cola de mensajes separada

Supongo que podríamos usar una cola de mensajes (como RabbitMQ ?), Donde bombeamos los mensajes en ella y en En el otro extremo 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 administrarlos

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 de Heroku, por lo tanto, aumentar el límite de conexión a 500 x cantidad de bases de datos.

Tras una consulta de lectura, este coordinador podría emitir consultas de 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 infracció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.
12
algunas respuestas lo tienen en cuenta, pero preferiría preguntar: ¿es absolutamente necesario que el 100% de su evento se inserte correctamente en la base de datos? Si es así, ¿cómo maneja el problema actualmente cuando se reinicia su servidor?
agregado el autor Walfrat, fuente
Así que quieres una disponibilidad del 100% pero no síncrona. Entonces mi apuesta sería persistir primero los eventos localmente (por ejemplo, archivos) y exportar los archivos de forma regular (esto podría ser la rodadura de un archivo tmp, para evitar bloqueos cada 30 segundos). Lo básico de este sistema es que puede tener todo al mismo tiempo (sin pérdida, proceso instantáneo, mantener el rendimiento). Necesita saber qué puede eliminar (por ejemplo, sincrónico, o pérdida real del 0%) para obtener lo que necesita. Sin embargo, esto depende del requerimiento de su sistema, que puede que usted no sea el que los solucione.
agregado el autor Walfrat, fuente
Deberías aclarar si esta tasa es máxima o promedio. Si es pico, ¿cuál es el número de eventos por día?
agregado el autor JimmyJames, fuente
"Resolvimos la emergencia al emitir los eventos ofensivos de alta frecuencia de los clientes a un ritmo más lento, pero aún queremos saber cómo manejar estos escenarios en caso de que necesitemos manejar esos eventos de alta frecuencia". No estoy seguro de cómo esto resuelve el problema. Si obtiene más de lo que puede manejar en promedio, ¿no ralentizará a un cliente significa que están continuamente creando una acumulación más profunda de eventos que deben manejarse?
agregado el autor JimmyJames, fuente
¿Dónde está tu cuello de botella? Está mencionando su grupo de conexiones, pero eso solo influye en el paralelismo, no en la velocidad por inserción. Si tiene 500 conexiones y, por ejemplo, 2000QPS, esto debería funcionar bien si cada consulta se completa en 250 ms, lo que es un tiempo muy largo. ¿Por qué está por encima de 15 ms? También tenga en cuenta que al usar un PaaS, está renunciando a importantes oportunidades de optimización, como escalar el hardware de la base de datos o usar réplicas de lectura para reducir la carga en la base de datos principal. Heroku no vale la pena a menos que el despliegue sea tu mayor problema.
agregado el autor amon, fuente
@NicholasKyriakides El hardware adecuado no es una microoptimización. Es la principal forma de escalar bases de datos. La latencia de la red dentro de un centro de datos es despreciable aquí, <1 ms. Escribir en un SSD de nivel empresarial también es <1 ms. Para 1000 transacciones, necesitará al menos 1k IOPS, por ejemplo, Los discos duros no pueden proporcionar, aunque RAID-0 puede ayudar. Un administrador de sistemas competente debe poder configurar todo esto correctamente. Sin embargo, ves problemas. O tienes un problema de rendimiento gigante en un componente de software (lo has descartado para la base de datos) o tu PaaS es realmente muy mala. La nube apesta por el rendimiento.
agregado el autor amon, fuente
¿Empaquetar algunos eventos en una sola solicitud antes de enviarlos a través de la red no es una opción? Resolví un problema similar haciendo que cada cliente "empaquetara" todos los eventos que ocurrieron en un período de tiempo determinado en una sola solicitud y que los enviaran cada 10 ~ 15s aproximadamente. Si esa es una opción, dame un ping y lo expandiré en una respuesta completa.
agregado el autor T. Sar, fuente
¿Cómo verificaste exactamente que el problema es el conjunto de conexiones? @amon tiene razón en sus cálculos. Intente emitir select null en 500 conexiones. Apuesto a que encontrará que el grupo de conexión no es el problema allí.
agregado el autor user26009, fuente
Si seleccionar nulo es problemático, entonces probablemente tengas razón. Aunque sería interesante donde se gasta todo ese tiempo. Ninguna red es tan lenta.
agregado el autor user26009, fuente
@amon El cuello de botella es de hecho el conjunto de conexiones. He ejecutado ANALYZE en las consultas y no son un problema. También he construido un prototipo para probar la hipótesis de la agrupación de conexiones y he comprobado que este es realmente el problema. La base de datos y el servidor en sí viven en máquinas diferentes, de ahí la latencia. Además, no queremos renunciar a Heroku a menos que sea absolutamente necesario, no preocuparnos por las implementaciones es una ventaja enorme para nosotros.
agregado el autor Nicholas Kyriakides, fuente
... Este escenario nos ha hecho pensar que, si bien esta vez podríamos "solucionar el problema con el acelerador" esta vez, pronto no lo haremos.
agregado el autor Nicholas Kyriakides, fuente
@JimmyJames no ralentizará a un cliente, ¿significa que están continuamente creando una acumulación más profunda de eventos que deben manejarse? . No en este caso. Aceleramos a los clientes para que envíen ese evento a un ritmo menor. Para ese evento no necesitamos necesitar los datos que se envían a ese ritmo, pero sería bueno tenerlos. Hay eventos que siempre necesitamos tenerlos. En este momento no tenemos tantos usuarios, por lo que el evento requerido causará el mismo problema, pero lo haremos pronto. No estoy resolviendo exactamente mi problema actual ...
agregado el autor Nicholas Kyriakides, fuente
@Walfrat No lo manejamos. Simplemente desaceleramos el ritmo en que los eventos se emiten como una solución temporal. Además: es absolutamente necesario que el 100% de su evento esté correctamente insertado en la base de datos . Si y no; Si un cliente envía un evento al servidor, quiero garantizar que estará disponible para que otros clientes lo lean inmediatamente y después de 2,3 años. No tiene que insertarse inmediatamente en la base de datos, pero cualquier solución propuesta sería preferiblemente tolerante a fallos.
agregado el autor Nicholas Kyriakides, fuente
@JimmyJames editó la pregunta, es promedio.
agregado el autor Nicholas Kyriakides, fuente
@usr Mi prueba de arnés se ejecutó en 50 conexiones, no en 500. He ejecutado SELECT NULL y aún es problemático. También he ejecutado ANALYZE en las consultas y sus tiempos parecen estar bien. Aunque el concepto de mi pregunta sigue en pie, lo actualizaré con datos más precisos. También me he olvidado de agregar el tamaño de la consulta que se envía a través del cable, que es bastante grande (~ 5KB en promedio)
agregado el autor Nicholas Kyriakides, fuente
Dicho esto, entiendo que hay micro-optimizaciones que podría hacer y que me ayudarán a resolver el problema actual . Me pregunto si hay una solución de arquitectura escalable para mi problema.
agregado el autor Nicholas Kyriakides, fuente
Como norma general, diría: cuando alcance los límites de la tecnología que está utilizando, debe comenzar a cambiar a otra tecnología.
agregado el autor Dominique, fuente

6 Respuestas

Mi conjetura es que necesita explorar más cuidadosamente un enfoque que ha rechazado

  • Encolar los eventos en nuestro servidor

Mi sugerencia sería comenzar a leer los diversos artículos publicados sobre la arquitectura LMAX . Lograron que el trabajo por lotes de gran volumen funcionara para su caso de uso, y es posible que sus compromisos se parezcan más a los suyos.

Además, es posible que desee ver si puede eliminar las lecturas, idealmente, le gustaría poder escalarlas independientemente de las escrituras. Eso puede significar mirar en CQRS (comando de segregación de responsabilidad de consulta).

El servidor puede reiniciarse mientras los eventos se ponen en cola, lo que hace que perdamos los eventos en cola.

En un sistema distribuido, creo que puede estar bastante seguro de que los mensajes se perderán. Es posible que pueda mitigar parte del impacto de eso al ser juicioso acerca de las barreras de secuencia (por ejemplo, asegurarse de que la escritura en el almacenamiento duradero ocurra antes de que el evento se comparta fuera del sistema).

  • 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 administrarlos

Tal vez, sería más probable que observara los límites de su negocio para ver si hay lugares naturales para compartir los datos.

¿Hay casos en los que perder datos es una compensación aceptable?

Bueno, supongo que podría haber, pero eso no es a donde iba. El punto es que el diseño debería haber incorporado la robustez necesaria para progresar ante la pérdida de mensajes.

Lo que a menudo se ve es un modelo basado en pull con notificaciones. El proveedor escribe los mensajes en un almacén duradero ordenado. El consumidor extrae los mensajes de la tienda, rastreando su propia marca de agua. Las notificaciones push se utilizan como un dispositivo de reducción de la latencia, pero si se pierde la notificación, el mensaje aún se obtiene (eventualmente) porque el consumidor está siguiendo un horario regular (la diferencia es que si se recibe la notificación, la extracción se produce antes ).

See Reliable Messaging Without Distributed Transactions, by Udi Dahan (already referenced by Andy) and Polyglot Data by Greg Young.

11
agregado
En un sistema distribuido, creo que puede estar bastante seguro de que los mensajes se perderán . De Verdad? ¿Hay casos en los que perder datos es una compensación aceptable? Tenía la impresión de que perder datos = falla.
agregado el autor Nicholas Kyriakides, fuente
@NicholasKyriakides, generalmente no es aceptable, por lo tanto, OP sugirió la posibilidad de escribir en una tienda duradera antes de emitir el evento. Consulte este artículo y este video de Udi Dahan, donde aborda el problema con más detalle.
agregado el autor Andy, fuente

Flujo de entrada

No está claro si sus 1000 eventos/segundo representan picos o si es una carga continua:

  • si es un pico, podría usar una cola de mensajes como búfer para distribuir la carga en el servidor de base de datos durante más tiempo;
  • si se trata de una carga constante, la cola de mensajes por sí sola no es suficiente, ya que el servidor DB nunca podrá ponerse al día. Entonces deberías pensar en una base de datos distribuida.

Solución propuesta

Intuitivamente, en ambos casos, me gustaría un Kafka evento basado en corriente:

  • All events are systematically published on a kafka topic
  • A consumer would subscribe to the events and store them to the database.
  • A query processor will handle the requests from the clients and query the DB.

Esto es altamente escalable en todos los niveles:

  • Si el servidor DB es el cuello de botella, simplemente agregue varios consumidores. Cada uno podría suscribirse al tema y escribir en un servidor de base de datos diferente. Sin embargo, si la distribución se produce de forma aleatoria en los servidores de base de datos, el procesador de consultas no podrá predecir el servidor de base de datos y tendrá que consultar varios servidores de base de datos. Esto podría llevar a un nuevo cuello de botella en el lado de la consulta.
  • Por lo tanto, se podría anticipar el esquema de distribución de la base de datos organizando el flujo de eventos en varios temas (por ejemplo, utilizando grupos de claves o propiedades, para particionar la base de datos según una lógica predecible).
  • Si un servidor de mensajes no es suficiente para manejar una creciente inundación de eventos de entrada, puede agregar kafka particitions para distribuir los temas de kafka en varios servidores físicos.

Ofreciendo eventos aún no escritos en el DB a clientes.

Desea que sus clientes puedan acceder también a la información que todavía está en la tubería y que aún no se ha escrito en la base de datos. Esto es un poco más delicado.

Opción 1: usar un caché para complementar las consultas de db

No he analizado en profundidad, pero la primera idea que se me ocurre es hacer que los procesadores de consultas sean un consumidor de los temas de kafka, pero de forma diferente kafka consumer group . El procesador de solicitudes recibiría todos los mensajes que recibirá el escritor de la base de datos, pero de manera independiente. Entonces podría mantenerlos en un caché local. Las consultas se ejecutarían en DB + caché (+ eliminación de duplicados).

El diseño se vería así:

enter image description here

La escalabilidad de esta capa de consulta podría lograrse agregando más procesadores de consulta (cada uno en su propio grupo de consumidores).

Opción 2: diseñar una API dual

Un mejor enfoque de IMHO sería ofrecer una API dual (use el mecanismo del grupo de consumidores separado):

  • una API de consulta para acceder a eventos en la base de datos y/o realizar análisis
  • una API de transmisión que solo reenvía mensajes directamente desde el tema

La ventaja, es que dejas que el cliente decida qué es lo interesante. Esto podría evitar que fusione sistemáticamente los datos de la base de datos con datos recién cobrados, cuando el cliente solo está interesado en nuevos eventos entrantes. Si la delicada fusión entre eventos nuevos y archivados es realmente necesaria, entonces el cliente tendría que organizarla.

Variantes

Propuse kafka porque está diseñado para volúmenes muy altos con mensajes persistentes para que pueda reiniciar los servidores si es necesario.

Podrías construir una arquitectura similar con RabbitMQ. Sin embargo, si necesita colas persistentes, puede disminuir el rendimiento . Además, que yo sepa, la única forma de lograr el consumo paralelo de los mismos mensajes por parte de varios lectores (por ejemplo, writer + cache) con RabbitMQ es clonar las colas . Por lo tanto, una mayor escalabilidad podría tener un precio más alto.

8
agregado
@NicholasKyriakides interpreté " Otros clientes solicitan continuamente leer todos los eventos con una clave en particular , incluso si todavía no están guardados en la base de datos . "como una necesidad de realizar una consulta de base de datos (" todo ") y fusionarla con los eventos entrantes (aquí se maneja con un" caché "alimentado directamente desde la entrada), eliminando los dobles. Si con "todo" significara simplemente "todo nuevo", podríamos simplificar: no hay caché, no hay combinación, y ya sea leer desde la base de datos o reenviar nuevos eventos
agregado el autor Christophe, fuente
Sí. Mi primer pensamiento sería no optar por una distribución aleatoria, ya que podría aumentar la carga de procesamiento de las consultas (es decir, la consulta de ambas bases de datos múltiples la mayor parte del tiempo). También podría considerar los motores de base de datos distribuidos (por ejemplo, ¿Ignite?). Pero hacer una elección informada requeriría una buena comprensión de los patrones de uso de la base de datos (qué más está en la base de datos, con qué frecuencia se consulta, qué tipo de consultas, hay restricciones transaccionales más allá de los eventos individuales, etc.).
agregado el autor Christophe, fuente
@NicholasKyriakides Gracias! 1) Estaba pensando simplemente en varios servidores de bases de datos independientes pero con un esquema de partición claro (clave, geografía, etc.) que podría usarse para enviar los comandos de manera efectiva. 2) Intuitivamente , tal vez porque Kafka está diseñado para muy alto rendimiento con mensajes persistentes, ¿necesita reiniciar sus servidores?). No estoy seguro de que RabbitMQ sea tan flexible para los escenarios distribuidos, y las colas persistentes disminuyen el rendimiento
agregado el autor Christophe, fuente
Estelar; ¿Qué quiere decir con una base de datos distribuida (por ejemplo, utilizando una especialización del servidor por grupo de claves) ? También por qué Kafka en lugar de RabbitMQ? ¿Hay alguna razón particular para elegir uno sobre el otro?
agregado el autor Nicholas Kyriakides, fuente
Para 1) Así que esto es bastante similar a mi idea de Usar múltiples bases de datos , pero me está diciendo que no debería distribuir los mensajes a cada una de las bases de datos. ¿Derecha?
agregado el autor Nicholas Kyriakides, fuente
Me pregunto, ¿por qué se necesita el caché local? La idea general de usar múltiples bases de datos/escritores es que los eventos se guarden instantáneamente y casi nunca haya un atraso. ¿Por qué no solo leer directamente desde el DB?
agregado el autor Nicholas Kyriakides, fuente
incluso si aún no están guardados en la base de datos. . Lo que quise decir aquí es que si se elige una solución que acepte que siempre habrá una acumulación de eventos que aún no se han escrito, entonces a los clientes de lectura también les gustaría obtener los eventos de acumulación. La idea de múltiples bases de datos prácticamente significa que no hay acumulación (en teoría) = eventos de base de datos nunca guardados = no es necesario un caché.
agregado el autor Nicholas Kyriakides, fuente
Solo quiero decir que a pesar de que Kafka puede dar un rendimiento muy alto, probablemente esté más allá de las necesidades de la mayoría de las personas. Descubrí que tratar con kafka y su API fue un gran error para nosotros. RabbitMQ no se queda atrás y tiene una interfaz que usted esperaría de un MQ
agregado el autor Ankit, fuente

Si entiendo correctamente el flujo de corriente es:

  1. Recepción y evento (¿Supongo que a través de HTTP?)
  2. Solicitar una conexión de la agrupación.
  3. Inserte el evento en la base de datos
  4. Libere la conexión al grupo.

Si es así, creo que el primer cambio en el diseño sería dejar de tener su código de manejo uniforme para devolver las conexiones al grupo en cada evento. En su lugar, cree un grupo de subprocesos/procesos de inserción que sea 1 a 1 con el número de conexiones de base de datos. Estos tendrán cada uno una conexión DB dedicada.

Utilizando algún tipo de cola concurrente, a continuación, estos subprocesos extraen los mensajes de la cola concurrente y los insertan. En teoría, nunca deben devolver la conexión a la agrupación o solicitar una nueva, pero es posible que tenga que construir un manejo en caso de que la conexión se estropee. Podría ser más fácil eliminar el proceso/hilo y comenzar uno nuevo.

Esto debería eliminar efectivamente la sobrecarga del conjunto de conexiones. Por supuesto, deberá poder realizar eventos de al menos 1000/conexiones por segundo en cada conexión. Es posible que desee probar diferentes números de conexiones, ya que tener 500 conexiones trabajando en las mismas tablas podría crear contienda en la base de datos, pero esa es una pregunta completamente diferente. Otra cosa a considerar es el uso de inserciones por lotes, es decir, cada hilo extrae una serie de mensajes y los inserta a la vez. Además, evite tener conexiones múltiples que intenten actualizar las mismas filas.

6
agregado

Suposiciones

Voy a asumir que la carga que describe es constante, ya que ese es el escenario más difícil de resolver.

También voy a asumir que tiene alguna forma de ejecutar cargas de trabajo de larga ejecución desencadenadas fuera del proceso de su aplicación web.

Solución

Assuming that you have correctly identified your bottleneck - latency between your process and the Postgres database - that is the primary problem to solve for. The Solución needs to account for your consistency Restricción with other clients wanting to read the events as soon as practicable after they are received.

Para resolver el problema de la latencia, debe trabajar de una manera que minimice la cantidad de latencia incurrida por evento para ser almacenado. Esto es lo que debe lograr si no está dispuesto o no puede cambiar hardware . Dado que estás en los servicios de PaaS y no tienes control sobre el hardware o la red, la única forma de reducir la latencia por evento será con algún tipo de escritura por lotes de eventos.

Necesitará almacenar una cola de eventos localmente que se desechan y se escriben periódicamente en su base de datos, ya sea una vez que alcance un tamaño determinado o después de un período de tiempo transcurrido. Un proceso deberá supervisar esta cola para desencadenar la descarga al almacén. Debería haber muchos ejemplos en torno a cómo administrar una cola concurrente que se vacíe periódicamente en el idioma de su elección: Aquí hay un ejemplo en C# , del popular sumidero de lotes periódicos de la biblioteca de registro de Serilog.

This SO answer describes the fastest way to flush data in Postgres - although it would require your batching store the queue on disk, and there is likely a problem to be solved there when your disk disappears upon reboot in Heroku.

Restricción

Another answer has already mentioned CQRS, and that is the correct approach to solve for the Restricción. You want to hydrate read models as each event is processed - a Mediator pattern can help encapsulate an event and distribute it to multiple handlers in-process. So one handler may add the event to your read model that is in-memory that clients can query, and another handler can be responsible for queuing the event for its eventual batched write.

El beneficio clave de CQRS es que desacopla sus modelos conceptuales de lectura y escritura, lo cual es una forma elegante de decir que escribe en un modelo y que lee de otro modelo totalmente diferente. Para obtener los beneficios de escalabilidad de CQRS, generalmente querrá asegurarse de que cada modelo se almacene por separado de manera óptima para sus patrones de uso. En este caso, podemos usar un modelo de lectura agregado, por ejemplo, un caché Redis, o simplemente en memoria, para garantizar que nuestras lecturas sean rápidas y consistentes, mientras que todavía usamos nuestra base de datos transaccional para escribir nuestros datos.

5
agregado

Los eventos llegan más rápido de lo que el grupo de conexiones de DB puede manejar

Este es un problema si cada proceso necesita una conexión de base de datos. El sistema debe estar diseñado para que tenga un grupo de trabajadores donde cada trabajador solo necesite una conexión de base de datos y cada trabajador pueda procesar múltiples eventos.

La cola de mensajes se puede usar con ese diseño, necesita productores de mensajes que envíen eventos a la cola de mensajes y los trabajadores (consumidores) procesan los mensajes de la cola.

Otros clientes pueden querer leer eventos al mismo tiempo

Esta restricción solo es posible si los eventos se almacenan en la base de datos sin ningún procesamiento (eventos sin procesar). Si los eventos se procesan antes de almacenarlos en la base de datos, la única forma de obtenerlos es desde la base de datos.

Si los clientes solo desean consultar eventos sin procesar, yo sugeriría utilizar un motor de búsqueda como Elastic Search. Incluso obtendrás la API de consulta/búsqueda de forma gratuita.

Dado que parece que consultar eventos antes de que se guarden en la base de datos es importante para usted, una solución simple como Elastic Search debería funcionar. Básicamente, simplemente almacena todos los eventos en él y no duplica los mismos datos al copiarlos en la base de datos.

El escalado de Elastic Search es fácil, pero incluso con una configuración básica tiene un rendimiento bastante alto.

Cuando necesite procesamiento, su proceso puede obtener los eventos de ES, procesarlos y almacenarlos en la base de datos. No sé cuál es el nivel de rendimiento que necesita de este procesamiento, pero estaría completamente separado de consultar los eventos de ES. De todos modos, no debería tener problemas de conexión, ya que puede tener un número fijo de trabajadores y cada uno con una conexión de base de datos.

3
agregado

Soltaría Heroku todos juntos, es decir, soltaría un enfoque centralizado: las escrituras múltiples que alcanzan el pico máximo de la conexión de la agrupación es una de las razones principales por las que se inventaron los clusters de bases de datos, principalmente porque no se carga la escritura. db (s) con solicitudes de lectura que pueden ser realizadas por otros db en el clúster, probaría con una topología maestro-esclavo, además, como ya mencionó alguien más, tener sus propias instalaciones de db haría posible ajustar el conjunto Sistema para asegurarse de que el tiempo de propagación de consultas se manejaría correctamente.

Buena suerte

1
agregado