O balanceamento de carga aumenta o desempenho do sistema, especialmente do ponto de vista do aplicativo, permitindo que vários computadores atendam aos mesmos dados. Ele funciona de tal forma que a carga está sendo distribuída entre as consultas do cliente para nós de réplica, além de seu nó primário ou mestre, enquanto roteia as modificações do banco de dados apenas para o nó mestre. Quaisquer modificações no nó mestre são posteriormente propagadas para cada réplica usando a replicação de streaming do PostgreSQL.
Como os balanceadores de carga podem afetar o PostgreSQL?
A utilização do balanceamento de carga deve direcionar os aplicativos clientes para se conectarem ao servidor de balanceamento de carga e distribuir as conexões iniciadas para os nós PostgreSQL disponíveis, dependendo do tipo de solicitação de consulta. Isso ajuda a enfatizar a carga pendente em um servidor PostgreSQL específico e promove o equilíbrio paralelo de carga entre os nós disponíveis no cluster.
Usando o PostgreSQL, já existem algumas soluções existentes para fazer isso funcionar. Essas soluções podem funcionar perfeitamente ou o balanceamento de carga pode funcionar com a topologia atual - com nós primários e de espera - mas o balanceamento de carga é implementado na própria camada de aplicativo. O balanceamento de carga enfrenta desafios com problemas de sincronização, que é a dificuldade fundamental para servidores trabalhando juntos. Como não existe uma solução única que elimine o impacto do problema de sincronização para todos os casos de uso, existem várias soluções. Cada solução aborda esse problema de uma maneira diferente e minimiza seu impacto para uma carga de trabalho específica.
Neste blog, veremos esses balanceadores de carga comparando-os e como isso é benéfico para sua carga de trabalho do PostgreSQL.
Balanceamento de carga HAProxy para PostgreSQL
HAProxy é um mecanismo sem bloqueio e orientado a eventos que combina um proxy com uma camada de E/S muito rápida e um agendador multithread baseado em prioridade. Como foi projetado com um objetivo de encaminhamento de dados em mente, sua arquitetura foi projetada para operar em um processo leve, otimizado para mover os dados o mais rápido possível com o mínimo de operações possível. Ele se concentra em otimizar a eficiência do cache da CPU mantendo as conexões na mesma CPU o maior tempo possível. Como tal, ele implementa um modelo em camadas que oferece mecanismos de desvio em cada nível, garantindo que os dados não atinjam níveis mais altos, a menos que seja necessário. A maior parte do processamento é realizada no kernel. O HAProxy faz o possível para ajudar o kernel a fazer o trabalho o mais rápido possível, dando algumas dicas ou evitando certas operações quando ele acha que elas podem ser agrupadas posteriormente. Como resultado, os números típicos mostram 15% do tempo de processamento gasto no HAProxy versus 85% no kernel no modo de fechamento TCP ou HTTP e cerca de 30% no HAProxy versus 70% no kernel no modo keep-alive HTTP.
HAProxy também possui recursos adicionais de balanceamento de carga. Por exemplo, o recurso de proxy TCP nos permite usá-lo para conexões de banco de dados, especialmente para PostgreSQL, usando seu suporte interno de serviço de verificação. Embora haja suporte ao serviço de banco de dados, ele não é suficiente para a verificação de integridade desejada, especialmente para um tipo de cluster de replicação. A abordagem padrão ao implantá-lo para produção é usar a verificação de TCP e, em seguida, depender do xinetd com HAProxy.
Prós de usar HAProxy para PostgreSQL
A melhor coisa com HAProxy é sua leveza, fácil de configurar e usar, e faz o trabalho conforme o esperado. O uso do HAProxy em cima de um cluster PostgreSQL foi implementado e implantado várias vezes de grandes organizações para várias PMEs/SMBs para uso em produção. Foi comprovado há muito tempo para produção e alta capacidade de carga de trabalho, não apenas para bancos de dados, mas até mesmo com outros serviços de rede, como aplicativos da Web ou balanceamento de carga geográfica (distribui o tráfego em vários data centers). Tendo o HAProxy em cima do PostgreSQL, ele permite que os usuários acelerem ou limitem as respostas para paralelizar e distribuir a carga adequadamente para todos os nós disponíveis no cluster. O mecanismo integrado com HAProxy também permite que o usuário configure alta disponibilidade de forma transparente e mais fácil de dimensionar se a carga for necessária e evitar ponto único de falha (SPOF).
Contras do uso do HAProxy para PostgreSQL
HAProxy não fornece filtragem de consulta nem análise de consulta para identificar o tipo de instrução que está sendo solicitada. Ele não tem a capacidade de executar uma divisão de leitura/gravação em uma única porta. Definir um balanceador de carga em cima do HAProxy requer que você tenha pelo menos configurar portas diferentes para suas gravações e portas diferentes para suas leituras. Isso requer alterações no aplicativo para atender às suas necessidades.
O HAProxy também faz um suporte de recurso muito simples com PostgreSQL para verificação de integridade, mas isso apenas determina se o nó está ativo ou não, como se estivesse apenas pingando o nó e aguardando uma resposta de retorno. Ele não identifica qual função um nó está tentando encaminhar as conexões solicitadas do cliente para o nó desejado. Portanto, ele não entende ou nenhum recurso no HAProxy para entender a topologia de replicação. Embora, um usuário possa criar listeners separados com base em portas diferentes, mas ainda adiciona alterações no aplicativo para satisfazer as necessidades de balanceamento de carga. Isso significa que usar um script externo com xinetd pode ser a solução para preencher os requisitos. Ainda assim, ele não está integrado ao HAProxy e pode estar sujeito a erros humanos.
Se um nó ou grupo de nós precisar ser colocado no modo de manutenção, você também deverá aplicar alterações em seu HAProxy, caso contrário, poderá ser catastrófico.
Pgpool-II para balanceamento de carga do seu PostgreSQL
Pgpool-II é um software de código aberto e é adotado pela enorme comunidade PostgreSQL para implementar o balanceamento de carga e usá-lo para atuar como seu middleware do aplicativo até a camada de proxy, então distribui a carga após analisar completamente o tipo de solicitação por consulta ou conexão com o banco de dados. O Pgpool-II está lá há tanto tempo desde 2003, que foi originalmente chamado de Pgpool até se tornar Pgpool-II em 2006, que serve como um testemunho de uma ferramenta de proxy muito estável, não apenas para balanceamento de carga, mas também para muitos recursos interessantes .
Pgpool-II é conhecido como o canivete suíço do PostgreSQL e é um software proxy que fica entre os servidores PostgreSQL e um cliente de banco de dados PostgreSQL. A ideia básica do PgPool-II é que ele fica no cliente, então as consultas de leitura devem ser entregues aos nós de espera, enquanto a gravação ou as modificações vão direto para o primário. É uma solução de balanceamento de carga muito inteligente que não apenas equilibra a carga, mas também oferece suporte à alta disponibilidade e fornece pool de conexões. O mecanismo inteligente permite balancear a carga entre mestres e escravos. Assim, as gravações são carregadas no mestre, enquanto as leituras de processamento são direcionadas para os servidores somente leitura disponíveis, que são seus nós supostamente de espera ativa. O Pgpool-II também fornece replicação lógica. Embora seu uso e importância tenham diminuído à medida que as opções de replicação incorporadas melhoraram no lado do servidor PostgreSQL, isso ainda continua sendo uma opção valiosa para versões mais antigas do PostgreSQL. Além de tudo isso, ele também fornece pool de conexões.
O Pgpool-II tem uma arquitetura mais envolvente que o PgBouncer para suportar todos os recursos que ele oferece. Como ambos oferecem suporte ao pool de conexões, o último não possui recursos de balanceamento de carga.
O Pgpool-II pode gerenciar vários servidores PostgreSQL. O uso da função de replicação permite criar um backup em tempo real em 2 ou mais discos físicos, para que o serviço possa continuar sem parar os servidores em caso de falha do disco. Como o Pgpool-II também é capaz de agrupar conexões, ele pode limitar as conexões excedentes. Há um limite no número máximo de conexões simultâneas com o PostgreSQL e as conexões são rejeitadas após esse número de conexões. A configuração do número máximo de conexões, no entanto, aumenta o consumo de recursos e afeta o desempenho do sistema. O pgpool-II também tem um limite no número máximo de conexões, mas conexões extras serão enfileiradas em vez de retornar um erro imediatamente.
No balanceamento de carga, se um banco de dados for replicado, a execução de uma consulta SELECT em qualquer servidor retornará o mesmo resultado. O pgpool-II aproveita o recurso de replicação para reduzir a carga em cada servidor PostgreSQL distribuindo consultas SELECT entre vários servidores, melhorando o rendimento geral do sistema. Na melhor das hipóteses, o desempenho melhora proporcionalmente ao número de servidores PostgreSQL. O balanceamento de carga funciona melhor em uma situação em que há muitos usuários executando muitas consultas ao mesmo tempo.
Usando a função de consulta paralela, os dados podem ser divididos entre os vários servidores, de modo que uma consulta possa ser executada em todos os servidores simultaneamente para reduzir o tempo total de execução. A consulta paralela funciona melhor ao pesquisar dados em grande escala.
Prós do uso do Pgpool para PostgreSQL
É um tipo de software rico em recursos não apenas para balanceamento de carga. Os principais recursos e suporte desta ferramenta são altamente sob demanda, que fornece pool de conexão, uma alternativa go PgBouncer, replicação nativa, recuperação online, cache de consulta na memória, failover automático e alta disponibilidade com seu subprocesso usando watchdog. Esta ferramenta é tão antiga e é continuamente suportada massivamente pela comunidade PostgreSQL, então lidar com problemas não pode ser difícil de procurar ajuda. A documentação é sua amiga aqui na hora de tirar dúvidas, mas buscar ajuda na comunidade não é difícil, e o fato desta ferramenta ser de código aberto, você pode usá-la livremente desde que esteja em conformidade com a licença BSD.
Pgpool-II também tem analisador SQL. Isso significa que é capaz de analisar com precisão os SQLs e reescrever a consulta. Isso permite que o Pgpool-II aumente o paralelismo dependendo da solicitação de consulta.
Contras do uso do Pgpool para PostgreSQL
Pgpool-II não oferece STONITH (atirar no outro nó na cabeça) que fornece mecanismo de vedação de nó. Se o servidor PostgreSQL falhar, ele mantém a disponibilidade do serviço. Pgpool-II também pode ser o ponto único de falha (SPOF). Quando o nó fica inativo, a conectividade e a disponibilidade do banco de dados param a partir desse ponto. Embora isso possa ser corrigido com redundância com Pgpool-II e tendo que utilizar watchdog para coordenar vários nós Pgpool-II, isso adiciona trabalho extra.
Para o pool de conexões, infelizmente, para aqueles que se concentram apenas no pool de conexões, o que o Pgpool-II não faz muito bem é o pool de conexões, especialmente para um pequeno número de clientes. Como cada processo filho tem seu próprio pool e não há como controlar qual cliente se conecta a qual processo filho, muita coisa é deixada de lado quando se trata de reutilizar conexões.
Usando o driver JDBC para balancear a carga do PostgreSQL
Java Database Connectivity (JDBC) é uma interface de programação de aplicativos (API) para a linguagem de programação Java, que define como um cliente pode acessar um banco de dados. Faz parte da plataforma Java Standard Edition e fornece métodos para consultar e atualizar dados em um banco de dados e é orientado para bancos de dados relacionais.
PostgreSQL JDBC Driver (PgJDBC para abreviar) permite que programas Java se conectem a um banco de dados PostgreSQL usando código Java padrão independente de banco de dados. É um driver JDBC de código aberto escrito em Pure Java (Tipo 4) e se comunica no protocolo de rede nativo PostgreSQL. Por isso, o driver é independente de plataforma; uma vez compilado, o driver pode ser usado em qualquer sistema.
Não é comparável às soluções de balanceamento de carga que apontamos anteriormente. Portanto, essa ferramenta é sua API de interface de programação de aplicativos que permite que você se conecte a partir de seu aplicativo para qualquer tipo de linguagem de programação escrita que suporte JDBC ou pelo menos tenha um adaptador para conectar-se a JDBC. Por outro lado, é mais favorável com aplicativos Java.
O balanceamento de carga com JDBC é bastante ingênuo, mas pode fazer o trabalho. Fornecido com os parâmetros de conexão que podem acionar o mecanismo de balanceamento de carga que esta ferramenta tem a oferecer,
- targetServerType - permite abrir conexões apenas para servidores com estado/função requeridos de acordo com o fator definidor para os servidores PostgreSQL. Os valores permitidos são any, primary, master (obsoleto), slave (descontinuado), secundário, preferSlave e preferSecondary. O estado ou função é determinado observando se o servidor permite gravações ou não.
- hostRecheckSeconds - controla por quanto tempo em segundos o conhecimento sobre um estado de host é armazenado em cache no cache global da JVM. O valor padrão é 10 segundos.
- loadBalanceHosts – permite configurar se o primeiro host é sempre tentado (quando definido como false) ou se as conexões são escolhidas aleatoriamente (quando definido como true)
Então, usando loadBalanceHosts que aceita um valor booleano. loadBalanceHosts é desativado durante o modo padrão e os hosts são conectados na ordem especificada. Se os hosts habilitados forem escolhidos aleatoriamente do conjunto de candidatos adequados. A sintaxe básica ao se conectar ao banco de dados usando jdbc é a seguinte,
- jdbc:postgresql:banco de dados
- jdbc:postgresql:/
- jdbc:postgresql://host/database
- jdbc:postgresql://host/
- jdbc:postgresql://host:port/database
- jdbc:postgresql://host:port/
Dado que loadBalanceHosts e a conexão recebem vários hosts configurados como abaixo,
jdbc:postgresql://host1:port1,host2:port2,host3:port3/database
Isso permite que o JDBC escolha aleatoriamente do conjunto de candidatos adequados.
Prós do uso do PgJDBC para PostgreSQL
Não é necessário exigir middleware ou proxy como balanceadores de carga. Esse processo adiciona mais aumento de desempenho do front-end do aplicativo, pois não há camada extra para cada solicitação passar. Se você tiver aplicativos prontos e escritos para suportar interface com JDBC, isso pode ser vantajoso e se você não precisar de mais middleware principalmente se seu orçamento for apertado e quiser limitar apenas os processos dedicados ao seu único propósito e função. Ao contrário de aplicativos de alto tráfego e grande demanda, pode exigir servidores proxy atuando como seus balanceadores de carga e pode exigir recursos extras para lidar adequadamente com altas solicitações de conexões que também exigem demanda de CPU e processamento de memória.
Contras do uso do PgJDBC para PostgreSQL
Você precisa configurar seu código para cada conexão a ser solicitada. É uma interface de programação de aplicativos, o que significa que há muito trabalho para lidar, especialmente se seu aplicativo for muito exigente em cada solicitação a ser enviada aos servidores adequados. Não há alta disponibilidade, escalabilidade automática e tem um único ponto de falha.
E quanto aos wrappers ou ferramentas implementadas com libpq para balanceamento de carga do seu PostgreSQL?
libpq é a interface do programador de aplicativos C para o PostgreSQL. libpq é um conjunto de funções de biblioteca que permite que programas clientes passem consultas para o servidor de backend PostgreSQL e recebam os resultados dessas consultas.
libpq também é o mecanismo subjacente para várias outras interfaces de aplicativos PostgreSQL, incluindo aquelas escritas para C++, PHP, Perl, Python, Tcl, Swift e ECPG. Portanto, alguns aspectos do comportamento da libpq serão importantes para você se você usar um desses pacotes.
libpq não automatiza o balanceamento de carga e não deve ser considerada uma ferramenta para soluções de balanceamento de carga. No entanto, ele é capaz de se conectar aos próximos servidores disponíveis se os servidores anteriores listados para conexão falharem. Por exemplo, se você tiver dois nós de espera ativa disponíveis, se o primeiro nó estiver muito ocupado e não responder ao valor de tempo limite correspondente, ele se conectará ao próximo nó disponível na conexão fornecida. Depende do tipo de atributos de sessão que você especificou. Isso depende do parâmetro target_session_attrs.
O parâmetro target_session_attrs aceita valores read-write, e qualquer um que seja o valor padrão se não for especificado. O que faz o parâmetro target_session_attrs é que, se configurado para leitura-gravação, somente uma conexão em que transações de leitura-gravação são aceitas durante a conexão. A consulta SHOW transaction_read_only será enviada em qualquer conexão bem-sucedida. Se o resultado estiver ativado, a conexão será fechada, o que significa que o nó é identificado como uma réplica ou não processa gravações. Se vários hosts foram especificados na cadeia de conexão, todos os servidores restantes serão tentados como se a tentativa de conexão tivesse falhado. O valor padrão deste parâmetro, qualquer, significa que todas as conexões são aceitáveis. Embora contar com target_session_attrs não seja suficiente para balanceamento de carga, você pode simular um modo round-robin. Veja meu código C de exemplo abaixo usando libpq,
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <libpq-fe.h>
const char* _getRoundRobinConn() {
char* h[2];
h[0] = "dbname=node40 host=192.168.30.40,192.168.30.50";
h[1] = "dbname=node50 host=192.168.30.50,192.168.30.40";
time_t t;
//srand((unsigned)time(&t));
sleep(1.85);
srand((unsigned)time(NULL));
return h[rand() % 2];
}
void
_connect()
{
PGconn *conn;
PGresult *res;
char strConn[120];
snprintf(strConn, 1000, "user=dbapgadmin password=dbapgadmin %s target_session_attrs=any", _getRoundRobinConn());
//printf("\nstrConn value is: %s\n", strConn);
conn = PQconnectdb(strConn);
res = PQexec(conn, "SELECT current_database(), inet_client_addr();");
if ( PQresultStatus(res)==PGRES_TUPLES_OK )
{
printf("current_database = %s on %s\n", PQgetvalue(res, 0, 0),
PQhost(conn));
} else {
printf("\nFailed... Message Code is: %d\n", PQresultStatus(res));
}
PQclear(res);
PQfinish(conn);
}
int main(void)
{
int i;
for (i=0 ; i<5 ; i++)
_connect();
return 0;
}
O resultado revela,
[email protected]:/home/vagrant# gcc -I/usr/include/postgresql -L/usr/lib/postgresql/12/lib libpq_conn.c -lpq -o libpq_conn; ./libpq_conn
current_database = node40 on 192.168.30.40
current_database = node40 on 192.168.30.40
current_database = node50 on 192.168.30.50
current_database = node40 on 192.168.30.40
current_database = node50 on 192.168.30.50
Observe que, se o nó .40 (o nó primário) ficar inativo, ele sempre direcionará a conexão para o .50 desde que seu valor target_session_attrs seja qualquer.
Nesse caso, você pode simplesmente criar o seu próprio livremente com a ajuda da libpq. Embora o processo de depender da libpq e/ou de seus wrappers seja muito bruto para dizer que isso pode fornecer o mecanismo de balanceamento de carga desejado com distribuição uniforme para os nós que você possui. Definitivamente, essa abordagem e codificação podem ser aprimoradas, mas o pensamento é que isso é gratuito e de código aberto, e você pode codificar sem depender de middlewares e projetar livremente a maneira como seu balanceamento de carga funcionará.
Prós de usar libpq para PostgresQL
biblioteca libpq é a interface de aplicação do programador construída na linguagem de programação C. No entanto, a biblioteca foi implementada em várias linguagens como wrappers para que os programadores possam se comunicar com o banco de dados PostgreSQL usando suas linguagens favoritas. Você pode criar diretamente seu próprio aplicativo usando seus idiomas favoritos e depois listar os servidores que você pretende que as consultas sejam enviadas, mas somente após o outro, se falha ou tempo limite enviar sua carga para os nós disponíveis que você pretende distribuir a carga. Está disponível em linguagens como Python, Perl, PHP, Ruby, Tcl ou Rust.
Contras de usar libpq para PostgresQL
A implementação do paralelismo de carga não é perfeita e você precisa escrever seu próprio mecanismo de balanceamento de carga por código. Não há configuração que você possa usar ou personalizar, pois é uma interface de programação para o banco de dados PostgreSQL com a ajuda do parâmetro target_session_attrs. Isso significa que, ao compor uma conexão de banco de dados, você deve ter uma série de conexões de leitura indo para seus nós de réplica/standby, então escrever consultas que vão para o gravador ou nó primário em seu código, seja em seu aplicativo ou você tenha que criar sua própria API para gerenciar a solução de balanceamento de carga.
Usar essa abordagem definitivamente não precisa ou depende de um middleware da perspectiva do aplicativo de front-end para o banco de dados como back-end. Claro que isso é leve, mas ao enviar a lista de servidores na conexão, isso não significa que a carga seja compreendida e enviada uniformemente, a menos que você precise adicionar seu código para essa abordagem. Isso só adiciona problemas, mas já existem soluções existentes, então por que precisa reinventar a roda?
Conclusão
A implementação de seus balanceadores de carga com o PostgreSQL pode ser exigente, mas depende do tipo de aplicativo e do custo com o qual você está lidando. Às vezes, para uma alta demanda de carga, é necessário que o middleware atue como proxy para distribuir adequadamente a carga e também supervisionar o estado ou a integridade do nó. Por outro lado, pode demandar recursos do servidor ou ter que ser executado em um servidor dedicado ou demandar CPU e memória extras para satisfazer as necessidades e isso aumenta o custo. Portanto, há também uma maneira simples, mas demorada, mas que oferece a distribuição de carga para os servidores disponíveis que você já possui. No entanto, requer habilidades de programação e compreensão da funcionalidade da API.