Qcache_free_memory ainda não está cheio. Recebo um monte de Qcache_lowmem_prunes

11

Comecei a mexer no cache de consultas do nosso CMS.

Alguém pode dizer-me (ou pelo menos dar um bom palpite) por que eu recebo um monte de Qcache_lowmem_prunesquando mais da metade dos Qcache_free_memoryé livre?

query_cache_size=512M
query_cache_limit=1M

É assim que fica depois de 12 horas

show status like '%qcach%';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| Qcache_free_blocks      | 10338     | 
| Qcache_free_memory      | 297348320 | 
| Qcache_hits             | 10254104  | 
| Qcache_inserts          | 6072945   | 
| Qcache_lowmem_prunes    | 725279    | 
| Qcache_not_cached       | 2237603   | 
| Qcache_queries_in_cache | 48119     | 
| Qcache_total_blocks     | 111346    | 
+-------------------------+-----------+

É assim que parecia flush query cache;

show status like '%qcach%';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| Qcache_free_blocks      | 1         | 
| Qcache_free_memory      | 443559256 | 
| Qcache_hits             | 10307015  | 
| Qcache_inserts          | 6115890   | 
| Qcache_lowmem_prunes    | 725279    | 
| Qcache_not_cached       | 2249405   | 
| Qcache_queries_in_cache | 26455     | 
| Qcache_total_blocks     | 54490     | 
+-------------------------+-----------+
Nifle
fonte

Respostas:

21

O cache de consulta é um recurso muito bom, mas não fique tentado a prestar muita atenção a ele e não fique muito grande. A compreensão de alguns de seus internos provavelmente ajudará nesse sentido.

O cache da consulta começa como um grande bloco contíguo de memória disponível. Então "blocos" são esculpidos neste grande bloco:

  • cada consulta em cache recebe um bloco
  • o conjunto de resultados complementar leva um bloco
  • cada tabela referenciada por qualquer consulta em cache (não importa quantas consultas que referenciam essa tabela estejam no cache) também recebe um bloco, um por tabela.

O tamanho do bloco é dinâmico, mas o servidor aloca um mínimo de query_cache_min_res_unitbytes por bloco, com um padrão típico de 4096 bytes.

Sempre que as consultas, os resultados que os acompanham e as referências de tabela são removidas do cache, tornando-se invalidadas pelas alterações nas tabelas subjacentes ou removendo espaço para consultas mais recentes, isso deixa novos buracos do tamanho do tamanho desses blocos e o número de "blocos livres" geralmente aumenta ... embora se dois ou mais blocos contíguos são liberados, o número de "blocos livres" aumenta apenas 1 e os "blocos livres" não aumentarão se os novos- blocos liberados são contíguos a um bloco já livre - o tamanho desse bloco livre se torna maior. Qualquer bloco aberto de memória livre no cache de consulta é contado como 1 bloco livre.

Obviamente, um bloco livre menor do que query_cache_min_res_unitnão será usado.

Portanto, o cache da consulta fragmenta. Se o servidor quiser armazenar em cache uma nova consulta e nenhum bloco livre de tamanho suficiente puder ser organizado (essa descrição é enganosamente simples, porque o algoritmo subjacente é complicado), outra coisa precisa ser removida ... essa é a sua Qcache_lowmem_prunes. Existe um algoritmo "menos usado recentemente" (LRU) que decide o que será removido.

Seria sensato perguntar por que o servidor não desfragmenta a memória ... mas isso não faria sentido. O cache da consulta ajuda quando pode, mas não é nada estratégico. Você não deseja investir o tempo de processamento (especialmente o tempo gasto em um bloqueio global) em tarefas desnecessárias de manutenção.

Seria contraproducente para o servidor gastar tempo reorganizando - desfragmentando - a memória no cache de consultas, pois os resultados armazenados em cache estão mudando constantemente e o objetivo principal do cache é melhorar o desempenho.

O bloqueio global é uma boa razão para você não usar um cache de consultas excessivamente grande ... o servidor passará muito tempo lá, pois as consultas aguardam a sua vez para ver se elas estão em cache e seu desempenho será prejudicado. .

Mas isso qcache_free_blocksé essencialmente um indicador de fragmentação do espaço livre. Agora, existem muitos blocos não contíguos de memória disponível no cache da consulta. Para que uma nova consulta seja inserida no cache, deve haver um pedaço grande de espaço livre suficiente para conter a consulta, seus resultados e (às vezes) suas referências de tabela. Se não houver, algo mais tem que acontecer ... o que você está vendo. Observe, novamente, que o espaço disponível nem sempre precisa necessariamente ser contíguo (pelo que posso ler lendo o código-fonte), mas nem todos os buracos serão preenchidos quando houver fragmentação.

