Como escalar php5 + MySQL acima de 200 solicitações / segundo?

16

Estou ajustando minha página inicial para desempenho, atualmente ele lida com cerca de 200 solicitações / segundo no 3.14.by, que realiza 6 consultas SQL e 20 req / segundo no 3.14.by/forum, que é o fórum phpBB.

Curiosamente, os números são praticamente os mesmos em alguns servidores VPS e Atom 330 dedicados.

O software para servidor é o seguinte: Apache2 + mod_php prefork 4 childs (tentei números diferentes aqui), php5, APC, nginx, memcached para armazenamento de sessões PHP.

O MySQL está configurado para consumir cerca de 30% da RAM disponível (~ 150Mb em VPS, 700Mb em servidor dedicado)

Parece que há um gargalo em algum lugar que não me permite ir mais alto, alguma sugestão? (ou seja, eu sei que fazer menos de 6 SQLs o tornaria mais rápido, mas isso não parece um fator limitante, pois o sqld não consome mais do que alguns% no topo devido a consultas em cache)

Alguém já testou que chutar o apache2 pré-bifurcado e deixar apenas o nginx + php é muito mais rápido?

Mais alguns benchmarks

Small 40-byte static file: 1484 r/s via nginx+apache2, 2452 if we talk to apache2 directly. 
Small "Hello world" php script: 458 r/s via ngin+apache2.

Atualização: Parece que o gargalo é o desempenho do MySQL nos dados em cache. A página com SQL único mostra 354req / s, com 6 SQLs - 180 req / s. O que você acha que eu posso ajustar aqui? (Eu posso extrair 100-200Mb para MySQL)

[client]
port        = 3306
socket      = /var/run/mysqld/mysqld.sock

[mysqld_safe]
socket      = /var/run/mysqld/mysqld.sock
nice        = 0

[mysqld]
default-character-set=cp1251
collation-server=cp1251_general_cs

skip-character-set-client-handshake

user        = mysql
pid-file    = /var/run/mysqld/mysqld.pid
socket      = /var/run/mysqld/mysqld.sock
port        = 3306
basedir     = /usr
datadir     = /var/lib/mysql
tmpdir      = /tmp
skip-external-locking

bind-address        = 127.0.0.1

key_buffer      = 16M
max_allowed_packet  = 8M
thread_stack        = 64K
thread_cache_size   = 16
sort_buffer_size    = 8M
read_buffer_size    = 1M

myisam-recover      = BACKUP
max_connections        = 650
table_cache            = 256
thread_concurrency     = 10

query_cache_limit       = 1M
query_cache_size        = 16M

expire_logs_days    = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet  = 8M

[mysql]
[isamchk]
key_buffer      = 8M

!includedir /etc/mysql/conf.d/
BarsMonster
fonte
Por que você está usando o Apache e o nginx?
18710 Jamieb
Essa é a configuração comum, Apache2 para PHP e vários aplicativos que exigem infraestrutura apache, nginx para reduzir o consumo de memória apache2 em carga.
BarsMonster 18/02/10
Na verdade, não estou entendendo o seu problema. Seu site está lento no momento? Se sim, quão lento é? E quanto você deseja acelerar? Você tentou criar um perfil de partes do seu site para determinar onde está o gargalo?
18710 Jamieb
Está na descrição: agora está em 180-200 solicitações / segundo. Embora isso seja muito mais do que suficiente para uma página inicial, quero ajustar essa configuração para que outros sites criados na mesma base de código funcionem mais rapidamente. Idealmente eu quero saturar conexão 100Mbit com páginas dinâmicas :-)
BarsMonster
2
"Solicitações por segundo" não é realmente uma métrica significativa nesse contexto. Meu netbook pode lidar com "200 solicitações por segundo". Você precisa nos dizer o tempo de resposta que deseja obter com esse tipo de taxa de conexão.
jamieb

Respostas:

29

Obviamente, há muita coisa que você pode tentar. Sua melhor aposta é perseguir seus logs em busca de consultas que não usam índices (ativar logs para essas) e outras consultas não otimizadas. Compilei uma lista enorme de opções relacionadas ao desempenho ao longo dos anos, por isso incluí aqui um pequeno subconjunto para suas informações - espero que ajude. Aqui estão algumas notas gerais sobre o que você pode tentar (se ainda não o fez):

