Estamos tendo problemas para lidar com o tráfego durante os horários de pico para nosso servidor de banco de dados. Estamos procurando melhorar o hardware (veja esta pergunta sobre esse lado das coisas ), mas também queremos trabalhar na configuração do pooling e no ajuste do servidor.
O aplicativo em que estamos trabalhando é um jogo multijogador baseado em turnos para smartphones, onde o back-end consiste em Rails com unicórnio e PostgreSQL 9.1 como banco de dados. Atualmente, temos 600.000 usuários registrados e, como o estado do jogo é armazenado no banco de dados, vários milhares de gravações são feitas a cada dois segundos. Analisamos os arquivos de log do PostgreSQL usando o PgBadger e durante as horas críticas recebemos muitos
FATAL: remaining connection slots are reserved for non-replication superuser connections
A solução ingênua para combater esse problema seria aumentar max_connections (que atualmente é 100) em postgresql.conf . Eu li http://wiki.postgresql.org/wiki/Number_Of_Database_Connections que indica que isso pode não ser a coisa certa a fazer. No artigo mencionado, é referido encontrar o ponto ideal entre max_connections e pool size .
O que pode ser feito para encontrar esse ponto ideal? Existem boas ferramentas para medir o desempenho de E/S para diferentes valores de max_connections e tamanho do pool ?
Nossa configuração atual é de 4 servidores de jogo, cada um com 16 trabalhadores unicórnios e um tamanho de pool de 5.
Aqui estão as configurações postgres não padrão que estamos usando:
version | PostgreSQL 9.1.5 on x86_64-unknown-linux-gnu,compiled by gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit
checkpoint_completion_target | 0.9
checkpoint_segments | 60
checkpoint_timeout | 6min
client_encoding | UTF8
effective_cache_size | 2GB
lc_collate | en_US.UTF-8
lc_ctype | en_US.UTF-8
log_destination | csvlog
log_directory | pg_log
log_filename | postgresql-%Y-%m-%d_%H%M%S.log
log_line_prefix | %t
log_min_duration_statement | 200ms
log_rotation_age | 1d
log_rotation_size | 10MB
logging_collector | on
max_connections | 100
max_stack_depth | 2MB
server_encoding | UTF8
shared_buffers | 1GB
ssl | on
TimeZone | localtime
wal_buffers | 16MB
work_mem | 8MB
A resposta curta aqui é "tentativa e erro guiados por métricas de monitoramento e desempenho".
Existem algumas regras gerais que devem ajudá-lo a encontrar a área vaga na qual você deve começar, mas são muito gerais. As diretrizes amplas "número de CPUs mais o número de discos independentes" são frequentemente citadas, mas é apenas um ponto de partida incrivelmente grosseiro.
O que você realmente precisa fazer é obter métricas de desempenho robustas para seu aplicativo. Comece a gravar as estatísticas.
Não há muitas ferramentas integradas para isso. Existem coisas como o
check_postgres
script nagios, o registro do contador de desempenho do sistema Cacti, o coletor de estatísticas do PostgreSQL, etc ... mas não há muito que reúna tudo. Infelizmente, você terá que fazer isso sozinho. Para o lado do PostgreSQL, consulte monitoramento no manual do PostgreSQL. Existem algumas opções de terceiros, como Postgres Enterprise Monitor do EnterpriseDB .Para as métricas de nível de aplicativo mencionadas aqui, você desejará registrá-las em estruturas de dados compartilhadas ou em um banco de dados externo não durável como o Redis e agregá-las conforme você as registra ou antes de gravá-las em seu banco de dados PostgreSQL. Tentar registrar diretamente no Pg irá distorcer suas medições com a sobrecarga criada pelo registro das medições e piorar o problema.
A opção mais simples é provavelmente um singleton em cada servidor de aplicativo que você usa para registrar as estatísticas do aplicativo. Você provavelmente deseja manter um min, max, n, total e média constantemente atualizados; dessa forma, você não precisa armazenar cada ponto de estatística, apenas os agregados. Esse singleton pode gravar suas estatísticas agregadas em Pg a cada x minutos, uma taxa baixa o suficiente para que o impacto no desempenho seja mínimo.
Começar com:
Qual é a latência do pedido? Em outras palavras, quanto tempo o aplicativo leva desde receber uma solicitação do cliente até responder ao cliente. Registre isso de forma agregada ao longo de um período de tempo, em vez de registros individuais. Agrupar por tipo de solicitação; digamos, por página.
Qual é o atraso de acesso ao banco de dados para cada consulta ou tipo de consulta que o aplicativo executa? Quanto tempo leva desde pedir informações ao banco de dados / armazenar informações até que seja feito e possa passar para a próxima tarefa? Novamente, agregue essas estatísticas no aplicativo e grave apenas as informações agregadas no banco de dados.
Como está sua taxa de transferência? Em determinados x minutos, quantas consultas de cada classe principal que seu aplicativo executa são atendidas pelo banco de dados?
Para esse mesmo intervalo de tempo de x minutos, quantas solicitações de clientes houve?
Amostragem a cada poucos segundos e agregação nas mesmas janelas de x minutos no banco de dados, quantas conexões de banco de dados havia? Quantos deles estavam ociosos? Quantos estavam ativos? Em inserções? Atualizações? Selecione% s? exclui? Quantas transações ocorreram nesse período? Consulte a documentação do coletor de estatísticas
Novamente amostrando e agregando no mesmo intervalo de tempo, como eram as métricas de desempenho do sistema host? Quantos lêem e quantos escrevem E/S de disco por segundo? Megabytes por segundo de leituras e gravações em disco? Utilização da CPU? Carga média? RAM usa?
Agora você pode começar a aprender sobre o desempenho do seu aplicativo correlacionando os dados, fazendo gráficos etc. Você começará a ver padrões, a encontrar gargalos.
Você pode descobrir que seu sistema está em um gargalo
INSERT
eUPDATE
está em altas taxas de transação, apesar de I/O de disco bastante baixo em megabytes por segundo. Isso seria uma dica de que você precisa melhorar o desempenho de liberação de disco com um controlador RAID de cache write-back com bateria ou alguns SSDs protegidos por energia de alta qualidade. Você também pode usarsynchronous_commit = off
if it's OK para perder algumas transações na falha do servidor e/ou umcommit_delay
, para reduzir parte da carga de sincronização.Ao representar graficamente suas transações por segundo em relação ao número de conexões simultâneas e corrigir a variação da taxa de solicitação que o aplicativo está vendo, você poderá ter uma ideia melhor de onde está seu ponto ideal de taxa de transferência.
Se você não tiver armazenamento de liberação rápida (BBU RAID ou SSDs rápidos e duráveis), não desejará mais do que um número razoavelmente pequeno de conexões de gravação ativa, talvez no máximo 2x o número de discos que possui, provavelmente menos, dependendo do arranjo do RAID , desempenho do disco, etc. Nesse caso, nem vale a pena tentar e errar; apenas atualize seu subsistema de armazenamento para um com descargas rápidas de disco .
Consulte
pg_test_fsync
para obter uma ferramenta que o ajudará a determinar se isso pode ser um problema para você. A maioria dos pacotes PostgreSQL instala esta ferramenta como parte do contrib, então você não precisa compilá-la. Se você obtiver menos de alguns milhares de operações/segundo,pg_test_fsync
precisará atualizar urgentemente seu sistema de armazenamento. Meu laptop equipado com SSD obtém 5000-7000. Minha estação de trabalho no trabalho com uma matriz RAID 10 de 4 discos de discos SATA de 7200 rpm e write-through (cache sem gravação) obtém cerca de 80 operações/segundo emf_datasync
, até 20 operações/segundo parafsync()
; é centenas de vezes mais lento . Compare: laptop com ssd vs estação de trabalho com RAID 10 write-through (sem cache). O SSD deste laptop é barato e não confio necessariamente nele para liberar seu cache de gravação em caso de perda de energia; Eu mantenho bons backups e não os usaria para dados que me interessam. SSDs de boa qualidade funcionam tão bem, se não melhor, e são duráveis para gravação.No caso da sua candidatura, aconselho-o vivamente a verificar:
UNLOGGED
tabelas para dados que você pode perder. Agregue-o periodicamente em tabelas registradas. Por exemplo, mantenha os jogos em andamento em tabelas não registradas e grave as pontuações em tabelas duráveis comuns.commit_delay
(menos útil com armazenamento de liberação rápida - dica)synchronous_commit
para transações que você pode perder (menos útil com armazenamento de descarga rápida - dica de dica)LISTEN
eNOTIFY
para executar a invalidação de cache usando gatilhos em tabelas PostgreSQL.Em caso de dúvida: http://www.postgresql.org/support/professional_support/