postgres postgresql proxy connection pool

Proxy e pool de conexões

Cada conexão da aplicação ao PostgreSQL consome recursos. Alguns deles são limitados, valiosos ou compartilhados com outras finalidades, como a memória, throughput de I/O e tempo de CPU. Outros afetam a eficiência de algoritmos, como o escalonador de processos é afetado pelo número de processos existentes, o tratador de interrupções é afetado pelo número de conexões estabelecidas e o detector de deadlocks é afetado pelo número de backends do PostgreSQL.

Por exemplo, em um cenário com muitas conexões estabelecidas, os processos de backend do PostgreSQL podem ser encontrados competindo por memória uns com os outros e também com o kernel, que manterá menos blocos de disco em page cache. Eles também podem competir por tempo de CPU se forem muito mais numerosos do que a quantidade de CPUs da máquina. Isso resulta em maior tempo de execução de cada transação, menor quantidade de transações por segundo (TPS) e pior desempenho geral.

No diagrama, (1) muitas conexões são estabelecidas da aplicação para o banco de dados. Cada conexão (2) tem pouca memória à disposição e pouco tempo de processamento. Elas também competem por páginas de buffers (3), que podem ser continuamente carregadas e descarregadas de shared_buffers, especialmente em padrões de acesso a dados muito dispersos em disco (como tabelas com chave primária em UUID). E o kernel tem pouca memória principal livre (4) para usar como page cache, o que faz operações de leitura e escrita serem efetivadas em disco com maior frequência.

  ╔═══════════╗   ┌═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═┐
  ║ Aplicação ║   ║                                    ╔══╡shared_buffers╞══╗ ║
  ╚═══════════╝   │              (2)   ╔╡postgres╞═══╗ ║        (3)         ║ │
    ││││││││(1)   ║  ╔╡postgres╞═══╗   ║ ╔══════════╗║ ║    ┏━━━┓  ┏━━━┓    ║ ║
    │││││││└──────│──║ ╔══════════╗║──→║→║ work_mem ║╠═╣    ┃   ┃  ┃   ┃    ║ │
    ││││││└───────║─→║→║ work_mem ║╠═══║ ╚══════════╝║═╣    ┗━━━┛  ┗━━━┛    ║ ║
    ││││││        │  ║ ╚══════════╝║   ╚═════════════╝ ║    ┏━━━┓  ┏━━━┓    ║ │
    ││││││        ║  ╚═════════════╝   ╔╡postgres╞═══╗ ║    ┃   ┃  ┃   ┃    ║ ║
    ││││││        │  ╔╡postgres╞═══╗   ║ ╔══════════╗║ ║    ┗━━━┛  ┗━━━┛    ║ │
    │││││└────────║──║ ╔══════════╗║──→║→║ work_mem ║╠═╣    ┏━━━┓  ┏━━━┓    ║ ║
    ││││└─────────│─→║→║ work_mem ║╠═══║ ╚══════════╝║═╣    ┃   ┃  ┃   ┃    ║ │
    ││││          ║  ║ ╚══════════╝║   ╚═════════════╝ ║    ┗━━━┛  ┗━━━┛    ║ ║
    ││││          │  ╚═════════════╝   ╔╡postgres╞═══╗ ║    ┏━━━┓  ┏━━━┓    ║ │
    ││││          ║  ╔╡postgres╞═══╗   ║ ╔══════════╗║ ║    ┃   ┃  ┃   ┃    ║ ║
    │││└──────────│──║ ╔══════════╗║──→║→║ work_mem ║╠═╣    ┗━━━┛  ┗━━━┛    ║ │
    ││└───────────║─→║→║ work_mem ║╠═══║ ╚══════════╝║═╩╦═══════════════════╝ ║
    ││            │  ║ ╚══════════╝║   ╚═════════════╝  ║         (4)         │
    ││            ║  ╚═════════════╝   ╔╡postgres╞═══╗  ║  ┏━━━┓ ┏━━━┓ ┏━━━┓  ║
    ││            │  ╔╡postgres╞═══╗   ║ ╔══════════╗║  ║  ┃   ┃ ┃   ┃ ┃   ┃  │
    │└────────────║──║ ╔══════════╗║──→║→║ work_mem ║╠══╣  ┗━━━┛ ┗━━━┛ ┗━━━┛  ║
    └─────────────│─→║→║ work_mem ║╠═══║ ╚══════════╝║══╝  ┏━━━┓ ┏━━━┓ ┏━━━┓  │
                  ║  ║ ╚══════════╝║   ╚═════════════╝     ┃   ┃ ┃   ┃ ┃   ┃  ║
                  │  ╚═════════════╝                       ┗━━━┛ ┗━━━┛ ┗━━━┛  │
                  ║                                                           ║
                  └═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═┘