MySQL

  • query_cache_type = 1 - as consultas SQL em cache estão ativadas. Se definido como 2, as consultas serão armazenadas em cache apenas se a dica SQL_CACHE for passada para elas. Da mesma forma com o tipo 1, você pode desativar o cache para uma consulta específica com a dica SQL_NO_CACHE
  • key_buffer_size = 128M (padrão: 8M) - buffer de memória para índices de tabela MyISAM. Em servidores dedicados, tente definir o key_buffer_size para pelo menos um quarto, mas não mais da metade, da quantidade total de memória no servidor
  • query_cache_size = 64M (padrão: 0) - tamanho do cache da consulta
  • back_log = 100 (padrão: 50, máx: 65535) - A fila de solicitações de conexão pendentes. Só importa quando há muitas conexões em pouco tempo
  • join_buffer_size = 1M (padrão: 131072) - um buffer usado ao ter varreduras de tabela completas (sem índices)
  • table_cache = 2048 (padrão: 256) - deve ser max_user_connections multiplicado pelo número máximo de JOINs que sua consulta SQL mais pesada contém. Use a variável "open_tables" nos horários de pico como um guia. Observe também a variável "open_tables" - ela deve estar próxima de "open_tables"
  • query_prealloc_size = 32K (padrão: 8K) - memória persistente para análise e execução de instruções. Aumentar se houver consultas complexas
  • sort_buffer_size = 16M (padrão: 2M) - ajuda na classificação (operações ORDER BY e GROUP BY)
  • read_buffer_size = 2M (padrão: 128K) - Ajuda com verificações sequenciais. Aumente se houver muitas varreduras seqüenciais.
  • read_rnd_buffer_size = 4M - ajuda a tabela MyISAM a acelerar a leitura após a classificação
  • max_length_for_sort_data - tamanho da linha a ser armazenada em vez do ponteiro da linha no arquivo de classificação. Pode evitar leituras aleatórias da tabela
  • key_cache_age_threshold = 3000 (padrão: 300) - hora de manter o cache de chaves na zona quente (antes de ser rebaixado para quente)
  • key_cache_division_limit = 50 (padrão: 100) - ativa um mecanismo de remoção de cache mais sofisticado (dois níveis). Indica a porcentagem a ser mantida no nível inferior. delay_key_write = ALL - o buffer de chaves não é liberado para a tabela em todas as atualizações de índice, mas apenas quando a tabela é fechada. Isso acelera bastante a gravação nas chaves, mas se você usar esse recurso, deverá adicionar a verificação automática de todas as tabelas MyISAM iniciando o servidor com a opção --myisam-recover = BACKUP, FORCE
  • memlock = 1 - processo de bloqueio na memória (para reduzir a troca de entrada / saída)

Apache

  • alterar o método de desova (para mpm por exemplo)
  • desativar logs, se possível
  • AllowOverride None - sempre que possível, desative .htaccess. Ele interrompe o apache para procurar arquivos .htaccess, se não forem usados, e salva uma solicitação de pesquisa de arquivo
  • SendBufferSize - Defina como padrão do sistema operacional. Em redes congestionadas, você deve definir esse parâmetro próximo ao tamanho do maior arquivo normalmente baixado
  • KeepAlive Off (padrão Ativado) - e instale o lingerd para fechar corretamente as conexões de rede e é mais rápido
  • DirectoryIndex index.php - Mantenha a lista de arquivos o mais curta e absoluta possível.
  • Opções FollowSymLinks - para simplificar o processo de acesso a arquivos no Apache
  • Evite usar mod_rewrite ou pelo menos expressões regulares complexas
  • ServerToken = prod

PHP

  • variables_order = "GPCS" (se você não precisar de variáveis ​​de ambiente)
  • register_globals = Desativado - além de ser um risco à segurança, ele também tem um impacto no desempenho
  • Mantenha o include_path o mínimo possível (evita pesquisas extras no sistema de arquivos)
  • display_errors = Desativado - Desativa a exibição de erros. Altamente recomendado para todos os servidores de produção (não exibe mensagens de erro feias em caso de problema).
  • magic_quotes_gpc = Desativado
  • magic_quotes _ * = Desativado
  • output_buffering = Ativado
  • Desative o log, se possível
  • expose_php = Desativado
  • register_argc_argv = Desativado
  • always_populate_raw_post_data = Desativado
  • coloque o arquivo php.ini onde o php procuraria primeiro.
  • session.gc_divisor = 1000 ou 10000
  • session.save_path = "N; / path" - para sites grandes, considere usá-lo. Divide arquivos de sessão em subdiretórios

