Qcache_free_memory pas encore plein Je reçois beaucoup de Qcache_lowmem_prunes

11

Je viens de commencer à essayer le cache de requêtes pour notre CMS.

Quelqu'un peut -il me dire (ou au moins donner une bonne estimation) pourquoi je reçois beaucoup de Qcache_lowmem_prunesquand plus de la moitié Qcache_free_memoryest libre?

query_cache_size=512M
query_cache_limit=1M

Voici à quoi cela ressemble après environ 12 heures

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    | 
+-------------------------+-----------+

Voilà comment cela s'est passé 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
la source

Réponses:

21

Le cache de requêtes est une fonctionnalité très intéressante, mais ne soyez pas tenté d'y prêter trop d'attention et ne soyez pas tenté de le rendre trop volumineux. Comprendre certains de ses éléments internes sera probablement utile à cet égard.

Le cache de requêtes commence comme un gros morceau contigu de mémoire disponible. Ensuite, des "blocs" sont découpés dans ce gros bloc:

  • chaque requête mise en cache prend un bloc
  • son ensemble de résultats compagnon prend un bloc
  • chaque table référencée par une requête mise en cache (quel que soit le nombre de requêtes référençant cette table dans le cache) prend également un bloc, un par table.

La taille de bloc est dynamique, mais le serveur alloue un minimum d' query_cache_min_res_unitoctets par bloc, avec une valeur par défaut typique de 4096 octets.

Chaque fois que les requêtes, les résultats qui les accompagnent et les références de table sont supprimés du cache, soit en devenant invalides par la modification des tables sous-jacentes, soit en élaguant pour faire de la place pour les requêtes plus récentes, cela laisse de nouveaux trous de la taille, quelle que soit la taille de ces blocs, et le nombre de "blocs libres" augmente généralement ... bien que si deux blocs contigus ou plus sont libérés, le nombre de "blocs libres" n'augmente que de 1, et les "blocs libres" n'augmenteront pas du tout si le nouveau- les blocs libérés sont contigus à un bloc déjà libre - la taille de ce bloc libre devient simplement plus grande. Tout bloc de mémoire libre ouvert dans le cache de requêtes est compté comme 1 bloc libre.

Bien sûr, un bloc libre plus petit que query_cache_min_res_unitne sera pas utilisé du tout.

Ainsi, les fragments de cache de requête. Si le serveur veut mettre en cache une nouvelle requête et qu'aucun bloc libre de taille suffisante ne peut être organisé (cette description est d'une simplicité trompeuse, car l'algorithme sous-jacent est compliqué), quelque chose d'autre doit être élagué ... c'est votre Qcache_lowmem_prunes. Il existe un algorithme «LRU (moins récemment utilisé) qui décide de ce qui doit être élagué.

Il serait judicieux de se demander pourquoi le serveur ne défragmente pas la mémoire ... mais cela n'aurait aucun sens. Le cache de requêtes aide quand il le peut, mais ce n'est pas du tout stratégique. Vous ne voulez pas investir le temps de traitement (en particulier le temps passé dans un verrou global) avec des tâches de maintenance inutiles.

Il serait contre-productif pour le serveur de passer du temps à réorganiser - défragmenter - la mémoire dans le cache de requêtes, car les résultats mis en cache changent constamment et tout l'intérêt du cache est d'améliorer les performances.

Le verrouillage global est une très bonne raison pour laquelle vous ne voulez pas utiliser un cache de requêtes trop volumineux ... le serveur y passera trop de temps car les requêtes attendent leur tour pour voir si elles sont mises en cache et vos performances en souffriront .