Mas a fragmentação tende a se estabilizar com o tempo, para uma determinada carga de trabalho, já que normalmente nada fica no cache de consultas pelo tempo que você poderia esperar.

Isso ocorre porque, de certa forma, o cache da consulta é brilhante em sua simplicidade.

Sempre que os dados em uma tabela referenciada por uma consulta em cache são alterados, todas as consultas que envolvem essa tabela são removidas do cache - mesmo se a alteração não afetar os resultados em cache. Isso é verdade mesmo se uma tabela for alterada, mas não for alterada, como no caso de uma transação do InnoDB que é revertida. As entradas do cache de consulta que referenciam essa tabela já foram eliminadas.

Além disso, o cache da consulta é verificado para cada consulta recebida antes que o servidor realmente analise a consulta. A única coisa que corresponderá é outra consulta exatamente igual, byte por byte. SELECT * FROM my_tablee select * from my_tablenão são idênticos byte a byte, portanto, o cache da consulta não percebe que é a mesma consulta.

FLUSH QUERY CACHEnão esvazia o cache da consulta. Ele desfragmenta o cache da consulta, e é por isso que se Qcache_free_blockstorna "1". Todo o espaço livre é consolidado.

RESET QUERY CACHE na verdade libera (limpa todo o conteúdo) o cache da consulta.

FLUSH STATUSlimpa os contadores, mas isso não é algo que você deseja fazer rotineiramente porque zera a maioria das variáveis ​​de status SHOW STATUS.

Aqui estão algumas demonstrações rápidas.

Linha de base:

mysql> show status like '%qcache%';
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 67091120 |
| Qcache_hits             | 0        |
| Qcache_inserts          | 0        |
| Qcache_lowmem_prunes    | 0        |
| Qcache_not_cached       | 1        |
| Qcache_queries_in_cache | 0        |
| Qcache_total_blocks     | 1        |
+-------------------------+----------+

Execute uma consulta ...

mysql> select * from junk where id = 2;

O total de blocos aumentou em 3, as inserções em 1 e as consultas no cache são 1.

+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 67089584 |
| Qcache_inserts          | 1        |
| Qcache_queries_in_cache | 1        |
| Qcache_total_blocks     | 4        |
+-------------------------+----------+

Execute a mesma consulta, mas com letras maiúsculas diferentes ...

mysql> SELECT * FROM junk where id = 2;

Esta consulta foi armazenada em cache separadamente. O total de blocos aumentou apenas 2 porque já tínhamos um bloco alocado para a tabela.

+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 67088560 |
| Qcache_inserts          | 2        |
| Qcache_queries_in_cache | 2        |
| Qcache_total_blocks     | 6        |
+-------------------------+----------+

Agora, alteramos uma linha diferente na tabela.

mysql> update junk set things = 'items' where id = 1;

As consultas e a referência da tabela são invalidadas do cache, deixando-nos 1 bloco livre contíguo, toda a memória cache liberada e todo o espaço livre consolidado em um bloco.

+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 67091120 |
| Qcache_queries_in_cache | 0        |
| Qcache_total_blocks     | 1        |
+-------------------------+----------+

O MySQL não armazenará uma consulta no cache que não seja determinística - como SELECT NOW();qualquer consulta que você diga especificamente para não armazenar em cache. SELECT SQL_NO_CACHE ...é a diretiva para informar ao servidor para não armazenar os resultados no cache. É útil para comparar o verdadeiro tempo de execução de uma consulta quando o cache está fornecendo uma resposta enganosamente rápida nas execuções subseqüentes.

Michael - sqlbot
fonte
Nos seus exemplos, é correto que query_cache_min_res_unit = 512? a memória livre diminui 512 * 3 entre 1 e 4 blocos usados ​​e 512 * 2 entre 4 e 6 blocos usados.
Aland
1
@andand é um ponto muito bom. Não, eu deveria estar usando o valor padrão 4096. Parece que o cache da consulta reduz o bloco à menor potência possível de dois após preenchê-lo, deixando o espaço livre no final, de modo que 7/8 da 4096 bytes inteiros alocados originalmente não estão ociosos. Vou ter que me aprofundar mais nisso.
Michael - sqlbot