
Bonjour Je m'appelle Maxim Matyukhin, je suis programmeur PHP chez
Badoo . Dans notre travail, nous utilisons activement MySQL. Mais parfois, nous manquons de performances, nous recherchons donc constamment des moyens d'accélérer son travail.
En 2010, Yoshinori Matsunobu a introduit le plugin NoSQL MySQL appelé HandlerSocket. Il a été affirmé que ce plugin vous permet d'effectuer plus de 750 000 requêtes par seconde. Nous sommes devenus curieux et presque immédiatement, nous avons commencé à utiliser cette solution. Nous avons tellement aimé le résultat que nous avons commencé à faire des
présentations et à rédiger des
articles faisant la promotion de HandlerSocket.
Apparemment, nous étions l'un des rares utilisateurs de ce plugin - depuis MySQL 5.7, il a cessé de fonctionner. Mais dans cette version, un autre plugin Oracle est apparu - le plugin InnoDB memcached, qui promettait des fonctionnalités similaires.
Malgré le fait que le plugin memcached soit réapparu dans MySQL 5.6 en 2013, il n'y a pas tellement d'articles à ce sujet et pour la plupart ils répètent la documentation: une simple étiquette est créée et des requêtes lui sont faites via le client memcached.
Nous avons une vaste expérience avec Memcached et sommes habitués à la facilité d'interagir avec lui. Du plugin memcached InnoDB, nous nous attendions à la même simplicité. Mais en fait, il s'est avéré que si les modèles d'utilisation du plug-in sont au moins légèrement différents de ceux décrits dans la documentation et les articles, de nombreuses nuances et limitations apparaissent, qui valent vraiment la peine d'être considérées si vous allez utiliser le plug-in.
MySQL HandlerSocket
Dans cet article, nous comparerons d'une manière ou d'une autre le nouveau plugin memcached avec l'ancien HandlerSocket. Par conséquent, je me souviens que c'était ce dernier.
Après avoir installé le plugin HandlerSocket, MySQL a commencé à écouter deux ports supplémentaires:
- Le premier port a reçu des demandes des clients pour la lecture des données.
- Le deuxième port a reçu des demandes d'enregistrement de données du client.
Le client devait établir une connexion TCP régulière sur l'un de ces ports (aucune authentification n'était prise en charge), et après cela, il était nécessaire d'envoyer la commande «open index» (une commande spéciale avec laquelle le client informait quelle table de quel index quels champs nous allions vers lire (ou écrire)).
Si la commande «open index» a fonctionné correctement, vous pouvez envoyer des commandes GET ou INSERT / UPDATE / DELETE selon le port sur lequel la connexion a été établie.
HandlerSocket a permis d'effectuer non seulement des GET sur la clé primaire, mais également des échantillons simples à partir d'un index non unique, des échantillons de plage, des multigets pris en charge et LIMIT. Dans le même temps, il était possible de travailler avec la table à la fois à partir du SQL ordinaire et via le plugin. Cela, par exemple, vous a permis d'apporter des modifications aux transactions via SQL, puis de lire ces données via HandlerSocket.
Il est important que HandlerSocket gère toutes les connexions avec un pool limité de threads via epoll, il était donc facile de prendre en charge des dizaines de milliers de connexions, tandis que dans MySQL lui-même, un thread a été créé pour chaque connexion et leur nombre était très limité.
En même temps, il s'agit toujours d'un serveur MySQL ordinaire - une technologie qui nous est familière. Nous savons comment le répliquer et le surveiller. La surveillance de HandlerSocket est difficile car elle ne fournit aucune métrique spécifique; cependant, certaines des métriques standard MySQL et InnoDB sont utiles.
Il y avait, bien sûr, des inconvénients, en particulier, ce plugin ne supportait pas de travailler avec le type d'horodatage. Eh bien, le protocole HandlerSocket est plus difficile à lire et donc plus difficile à déboguer.
En savoir plus sur HandlerSocket
ici . Vous pouvez également regarder l'
une de nos présentations .
Plugin Memo caché InnoDB
Que nous offre le nouveau plugin memcached?
Comme son nom l'indique, son idée est d'utiliser le client memcached pour travailler avec MySQL et pour recevoir et enregistrer des données via des commandes memcached.
Vous pouvez
lire ici les principaux avantages du plugin.
Nous sommes particulièrement intéressés par ce qui suit:
- Faible consommation CPU.
- Les données sont stockées dans InnoDB, ce qui donne certaines garanties.
- Vous pouvez travailler avec des données à la fois via Memcached et via SQL; ils peuvent être répliqués à l'aide des outils intégrés de MySQL.
Vous pouvez ajouter des avantages à cette liste comme:
- Connexion rapide et bon marché. Une connexion MySQL régulière est traitée par un thread, et le nombre de threads est limité, et dans le plugin memcached, un thread traite toutes les connexions dans la boucle d'événements.
- La possibilité de demander plusieurs clés avec une seule demande GET.
- Par rapport à MySQL HandlerSocket, dans le plugin memcached, vous n'avez pas besoin d'utiliser la commande «Open Table» et toutes les opérations de lecture et d'écriture se produisent sur le même port.
Plus de détails sur le plugin peuvent être trouvés dans la
documentation officielle. Pour nous, les pages les plus utiles sont:
- Architecture memcached InnoDB .
- InnoDB Memcached Plugin Internals .
Après avoir installé le plugin, MySQL commence à accepter les connexions sur le port 11211 (port memcached standard). Une base de données spéciale (schéma) innodb_memcache apparaît également, dans laquelle vous allez configurer l'accès à vos tables.
Exemple simple
Supposons que vous ayez déjà une table avec laquelle vous souhaitez travailler via le protocole memcached:
CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
et vous souhaitez recevoir et modifier des données sur la clé primaire.
Vous devez d'abord décrire la correspondance entre la clé memcached et la table SQL dans la table innodb_memcache.containers. Ce tableau ressemble à ceci (j'ai supprimé la description de l'encodage pour en faciliter la lecture):
CREATE TABLE `containers` ( `name` varchar(50) NOT NULL, `db_schema` varchar(250) NOT NULL, `db_table` varchar(250) NOT NULL, `key_columns` varchar(250) NOT NULL, `value_columns` varchar(250) DEFAULT NULL, `flags` varchar(250) NOT NULL DEFAULT '0', `cas_column` varchar(250) DEFAULT NULL, `expire_time_column` varchar(250) DEFAULT NULL, `unique_idx_name_on_key` varchar(250) NOT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT
Les domaines les plus importants:
- nom - préfixe de votre clé Memcached;
- db_schema - nom de la base (circuit);
- db_table est votre table;
- colonnes_clé - le nom du champ dans la table par lequel nous allons rechercher (généralement c'est votre clé primaire);
- value_columns - une liste de champs de la table qui seront disponibles pour le plugin memcached;
- unique_idx_name_on_key est l'index par lequel effectuer la recherche (bien que vous ayez déjà spécifié key_columns, ils peuvent se trouver dans différents index et vous devez spécifier explicitement l'index).
Les champs restants ne sont pas très importants pour commencer.
Ajoutez une description de notre table à innodb_memcache.containers:
INSERT INTO innodb_memcache.containers SET name='auth', db_schema='test', db_table='auth', key_columns='email', value_columns='password|type', flags='0', cas_column='0', expire_time_column='0', unique_idx_name_on_key='PRIMARY';
Dans cet exemple, name = 'auth' est le préfixe de notre clé memcached. Dans la documentation, il est souvent appelé table_id, et plus loin dans l'article, j'utiliserai ce terme.
Maintenant, TELNET se connecte au plugin memcached et essaie d'enregistrer et d'obtenir les données:
[21:26:22] maxm@localhost: ~> telnet memchached-mysql.dev 11211 Trying 127.0.0.1... Connected to memchached-mysql.dev. Escape character is '^]'. get @@auth.max@example.com END set @@auth.max@example.com 0 0 10 1234567|89 STORED get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END
Nous avons d'abord envoyé une demande GET, elle ne nous a rien retourné. Ensuite, nous avons enregistré les données avec une demande SET, après quoi nous les avons récupérées avec un GET.
GET a renvoyé la ligne suivante: 1234567 | 89. Ce sont les valeurs des champs "mot de passe" et "type", séparées par le symbole "|". Les champs sont retournés dans l'ordre dans lequel ils ont été décrits dans innodb_memcache.containers.value_columns.
Peut-être vous demandez-vous maintenant: "Que se passera-t-il si le symbole" | "apparaît dans le" mot de passe "?" Je vais en parler ci-dessous.
Grâce à SQL, ces données sont également disponibles:
MySQL [(none)]> select * from auth where email='max@example.com'; +-----------------+----------+------+ | email | password | type | +-----------------+----------+------+ | max@example.com | 1234567 | 89 | +-----------------+----------+------+ 1 row in set (0.00 sec)
Table_id par défaut
Il existe également un tel mode de fonctionnement:
get @@auth VALUE @@auth 0 21 test/auth END get max@example.com VALUE max@example.com 0 10 1234567|99 END set ivan@example.com 0 0 10 qwerty|xxx STORED get ivan@example.com VALUE ivan@example.com 0 10 qwerty|xxx END
Dans cet exemple, avec get @@ auth, nous faisons de table_id auth le préfixe par défaut pour cette connexion. Après cela, toutes les requêtes suivantes peuvent être effectuées sans spécifier table_id.
Jusqu'à présent, tout est simple et logique. Mais si vous commencez à comprendre, il existe de nombreuses nuances. Je vais vous dire ce que nous avons trouvé.
Nuances
Mise en cache de la table innodb_memcache.containers
Le plugin memcached lit la table innodb_memcache.containers une fois au démarrage. De plus, si un table_id inconnu arrive via le protocole Memcached, le plugin le recherche dans la table. Par conséquent, vous pouvez facilement ajouter de nouvelles clés (table_id), mais si vous souhaitez modifier les paramètres d'un table_id existant, vous devez redémarrer le plugin memcached:
mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so";
Entre ces deux requêtes, l'interface Memcached ne fonctionnera pas. Pour cette raison, il est souvent plus facile de créer un nouveau table_id que de changer l'existant et de redémarrer le plugin.
Ce fut une surprise pour nous qu'une telle nuance importante du fonctionnement du plug-in soit décrite sur la page
Adapter une application memcached pour le plug-in
InnoDB memcached , qui n'est pas un endroit très logique pour de telles informations.
Drapeaux, cas_column, expire_time_column
Ces champs sont nécessaires pour simuler certaines fonctionnalités de Memcached. La documentation pour eux est incohérente. La plupart des exemples illustrent l'utilisation de tables dans lesquelles se trouvent ces champs. Il se peut que vous deviez les ajouter à vos tables (et ce sont au moins trois champs INT). Mais non. Si vous n'avez pas de tels champs dans les tables et que vous n'allez pas utiliser des fonctionnalités Memcached telles que CAS, expiration ou indicateurs, vous n'avez pas besoin d'ajouter ces champs aux tables.
Lors de la configuration de la table dans innodb_memcache.containers, vous devez entrer «0» dans ces champs, faire exactement la ligne avec zéro:
INSERT INTO innodb_memcache.containers SET name='auth', db_schema='test', db_table='auth', key_columns='email', value_columns='password|type', flags='0', cas_column='0', expire_time_column='0', unique_idx_name_on_key='PRIMARY';
Il est ennuyeux que cas_column et expire_time_column aient une valeur par défaut NULL, et si vous exécutez INSERT INTO innodb_memcache.containers sans spécifier une valeur de '0' pour ces champs, NULL y sera stocké et ce préfixe memcache ne fonctionnera tout simplement pas.
Types de données
D'après la documentation, il n'est pas très clair quels types de données peuvent être utilisés lorsque vous travaillez avec le plugin. À plusieurs endroits, il est dit que le plugin ne peut fonctionner qu'avec des champs de texte (CHAR, VARCHAR, BLOB). Ici: L'
adaptation d'un schéma MySQL existant pour le plugin Memo caché InnoDB propose de stocker des nombres dans des champs de chaîne, et si vous devez ensuite travailler avec ces champs numériques à partir de SQL, puis créez une VUE dans laquelle les champs VARCHAR avec des nombres seront convertis en champs INTEGER :
CREATE VIEW numbers AS SELECT c1 KEY, CAST(c2 AS UNSIGNED INTEGER) val FROM demo_test WHERE c2 BETWEEN '0' and '9999999999';
Cependant, à certains endroits de la documentation, il est toujours écrit que vous pouvez travailler avec des nombres. Jusqu'à présent, nous n'avons qu'une réelle expérience de production avec des champs de texte, mais les résultats expérimentaux montrent que le plugin fonctionne également avec des nombres:
CREATE TABLE `numbers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `counter` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB INSERT INTO innodb_memcache.containers SET name='numbers', db_schema='test', db_table='numbers', key_columns='id', value_columns='counter', flags='0', cas_column='0',expire_time_column='0',unique_idx_name_on_key='PRIMARY';
Après cela, via le protocole Memcached:
get @@numbers.1 END set @@numbers.1 0 0 2 12 STORED get @@numbers.1 VALUE @@numbers.1 0 2 12 END
Nous voyons que le plugin memcached peut renvoyer tous les types de données. Mais il les renvoie sous la forme dans laquelle ils se trouvent dans InnoDB, donc, par exemple, dans le cas de timestamp / datetime / float / decimal / JSON, une chaîne binaire est retournée. Mais les entiers sont retournés tels que nous les voyons via SQL.
Multiget
Le protocole memcached vous permet de demander plusieurs clés avec une seule demande:
get @@numbers.2 @@numbers.1 VALUE @@numbers.2 0 2 12 VALUE @@numbers.1 0 2 13 END
Le fait que le multiget fonctionne est déjà bon. Mais cela fonctionne dans le cadre d'un table_id:
get @@auth.ivan@example.com @@numbers.2 VALUE @@auth.ivan@example.com 0 10 qwerty|xxx END
Ce point est décrit dans la documentation ici:
https://dev.mysql.com/doc/refman/8.0/en/innodb-memcached-multiple-get-range-query.html . Il s'avère qu'en multiget, vous ne pouvez spécifier table_id que pour la première clé, si toutes les autres clés sont extraites de la table_id par défaut (exemple de la documentation):
get @@aaa.AA BB VALUE @@aaa.AA 8 12 HELLO, HELLO VALUE BB 10 16 GOODBYE, GOODBYE END
Dans cet exemple, la deuxième clé est extraite de la table_id par défaut. Nous pourrions spécifier beaucoup plus de clés à partir du table_id par défaut, et pour la première clé, nous avons spécifié un table_id distinct, et cela n'est possible que dans le cas de la première clé.
Nous pouvons dire que le multiget fonctionne dans le cadre d'une table, car vous n'avez pas envie de vous fier à une telle logique dans le code de production: ce n'est pas évident, c'est facile de l'oublier, de se tromper.
Si on le compare à HandlerSocket, alors là aussi, le multiget fonctionnait dans la même table. Mais cette restriction semblait naturelle: le client ouvre l'index dans la table et lui demande une ou plusieurs valeurs. Mais lorsque vous travaillez avec le plugin memcached multiget sur plusieurs clés avec des préfixes différents, c'est une pratique normale. Et vous attendez la même chose du plugin MySQL memcached. Mais non :(
INCR, DEL
J'ai déjà donné des exemples de requêtes GET / SET. Les requêtes INCR et DEL ont une fonctionnalité. Cela réside dans le fait qu'ils ne fonctionnent que lors de l'utilisation du table_id par défaut:
DELETE @@numbers.1 ERROR get @@numbers VALUE @@numbers 0 24 test/numbers END delete 1 DELETED
Limitations du protocole Memcached
Memcached possède un protocole de texte, qui impose certaines limitations. Par exemple, les clés memcached ne doivent pas contenir de caractères d'espace blanc (espace, saut de ligne). Si vous regardez à nouveau la description du tableau de notre exemple:
CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
cela signifie que dans le champ «e-mail», il ne devrait pas y avoir de tels caractères.
En outre, les clés memcached doivent être inférieures à 250 octets (octets, pas de caractères). Si vous en envoyez plus, vous obtenez une erreur:
"CLIENT_ERROR bad command line format"
De plus, il faut tenir compte du fait que le plugin memcached ajoute sa propre syntaxe au protocole memcached. Par exemple, il utilise le caractère "|" comme séparateur de champ dans la réponse. Vous devez vous assurer que ce symbole n'est pas utilisé dans votre tableau. Le séparateur peut être configuré, mais les paramètres s'appliqueront à toutes les tables sur l'ensemble du serveur MySQL.
Délimiteur de champ value_columns
Si vous devez renvoyer plusieurs colonnes via le protocole memcached, comme dans notre premier exemple:
get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END
les valeurs des colonnes sont ensuite séparées par le séparateur standard "|". La question se pose: "Que se passera-t-il si, par exemple, le caractère" | "est dans le premier champ de la ligne?" Le plugin memcached dans ce cas renverra la chaîne telle quelle, quelque chose comme ceci: 1234 | 567 | 89. Dans le cas général, il est impossible de comprendre où se trouve un champ.
Par conséquent, il est important de choisir tout de suite le bon séparateur. Et comme il sera utilisé pour toutes les clés de toutes les tables, ce devrait être un symbole universel qui ne sera trouvé dans aucun champ avec lequel vous travaillerez via le protocole memcached.
Résumé
Cela ne veut pas dire que le plugin memcached est mauvais. Mais on a l'impression qu'il a été écrit pour un schéma de travail spécifique: un serveur MySQL avec une table accessible à l'aide du protocole memcached, et ce table_id devient par défaut. Les clients établissent une connexion persistante avec le plug-in Memcached et font des requêtes à l'ID de table par défaut. Probablement, dans un tel schéma, tout fonctionnera parfaitement. Si vous vous en éloignez, vous rencontrez divers inconvénients.
Vous vous attendiez peut-être à voir certains rapports sur les performances des plugins. Mais nous n'avons pas encore décidé de l'utiliser dans des endroits très chargés. Nous ne l'avons utilisé que dans quelques systèmes peu chargés et là, il fonctionne à peu près à la même vitesse que le HandlerSocket, mais nous n'avons pas fait de benchmarks honnêtes. Mais néanmoins, le plugin fournit une telle interface avec laquelle le programmeur peut facilement faire une erreur - vous devez garder à l'esprit de nombreuses nuances. Par conséquent, nous ne sommes pas encore prêts à utiliser ce plugin en vrac.
Nous avons fait quelques demandes de fonctionnalités dans le suivi des bogues MySQL:
https://bugs.mysql.com/bug.php?id=95091https://bugs.mysql.com/bug.php?id=95092https://bugs.mysql.com/bug.php?id=95093https://bugs.mysql.com/bug.php?id=95094Espérons que l'équipe de développement du plugin memcached améliorera son produit.