Mais qcache_free_blocksc'est essentiellement un indicateur de la fragmentation de l'espace libre. C'est maintenant que de nombreux blocs non contigus de mémoire disponible existent dans le cache de requête. Pour qu'une nouvelle requête soit insérée dans le cache, il doit y avoir un bloc d'espace suffisamment grand pour contenir la requête, ses résultats et (parfois) ses références de table. S'il n'y en a pas, alors il faut que quelque chose d'autre aille ... c'est ce que vous voyez. Notez, encore une fois, que l'espace disponible ne doit pas nécessairement être contigu (d'après ce que je peux dire en lisant le code source), mais tous les trous ne seront pas remplis en cas de fragmentation.

Mais la fragmentation tend à se stabiliser au fil du temps, pour une charge de travail donnée, car rien ne reste généralement dans le cache de requêtes aussi longtemps que vous pouvez vous y attendre.

En effet, à certains égards, le cache de requêtes est brillant dans sa simplicité.

Chaque fois que les données d'une table référencée par une requête mise en cache changent, toutes les requêtes impliquant cette table sont supprimées du cache, même si la modification n'a pas d'impact sur les résultats mis en cache. Cela est même vrai si une table change, mais ne change pas, comme dans le cas d'une transaction InnoDB qui est annulée. Les entrées du cache de requêtes faisant référence à cette table ont déjà été purgées.

En outre, le cache de requêtes est vérifié pour chaque requête entrante avant que le serveur analyse réellement la requête. La seule chose qui correspondra est une autre requête qui était exactement la même, octet par octet. SELECT * FROM my_tableet select * from my_tablene sont pas identiques octet par octet, de sorte que le cache de requêtes ne se rend pas compte qu'il s'agit de la même requête.

FLUSH QUERY CACHEne vide pas le cache de requête. Il défragmente le cache de requêtes, c'est pourquoi Qcache_free_blocksdevient "1". Tout l'espace libre est consolidé.

RESET QUERY CACHE vide en fait (efface tout le contenu de) le cache de requêtes.

FLUSH STATUSefface les compteurs, mais ce n'est pas quelque chose que vous voulez faire régulièrement car cela met à zéro la plupart des variables d'état dans SHOW STATUS.

Voici quelques démonstrations rapides.

Référence:

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        |
+-------------------------+----------+

Exécutez une requête ...

mysql> select * from junk where id = 2;

Le nombre total de blocs a augmenté de 3, les insertions de 1 et les requêtes dans le cache sont de 1.

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

Exécutez la même requête, mais avec des majuscules différentes ...

mysql> SELECT * FROM junk where id = 2;

Cette requête a été mise en cache séparément. Le nombre total de blocs n'a augmenté que de 2 car nous avions déjà un bloc alloué pour la table.

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

Maintenant, nous changeons une ligne différente dans le tableau.

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

Les requêtes et la référence de table sont invalidées à partir du cache, nous laissant avec 1 bloc libre contigu, toute la mémoire cache libérée et tout l'espace libre consolidé en un seul bloc.

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

MySQL ne stockera pas une requête dans le cache qui n'est pas déterministe - comme SELECT NOW();ou toute requête que vous lui demandez spécifiquement de ne pas mettre en cache. SELECT SQL_NO_CACHE ...est la directive pour dire au serveur de ne pas stocker les résultats dans le cache. Il est utile pour comparer le temps d'exécution réel d'une requête lorsque le cache vous donne une réponse trompeusement rapide sur les exécutions suivantes.

Michael - sqlbot
la source
Dans vos exemples, est-il exact que query_cache_min_res_unit = 512? la mémoire libre diminue de 512 * 3 entre 1 et 4 blocs utilisés et de 512 * 2 entre 4 et 6 blocs utilisés.
aland
1
@aland c'est un très bon point. Non, j'aurais dû utiliser la valeur par défaut de 4096. Il semble que le cache de requêtes réduit le bloc à la plus petite puissance possible de deux après l'avoir rempli, en laissant l'espace libre à la fin, de sorte que les 7/8 des les 4096 octets alloués à l'origine ne sont pas bloqués. Je vais devoir approfondir cela.
Michael - sqlbot