Ajustes no SO

  • Monte discos rígidos usados ​​com a opção -o noatime (sem tempo de acesso). Adicione também esta opção ao arquivo / etc / fstab.
  • Ajuste o / proc / sys / vm / swappiness (de 0 a 100) para ver os melhores resultados
  • Usar discos RAM - mount --bind -ttmpfs / tmp / tmp
Ivan Peevski
fonte
Essa é uma lista legal, eu já tive a maioria delas e, quando adicionei as coisas restantes, o desempenho não aumentou. Looks como gargalo é em algum lugar entre PHP e MySQL não ser capaz de lidar com mais de 800 pedidos por segundo a partir de consulta de cache ...
BarsMonster
Ok, como você se conecta ao banco de dados (mysql_pconnect () em vez de mysql_connect ())? Você usa conexões persistentes? tente ambos os sentidos ...
Ivan Peevski
Eu já estou no pconnect e o pool de conexão está ativado no php.ini ...: -S
BarsMonster
Apenas pela perfeição, eu tentaria apenas me conectar. Eu já vi casos (especialmente em testes de carga) em que esse desempenho é melhor.
Ivan Peevski
1

Se o gargalo não for CPU, seu IO - rede ou disco. Então .. você precisa ver o quanto de IO está acontecendo. Eu não teria pensado que era a rede (a menos que você esteja em um link half-duplex de 10 Mbps, mas vale a pena verificar o comutador caso a detecção automática não esteja funcionando corretamente).

Isso deixa a E / S do disco, o que pode ser um grande fator, especialmente nos VPSs. Use sar ou iostat para dar uma olhada nos discos e, em seguida, pesquise no google como encontrar mais detalhes se seu disco estiver sendo usado intensamente.

gbjbaanb
fonte
Sim, a rede não é o problema - ao executar ab do servidor local, o desempenho é o mesmo. Eu verifiquei o tempo de iowait - está abaixo de 0,01% - basicamente tudo está no cache do disco e não há gravações no disco envolvidas na solicitação de processamento (todos os logs estão desativados).
BarsMonster
1

Gostaria de olhar para o cache com Nginx ( memcached ) ou Varnish .

Pelo menos você deve servidor de arquivos estáticos com Nginx como SaveTheRbtz disse.

Espennilsen
fonte
Como são páginas dinâmicas, prefiro não armazená-las em cache.
BarsMonster
11
O memcached não é um aplicativo de cache tradicional e pode fazer maravilhas para páginas dinâmicas. Ele fica entre o banco de dados e seu aplicativo. Você aplica as primeiras consultas em cache para um objeto, se ele não estiver lá, em seguida, será carregado a partir do banco de dados. O efeito líquido é que você está usando a RAM para atender às suas solicitações de banco de dados, em vez do armazenamento persistente muito mais lento no banco de dados.
18710 Jamieb
Memcache pode ser usado com nginx, que é um recurso conhecido. O armazenamento persistente mais lento não é usado, está tudo no cache de consultas no MySQL.
BarsMonster
Memcached e cache de consultas do MySQL não são realmente comparáveis; eles nem fazem a mesma coisa. Você é rápido em derrubar praticamente todas as sugestões postadas aqui sem se preocupar em entendê-las. Eu recomendo que você seja um pouco mais aberto.
21710 Jamieb
Entendo claramente a diferença entre o cache de consulta do memcached e o MySQL. Mas, devido ao fato de que tudo está no cache de consultas com 100% de taxa de acertos, eu não chamaria isso de "armazenamento persistente lento". A resposta original de ontem foi sobre o uso do NginX + Memcached, cenário bastante comum para armazenar páginas inteiras em cache. O armazenamento em cache de objetos individuais é outro cenário totalmente diferente. Enquanto estiver usando o memcached na frente do MySQL, estou pensando em obter mais suco sem ele por enquanto (já que isso exigiria algumas alterações no código).
BarsMonster
1

Como o servidor não parece ser problema, talvez o gerador de carga seja. Tente executá-lo em várias máquinas.

OliverS
fonte
O desempenho é o mesmo, mesmo que eu o execute no próprio servidor. Não importa o quão manu conexões simultâneas - 10 ou 50. O teste de carga é faz via ab -c 10 -t 10
BarsMonster
1

Parece-me que você pode estar alcançando a quantidade máxima de conexões que o Apache permite. Dê uma olhada na sua configuração do Apache. Aumentar o limite do servidor e o número máximo de clientes deve ajudar se você ainda não estiver vinculado a outro limite, como E / S ou memória. Observe os valores presentes para mpm_prefork_module ou mpm_worker_module e ajuste de acordo com suas necessidades.

