J'ai une instance gérée MySQL (5.7.19) sur AWS. En général, les choses fonctionnent assez bien. Je suis constamment autour de 4% d'utilisation du processeur et bien en dessous de mes limites IOPS pour l'instance éclatable (t2.micro). Cependant, si je fais une requête qui n'utilise pas d'index sur une table qui est probablement tombée hors de RAM et se trouve sur le disque, MySQL se "verrouillera" pendant environ une minute. Lire Les IOPS augmentent, mais généralement pas assez pour puiser dans mon pool de crédits. Le processeur sera bloqué à 100% jusqu'à la fin de la requête. Les autres connexions du service en cours d'exécution seront mises en file d'attente (je commencerai à voir plus de 60 connexions), et beaucoup finiront par s'arrêter.
Voici un exemple de requête qui a verrouillé la base de données pendant près d'une minute:
SELECT *
FROM mydb.PurchaseDatabase
WHERE Time between '2018-11-20 00:00:00' AND '2018-11-23 00:00:00'
and ItemStatus=0
and ItemID="exampleitem";
Voici les mesures du tableau de bord RDS lorsque j'ai fait cette requête:
Si je lance la requête une deuxième fois, elle se termine presque instantanément (elle est maintenant probable dans RAM à partir de la requête récente sur la même table). Les requêtes similaires sont également à nouveau rapides (0,173 seconde, par exemple) ). J'ai activé la journalisation des requêtes lentes ainsi que la journalisation des erreurs et j'ai effectué la requête un jour plus tard avec le même délai de 30 secondes (la table avait été paginée ou autre en mémoire RAM). Cependant, rien n'était écrit dans la table des requêtes lentes. J'ai vérifié les journaux d'erreurs, et je peux voir des messages comme celui-ci lorsque je fais des requêtes lentes:
2018-11-28T06: 21: 05.498947Z 0 [Remarque] InnoDB: page_cleaner: la boucle prévue de 1000 ms a pris 37072 ms. Les paramètres peuvent ne pas être optimaux. (rincé = 4 et expulsé = 0, pendant le temps.)
Je pense que cela pourrait simplement être un autre symptôme du problème sous-jacent, c'est que mon instance a du mal à lire/écrire à partir du disque. J'utilise un stockage soutenu par SSD et mon solde de rafales sur le volume EBS n'est pas affecté par ces requêtes lentes. J'ai beaucoup de crédits avant et après les requêtes.
J'ai alors, bêtement, décidé d'essayer d'aider la base de données en effaçant les anciens enregistrements. J'ai effectué une suppression comme ceci:
DELETE FROM mydb.PurchaseDatabase
WHERE Time between '2018-01-01 00:00:00'
and '2018-07-31 00:00:00'
and ItemStatus=0;
Ce qui a affecté environ 50k des 190k lignes du tableau. Cette requête est "revenue" à MySQL Workbench en 0,505 seconde, mais a en fait supprimé la base de données pendant près de 8 minutes! Pendant ce temps, l'instance RDS n'a même pas pu écrire dans les journaux ou Cloudwatch.
Il a fallu 8 minutes pour libérer environ 6 Mo de lignes de base de données (le processeur est fixé à 100% pendant cette période). Je suis bien en dessous de l'utilisation du processeur en général et des IOPS pour la taille de mon instance. T2.micro n'est-il pas vraiment capable de gérer ce type de charge de travail? Puis-je faire quelque chose pour mieux comprendre ce qui se passe? J'ai également essayé d'écrire les journaux de performances, mais ils n'ont pas réussi à écrire pendant ce temps d'arrêt de 8 minutes, donc je ne suis jamais vraiment en mesure d'examiner le problème.
Après ce temps d'arrêt, le journal des erreurs contenait cet avertissement:
2018-11-28T18: 35: 59.497627Z 0 [Avertissement] InnoDB: Une longue attente de sémaphore: --Thread 47281889883904 a attendu à srv0srv.cc ligne 1982 pendant 250,00 secondes le sémaphore: X-lock sur RW-latch à 0x2b00a8fcf258 créé en fichier dict0dict.cc ligne 1184 un écrivain (id de fil 47281896187648) l'a réservé en mode nombre exclusif de lecteurs 0, indicateur de serveurs 1, lock_Word: 0 Dernière lecture verrouillée dans le fichier row0purge.cc ligne 862 Dernière écriture bloquée dans le fichier/local /mysql-5.7.19.R1/storage/innobase/dict/dict0stats.cc ligne 2375
Remarque: Cela supprime la base de données entière, pas seulement la table PurchaseDatabase. La file d'attente de connexion se remplit de requêtes non traitées jusqu'à ce que la file d'attente soit finalement pleine et qu'aucune autre connexion ne soit acceptée. Les anciennes connexions expirent finalement.
Je suppose que c'est une sorte d'interaction EBS/RDS, mais je ne vois tout simplement pas comment vous êtes censé pouvoir éclater à 3000 IOPS, mais je ne peux même pas gérer une rafale de lecture de 30 IOP. Toutes les suggestions seraient très appréciées, car je crains que ces problèmes ne commencent à apparaître pendant la charge de travail normale, car je ne comprends pas la cause première.
Dans cet exemple, PurchaseDatabase est créé avec l'instruction suivante.
CREATE TABLE PurchaseDatabase (
ID BIGINT UNSIGNED,
TitleID VARCHAR(127),
TransactionID BIGINT UNSIGNED,
SteamID BIGINT UNSIGNED,
State TINYINT UNSIGNED,
Time DATETIME,
TimeCreated DATETIME,
ItemID VARCHAR(127),
Quantity INT UNSIGNED,
Price INT UNSIGNED,
Vat INT,
ItemStatus TINYINT UNSIGNED,
PRIMARY KEY (ID, TitleID)
);
Au cas où cela serait utile, voici mon tableau de bord de performances pour cette base de données. Cela représente environ 50% de la charge de pointe attendue (nous constatons un fort cycle jour/nuit de 24 heures lorsque les utilisateurs se connectent pour jouer dans certains fuseaux horaires).
Notez également que ce problème n'est pas uniquement causé par la table PurchaseDatabase. Toute requête sur l'une de mes tables peut provoquer ce même problème.
En parcourant les journaux, je peux voir que le processeur est consommé par le système. Donc mysqld n'obtient probablement pas de temps CPU pour répondre aux demandes qui auraient pu être bien servies à partir du pool de tampons en mémoire. Cela n'a tout simplement pas eu de chance:
mysqld cpuUsedPc: 0.22
OS processes cpuUsedPc: 507.87
RDS processes cpuUsedPc: 2.03
L'OS semble être rattrapé en lecture/écriture sur EBS. Voici le disque IO pendant ce temps:
"diskIO": [
{
"writeKbPS": 807.33,
"readIOsPS": 25.92,
"await": 15.95,
"readKbPS": 105.8,
"rrqmPS": 0,
"util": 17.75,
"avgQueueLen": 63.77,
"tps": 66.62,
"readKb": 6348,
"device": "rdsdev",
"writeKb": 48440,
"avgReqSz": 13.71,
"wrqmPS": 0,
"writeIOsPS": 40.7
}
],
Donc, même si je n'atteins pas la limite de rafale de mes IOPS, je semble toujours être IO lié. Est-ce que cela semble correct?
"avgQueueLen": 63,77, - Aïe!
C'était l'échange! J'ai fini par répliquer la base de données sur le même matériel et j'ai écrit quelques scripts pour émuler le trafic en direct sur la base de données. J'ai également exécuté de grosses requêtes pour aider à remplir le pool de tampons, puis je me suis assuré que ma base de données de réplicas correspondait approximativement aux métriques de ma base de données de production. J'ai ensuite essayé d'exécuter de grandes requêtes contre lui et il s'est verrouillé, même avec des index appliqués. Je pouvais reproduire le problème sans supprimer le service de production, alors maintenant je peux casser les choses autant que je veux.
J'ai remarqué que l'exécution des mêmes requêtes volumineuses plus tôt dans la vie de la base de données de répliques n'avait pas causé de problème et j'ai retrouvé le point de départ des blocages. Cela se produit presque immédiatement après que le pool de mémoire tampon soit suffisamment volumineux pour pousser certaines données (OS ou autre) à échanger sur l'instance t2.micro. Voici une image de Cloudwatch du swap en croissance après que la mémoire libre puisse descendre en dessous de ~ 50 Mo environ:
Toutes les requêtes volumineuses (avec ou sans index) commencent à verrouiller la base de données après le cercle rouge. Vous pouvez voir l'utilisation globale du processeur pendant 5 minutes lorsque j'ai verrouillé la base de données pendant près d'une minute en effectuant une suppression.
Avec cette théorie à l'esprit, j'ai essayé deux solutions:
1) J'ai changé la valeur de innodb_buffer_pool_size à 375M (au lieu de sa valeur AWS par défaut de 3/4 l'instance RAM size). Cela réduit la taille maximale du pool de tampons et garantit que la base de données l'empreinte mémoire n'augmentera pas suffisamment pour pousser le système d'exploitation/etc dans le swap.
2) J'ai essayé d'exécuter la base de données sur une instance plus grande (2 Go de RAM). Cela a également fonctionné!
Ces deux solutions fonctionnent, et le bonus avec (1) est que je n'ai pas à dépenser d'argent supplémentaire. Je travaille sur le réglage de la valeur de innodb_buffer_pool_size afin qu'elle soit aussi grande que possible sans provoquer de swap. J'ai exécuté la même requête DELETE en 1.2s et la base de données a continué de répondre. La capture d'écran ci-dessous n'était même pas possible avec la base de données de production car la base de données cesserait de répondre pendant ces longues requêtes, de sorte que le tableau de bord ne se mettrait jamais à jour et ne perdrait finalement pas la connexion.
Pour commencer, ayez de meilleurs index. Pour le SELECT
:
INDEX(ItemID, ItemStatus, -- in either order
`Time`) -- then the range
Pour le DELETE
:
INDEX(ItemStatus, `Time`) -- in this order
(Non, il n'y a pas d'index unique optimal pour les deux requêtes.)
Cela devrait aider à la performance (CPU, IOPS, "verrouillage") et peut (ou peut-être pas) aider avec d'autres problèmes, tels que le page_cleaners
.
Environ combien de lignes dans le tableau? (140K maintenant?) Dans l'ensemble de résultats de ce SELECT
?
Plus...
innodb_file_per_table
est un problème de disposition du disque; non pertinent. Le principal accordable est le cache en RAM: innodb_buffer_pool_size
. Mais, avec seulement 1 Go de RAM, ce cache doit être assez petit. Cela expliquerait pourquoi une table de 190 000 lignes aurait besoin d'IOP.
Voici le scénario ... Les requêtes ne sont effectuées que dans le buffer_pool. C'est-à-dire que pour lire (ou écrire ou supprimer) une ligne, le bloc de 16 Ko dans lequel il vit (ou vivra) est d'abord introduit dans le buffer_pool. Le tableau mentionné pourrait être d'environ 50 Mo. Je suppose (sans le savoir) que le buffer_pool_size est défini pour environ 100 Mo. Les SELECT
et DELETE
en cours de discussion nécessitaient une analyse complète de la table, par conséquent 50 Mo seraient tirés dans le buffer_pool, poussant des blocs d'autres tables si nécessaire. Et vice versa quand quelque chose de gros se produit avec ces tables.
Plan A: ajoutez les index recommandés. Il peut y avoir d'autres façons de réduire l'empreinte du disque pour améliorer la cachabilité. Par exemple, BIGINT
est de 8 octets; INT
n'est que de 4 octets, mais a une limite de quelques milliards. Mais même MEDIUMINT
(3 octets, quelques millions), etc., peut suffire.
Plan B: Obtenez une configuration RDS plus importante.
Taux par seconde = suggestions RPS à considérer pour votre groupe de performances AWS
read_rnd_buffer_size=256K # from 512K reduce RAM/connect & handler_read_rnd_next RPS of 68
innodb_avg_flushing_loops=5 # from 30 to reduce innodb_buffer_pool_pages_dirty of 2,493
innodb_io_capacity=1900 # from 200 to use more of SSD IOPS capacity
thread_cache_size=100 # from 8 to reduce threads_created of 1,082
max_connections=96 # from 66 to reduce connection_errors_max_connections of 1678
innodb_log_buffer_size=24M # from 8M to support ~8 min logging
innodb_change_buffer_max_size=15 # from 25 percent since only 3% used
TOUS sont des variables dynamiques, ne nécessiteront pas d'arrêt/démarrage des services.
Pour des suggestions supplémentaires, consultez mon profil, profil réseau pour les coordonnées. Il y a BEAUCOUP plus de possibilités de réglage, même après avoir réussi à améliorer les performances de vos touches de table.