Temos uma situação em que tenho que lidar com um influxo maciço de eventos chegando ao nosso servidor, a cerca de 1000 eventos por segundo, em média (o pico pode ser ~ 2000).
O problema
Nosso sistema está hospedado no Heroku e usa um Heroku Postgres DB relativamente caro , que permite um máximo de 500 conexões de DB. Usamos o pool de conexões para conectar-se do servidor ao banco de dados.
Os eventos ocorrem mais rapidamente do que o pool de conexões do banco de dados pode suportar
O problema que temos é que os eventos são mais rápidos do que o pool de conexões pode suportar. Quando uma conexão termina a viagem de ida e volta da rede do servidor para o banco de dados, para que possa ser liberada de volta ao pool, mais n
eventos adicionais ocorrem.
Eventualmente, os eventos se acumulam, esperando para serem salvos e, como não há conexões disponíveis no pool, eles atingem o tempo limite e todo o sistema fica inoperante.
Resolvemos a emergência emitindo os eventos ofensivos de alta frequência em um ritmo mais lento dos clientes, mas ainda queremos saber como lidar com esses cenários no caso de precisarmos lidar com esses eventos de alta frequência.
Restrições
Outros clientes podem querer ler eventos simultaneamente
Outros clientes solicitam continuamente a leitura de todos os eventos com uma chave específica, mesmo que ainda não estejam salvos no banco de dados.
Um cliente pode consultar GET api/v1/events?clientId=1
e obter todos os eventos enviados pelo cliente 1, mesmo que esses eventos ainda não tenham sido salvos no banco de dados.
Existem exemplos de "sala de aula" sobre como lidar com isso?
Soluções possíveis
Enfileire os eventos em nosso servidor
Podemos enfileirar os eventos no servidor (com a fila tendo uma simultaneidade máxima de 400 para que o pool de conexões não se esgote).
Essa é uma má ideia porque:
- Ele consumirá a memória disponível do servidor. Os eventos enfileirados empilhados consumirão grandes quantidades de RAM.
- Nossos servidores são reiniciados uma vez a cada 24 horas . Este é um limite rígido imposto pelo Heroku. O servidor pode reiniciar enquanto os eventos estão na fila, causando a perda dos eventos na fila.
- Introduz o estado no servidor, prejudicando a escalabilidade. Se tivermos uma configuração para vários servidores e um cliente quiser ler todos os eventos enfileirados + salvos, não saberemos em qual servidor os eventos enfileirados estão.
Use uma fila de mensagens separada
Suponho que poderíamos usar uma fila de mensagens (como o RabbitMQ ?), Onde bombeamos as mensagens e, por outro lado, existe outro servidor que trata apenas de salvar os eventos no banco de dados.
Não tenho certeza se as filas de mensagens permitem a consulta de eventos em fila de espera (que ainda não foram salvos). Se outro cliente quiser ler as mensagens de outro cliente, posso obter as mensagens salvas do banco de dados e as mensagens pendentes da fila e concatená-los juntos para que eu possa enviá-los de volta ao cliente de solicitação de leitura.
Use vários bancos de dados, cada um salvando uma parte das mensagens com um servidor coordenador de banco de dados central para gerenciá-las
Outra solução que temos é usar vários bancos de dados, com um "coordenador de banco de dados / balanceador de carga" central. Ao receber um evento, esse coordenador escolheria um dos bancos de dados para escrever a mensagem. Isso deve permitir o uso de vários bancos de dados Heroku, aumentando o limite de conexão para 500 x número de bancos de dados.
Em uma consulta de leitura, esse coordenador pode emitir SELECT
consultas para cada banco de dados, mesclar todos os resultados e enviá-los de volta ao cliente que solicitou a leitura.
Essa é uma má ideia porque:
- Essa ideia parece ... ahem ... excesso de engenharia? Seria um pesadelo para gerenciar também (backups etc.). É complicado criar e manter e, a menos que seja absolutamente necessário, soa como uma violação do KISS .
- Sacrifica a consistência . Fazer transações em vários bancos de dados não é possível se seguirmos essa ideia.
ANALYZE
as próprias consultas e elas não são um problema. Também construí um protótipo para testar a hipótese do conjunto de conexões e verifiquei que esse é realmente o problema. O banco de dados e o próprio servidor vivem em máquinas diferentes, daí a latência. Além disso, não queremos desistir do Heroku, a menos que seja absolutamente necessário, não estar preocupado com implantações é uma grande vantagem para nós.
select null
em 500 conexões. Aposto que você encontrará que o pool de conexões não é o problema lá.