ServerLimit 512
MaxClients 512
Erik Giberti
fonte
Bem, eu realmente preciso disso desde que eu tenho nginx na frente do apache2, então eu acredito que não há muito sence de ter mais de núcleos físicos * 2 processos de Apache2 ....
BarsMonster
Apenas verifiquei isso. O aumento do número de processos Apache2 de 4 para 16 não melhorou o desempenho (caiu até 0,5%). Aumentar o número de trabalhadores nginx para 2 ou 4 não melhorou nada.
BarsMonster
11
Se seus dados são razoavelmente estáticos, ou seja, não atualizam o carregamento de todas as outras páginas, você pode aumentar seu query_cache. O MySQL mantém o conjunto de resultados dessa maneira e extrai da memória. No entanto, se a tabela que está sendo armazenada em cache receber gravações durante esse período, ela invalidará o cache (mesmo que os dados não sejam afetados), fazendo com que ela desperdice memória.
Erik Giberti
Agora eu vejo 100% rácio de cache de consultas hit, e MySQL é ainda sente lento ...
BarsMonster
11
Adicione skip-name-resolve ao seu arquivo de configuração do MySQL. Isso salvará uma pesquisa de DNS em todas as conexões com o servidor. A desvantagem aqui é que todas as conexões precisarão ser bloqueadas por IP (supondo que você não esteja usando '%'). Se o SQL estiver no mesmo servidor e não precisar ser acessado em outro local que não seja o host local, você também poderá adicionar a rede de ignorar para eliminar toda a pilha TCP / IP. No entanto, acho que o gargalo é o Apache.
Erik Giberti
0

Essa carga é gerada por uma ferramenta ou cargas do mundo real?

Você pode verificar o memcached. Vi problemas em altas taxas de conexão causando latência no aplicativo.

Se estiver usando um gerador de carga, o que você obtém ao acessar uma pequena página estática?

Durante as cargas, convém verificar a pilha da rede quanto a condições TIME_WAIT. Talvez você esteja preenchendo sua fila de conexão.

Há mais de 100 razões e itens que você pode ver, mas sem mais informações, estou apenas dando palpites neste momento.

jeffatrackaid
fonte
Ele é testado através da URL ab-c 10 -t 10. Eu estou comparando com o próprio servidor, portanto a rede não deve ser o problema. Publiquei mais referências por sua solicitação.
BarsMonster
Eu não gastaria muito esforço sintonizando com ab. Você pode achar que isso não se traduz bem no desempenho do mundo real. O que você pode querer fazer é dissecar seu aplicativo e testar cada componente. Por exemplo, acerte o servidor apache diretamente com apenas uma página estática muito pequena. Isso lhe dará uma idéia do seu máximo de req / s no back-end. Coloque o nginx na frente e teste novamente chamando o mesmo arquivo de back-end. Em seguida, teste com uma página php simples do tipo "olá mundo" Às vezes, todas as camadas podem mascarar algo simples. Além disso, observe as conexões durante o teste. Verifique se a pilha de rede não está enchendo.
jeffatrackaid
Fiz esses benchmarks ontem e eles estão na descrição original atualizada da pergunta. Além disso, os testes são feitos no host local, portanto, a rede não é um problema.
BarsMonster
A rede pode ser um problema mesmo quando feito em um host local. Não é provável no seu caso, mas pode causar problemas. Pelo menos agora você tem um limite superior de ~ 450 req / s com sua configuração atual do PHP. O próximo passo é fazer uma chamada de banco de dados e ver como isso muda. Gosto de dividir isso ao fazer ajustes de alto nível, pois pode realmente ajudá-lo a identificar a camada que causa mais problemas.
jeffatrackaid
-1

99% das vezes que problemas como esse são rastreados no banco de dados. Certifique-se de que seus índices de acerto sejam os primeiros. Se isso não funcionar, comece a armazenar em cache tudo o que puder.


fonte
É todos os índices e, como eu estava dizendo, é ainda bater cache de consultas MySQL em 100% dos casos
BarsMonster
-1

Eu recomendo que você use (se possível) um pool de conexões para manter o banco de dados conectado aos seus aplicativos da Web (não é necessário reconectar a cada solicitação). Isso pode fazer uma enorme diferença de velocidade.

Além disso, tente analisar todas as suas consultas com EXPLAIN (e por que não criar um perfil de suas consultas com SHOW PROFILE?).

Kedare
fonte
Todas as consultas usam índices. O pool de conexões MySQL é usado.
BarsMonster