Um pool de conexões é uma estratégia usada para multiplexar a demanda de uma grande quantidade de conexões geradas pela camada de aplicação para uma pequena quantidade de conexões estabelecidas no banco de dados, com o intuito de melhorar a eficiência do uso de recursos, diminuir o tempo de transações e aumentar o desempenho geral.

O pool pode ser implementado na aplicação ou em um componente externo posicionado em algum ponto da rede entre a aplicação e o banco de dados, chamado proxy. O uso de um proxy é vantajoso mesmo se ele estiver instalado na mesma máquina do PostgreSQL, já que conexões nele são menos custosas que conexões ao PostgreSQL. Proxies externos também podem ser usados para redirecionar conexões para servidores diferentes, o que permite distribuição de carga e chaveamento de servidores em cluster de alta disponibilidade.

Por exemplo, no diagrama o proxy (1) recebe as muitas conexões da aplicação e propaga elas para poucas conexões ao PostgreSQL. Se cada conexão de origem iria executar uma transação (T1-T8) e ficar ociosa o resto do tempo, como é o caso de uso mais comum, as transações são então enfileiradas e executadas sequencialmente (2). Cada transação trabalha em um backend (3) com mais memória e tempo de CPU, assim como menor risco de locks uns com os outros. O backend tem acesso a buffers e cache mais quentes que não sofrem trocas causadas por outros backends com tanta frequência, permitindo uma operação maior em memória. E quando a invalidação de buffers acontece, a troca é mais rápida pois o kernel mantém mais blocos de disco relevantes em page cache (5). Com isso, o tempo de cada transação é minimizado e mais transações são completadas por unidade de tempo.

  ╔═══════════╗   ┌═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═┐
  ║ Aplicação ║   ║              (2)          (3)        ╔═╡shared_buffers╞═╗ ║
  ╚═══════════╝   │     (1)      T3  T1 ╔╡postgres╞════╗ ║        (4)       ║ │
    ││││││││    T1║  ╔╡proxy╞╗    │   │ ║ ╔══════════╗ ║ ║   ┏━━━┓  ┏━━━┓   ║ ║
    │││││││└──────│─→║       ║─→─→─→─→─→║→║ work_mem ║╗╠═╣   ┃   ┃  ┃   ┃   ║ │
    ││││││└───────║─→║       ║  │   │   ║ ╚══════════╝║║ ║   ┗━━━┛  ┗━━━┛   ║ ║
    ││││││      T2│  ║       ║ T4  T2   ║  ╚══════════╝║ ║   ┏━━━┓  ┏━━━┓   ║ │
    ││││││        ║  ║       ║          ╚══════════════╝ ║   ┃   ┃  ┃   ┃   ║ ║
    ││││││      T3│  ║       ║                           ║   ┗━━━┛  ┗━━━┛   ║ │
    │││││└────────║─→║       ║   T7  T5 ╔╡postgres╞════╗ ║   ┏━━━┓  ┏━━━┓   ║ ║
    ││││└─────────│─→║       ║    │   │ ║ ╔══════════╗ ║ ║   ┃   ┃  ┃   ┃   ║ │
    ││││        T4║  ║       ║─→─→─→─→─→║→║ work_mem ║╗╠═╣   ┗━━━┛  ┗━━━┛   ║ ║
    ││││          │  ║       ║  │   │   ║ ╚══════════╝║║ ║   ┏━━━┓  ┏━━━┓   ║ │
    ││││        T5║  ║       ║ T8  T6   ║  ╚══════════╝║ ║   ┃   ┃  ┃   ┃   ║ ║
    │││└──────────│─→║       ║          ╚══════════════╝ ║   ┗━━━┛  ┗━━━┛   ║ │
    ││└───────────║─→║       ║     ┏━━━┓ ┏━━━┓ ┏━━━┓     ╚══════════════════╝ ║
    ││          T6│  ║       ║     ┃   ┃ ┃   ┃ ┃   ┃                          │
    ││            ║  ║       ║     ┗━━━┛ ┗━━━┛ ┗━━━┛  (5)                     ║
    ││          T7│  ║       ║     ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓  │
    │└────────────║─→║       ║     ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃  ║
    └─────────────│─→║       ║     ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛  │
                T8║  ║       ║     ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┏━━━┓  ║
                  │  ╚═══════╝     ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃ ┃   ┃  │
                  ║                ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛ ┗━━━┛  ║
                  └═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═┘

Proxies como esses podem operar de diversos modos, multiplexando conexões inteiras, transações ou comandos individuais.

O PostgreSQL não implementa essa funcionalidade internamente, mas algumas ferramentas externas são comumente usadas: