Plugin Memcached: NoSQL en MySQL



Hola Mi nombre es Maxim Matyukhin, soy programador de PHP en Badoo . En nuestro trabajo, utilizamos activamente MySQL. Pero a veces nos falta su rendimiento, por lo que constantemente buscamos formas de acelerar su trabajo.

En 2010, Yoshinori Matsunobu introdujo el complemento NoSQL MySQL llamado HandlerSocket. Se afirmó que este complemento le permite realizar más de 750,000 solicitudes por segundo. Nos volvimos curiosos y casi de inmediato comenzamos a usar esta solución. Nos gustó tanto el resultado que comenzamos a hacer presentaciones y escribir artículos promocionando HandlerSocket.

Aparentemente, fuimos uno de los pocos usuarios de este complemento, ya que MySQL 5.7 dejó de funcionar. Pero en esta versión apareció otro complemento de Oracle: el complemento IncaDB memcached, que prometía una funcionalidad similar.

A pesar de que el complemento memcached apareció de nuevo en MySQL 5.6 en 2013, no hay tantos artículos al respecto y, en su mayor parte, repiten la documentación: se crea una etiqueta simple y se realizan solicitudes a través del cliente memcached.

Tenemos una amplia experiencia con Memcached y estamos acostumbrados a la facilidad de interactuar con él. Desde el complemento memcached de InnoDB esperábamos la misma simplicidad. Pero, de hecho, resultó que si los patrones para usar el complemento son al menos ligeramente diferentes de los descritos en la documentación y los artículos, aparecerán muchos matices y limitaciones, que definitivamente vale la pena considerar si va a usar el complemento.

MySQL HandlerSocket


En este artículo, compararemos de una forma u otra el nuevo complemento memcached con el antiguo HandlerSocket. Por lo tanto, recuerdo que fue lo último.

Después de instalar el complemento HandlerSocket, MySQL comenzó a escuchar dos puertos adicionales:

  1. El primer puerto recibió solicitudes de clientes para leer datos.
  2. El segundo puerto recibió solicitudes del cliente para la grabación de datos.

El cliente tenía que establecer una conexión TCP regular en uno de estos puertos (no se admitía autenticación), y después de eso era necesario enviar el comando "abrir índice" (un comando especial con el que el cliente informaba a qué tabla de qué índice a qué campos íbamos a leer (o escribir)).

Si el comando "abrir índice" funcionó correctamente, puede enviar comandos GET o INSERT / UPDATE / DELETE dependiendo del puerto al que se estableció la conexión.

HandlerSocket permitió realizar no solo GET en la clave primaria, sino también muestras simples de un índice no exclusivo, muestras de rango, multigets compatibles y LIMIT. Al mismo tiempo, fue posible trabajar con la tabla tanto desde SQL ordinario como a través del complemento. Esto, por ejemplo, le permitió hacer algunos cambios en las transacciones a través de SQL y luego leer estos datos a través de HandlerSocket.

Es importante que HandlerSocket manejara todas las conexiones con un grupo limitado de subprocesos a través de epoll, por lo que fue fácil admitir decenas de miles de conexiones, mientras que en MySQL se creó un subproceso para cada conexión y su número era muy limitado.

Al mismo tiempo, sigue siendo un servidor MySQL ordinario, una tecnología que nos es familiar. Sabemos cómo replicarlo y monitorearlo. Monitorear HandlerSocket es difícil porque no proporciona ninguna métrica específica; sin embargo, algunas de las métricas estándar de MySQL e InnoDB son útiles.

Hubo, por supuesto, inconvenientes, en particular, este complemento no era compatible con el tipo de marca de tiempo. Bueno, el protocolo HandlerSocket es más difícil de leer y, por lo tanto, más difícil de depurar.

Lea más sobre HandlerSocket aquí . También puedes ver una de nuestras presentaciones .

Complemento memcached de InnoDB


¿Qué nos ofrece el nuevo plugin memcached?

Como su nombre lo indica, su idea es usar el cliente memcached para trabajar con MySQL y recibir y guardar datos a través de comandos memcached.

Puede leer sobre las principales ventajas del complemento aquí .

Estamos más interesados ​​en lo siguiente:

  1. Bajo consumo de CPU.
  2. Los datos se almacenan en InnoDB, lo que brinda ciertas garantías.
  3. Puede trabajar con datos tanto a través de Memcached como a través de SQL; Se pueden replicar utilizando las herramientas integradas de MySQL.

Puede agregar más ventajas a esta lista como:

  1. Conexión rápida y económica. Un subproceso procesa una conexión MySQL normal, y el número de subprocesos es limitado, y en el complemento memcached, un subproceso procesa todas las conexiones en el bucle de eventos.
  2. La capacidad de solicitar varias claves con una solicitud GET.
  3. Si se compara con MySQL HandlerSocket, entonces en el plugin memcached no necesita usar el comando "Abrir tabla" y todas las operaciones de lectura y escritura ocurren en el mismo puerto.


Se pueden encontrar más detalles sobre el complemento en la documentación oficial. Para nosotros, las páginas más útiles fueron:

  1. Arquitectura memcached de InnoDB .
  2. InnoDB Memcached Plugin Internals .

Después de instalar el complemento, MySQL comienza a aceptar conexiones en el puerto 11211 (puerto memcached estándar). También aparece una base de datos especial (esquema) innodb_memcache, en la que configurará el acceso a sus tablas.

Ejemplo simple


Supongamos que ya tiene una tabla con la que desea trabajar a través del protocolo 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 

y desea recibir y modificar datos en la clave primaria.

Primero debe describir la correspondencia entre la clave memcached y la tabla SQL en la tabla innodb_memcache.containers. Esta tabla se parece a esto (eliminé la descripción de codificación para que sea más fácil de leer):

 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 

Los campos más importantes:

  • nombre: prefijo de su clave Memcached;
  • db_schema - nombre de la base (circuito);
  • db_table es tu tabla;
  • key_columns: el nombre del campo en la tabla en el que buscaremos (generalmente esta es su clave principal);
  • value_columns: una lista de campos de la tabla que estará disponible para el complemento memcached;
  • unique_idx_name_on_key es el índice a buscar (aunque ya ha especificado key_columns, pueden estar en diferentes índices y debe especificar el índice explícitamente).

Los campos restantes no son muy importantes para empezar.

Agregue una descripción de nuestra tabla a 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'; 

En este ejemplo, name = 'auth' es el prefijo de nuestra clave memcached. En la documentación a menudo se llama table_id, y más adelante en el artículo usaré este término.

Ahora TELNET se conecta al plugin memcached e intenta guardar y obtener los datos:

 [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 

Primero enviamos una solicitud GET, no nos devolvió nada. Luego guardamos los datos con una solicitud SET, después de lo cual los recuperamos con un GET.

GET devolvió la siguiente línea: 1234567 | 89. Estos son los valores de los campos "contraseña" y "tipo", separados por el símbolo "|". Los campos se devuelven en el orden en que se describieron en innodb_memcache.containers.value_columns.

Quizás ahora se esté preguntando: "¿Qué sucederá si se encuentra el símbolo" | "en la" contraseña "?" Hablaré de esto a continuación.

A través de SQL, estos datos también están 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 predeterminado


También existe tal modo de operación:

 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 

En este ejemplo, con get @@ auth, hacemos que table_id auth sea el prefijo predeterminado para esta conexión. Después de eso, todas las consultas posteriores se pueden hacer sin especificar table_id.

Hasta ahora, todo es simple y lógico. Pero si comienzas a entender, entonces hay muchos matices. Te diré lo que encontramos.

Matices


Almacenamiento en caché de la tabla innodb_memcache.containers


El complemento memcached lee la tabla innodb_memcache.containers una vez al inicio. Además, si un table_id desconocido llega a través del protocolo Memcached, el complemento lo busca en la tabla. Por lo tanto, puede agregar fácilmente nuevas claves (table_id), pero si desea cambiar la configuración de un table_id existente, debe reiniciar el complemento memcached:

 mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so"; 

Entre estas dos solicitudes, la interfaz Memcached no funcionará. Debido a esto, a menudo es más fácil crear un nuevo table_id que cambiar el existente y reiniciar el complemento.

Fue una sorpresa para nosotros que se describa un matiz tan importante de la operación del complemento en la página Adaptación de una aplicación memcached para la página InnoDB Plugin memcached , que no es un lugar muy lógico para dicha información.

Banderas, cas_column, expire_time_column


Estos campos son necesarios para simular algunas características de Memcached. La documentación para ellos es inconsistente. La mayoría de los ejemplos en él ilustran el trabajo con tablas en las que se encuentran estos campos. Puede haber una preocupación de que necesitará agregarlos a sus tablas (y estos son al menos tres campos INT). Pero no Si no tiene dichos campos en las tablas y no va a utilizar la funcionalidad de Memcached como CAS, caducidad o marcas, entonces no necesita agregar estos campos a las tablas.

Al configurar la tabla en innodb_memcache.containers, debe ingresar '0' en estos campos, hacer exactamente la línea con cero:

 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'; 

Es molesto que cas_column y expire_time_column tengan un valor predeterminado de NULL, y si ejecuta INSERT INTO innodb_memcache.containers sin especificar un valor de '0' para estos campos, NULL se almacenará en ellos y este prefijo memcache simplemente no funcionará.

Tipos de datos


De la documentación no está muy claro qué tipos de datos se pueden usar cuando se trabaja con el complemento. En varios lugares se dice que el complemento solo puede funcionar con campos de texto (CHAR, VARCHAR, BLOB). Aquí: la adaptación de un esquema MySQL existente para el complemento memcached de InnoDB ofrece almacenar números en campos de cadena, y si luego necesita trabajar con estos campos de números desde SQL, cree una VISTA en la que los campos VARCHAR con números se convertirán en campos INTEGER :

 CREATE VIEW numbers AS SELECT c1 KEY, CAST(c2 AS UNSIGNED INTEGER) val FROM demo_test WHERE c2 BETWEEN '0' and '9999999999'; 

Sin embargo, en algunos lugares de la documentación todavía está escrito que puede trabajar con números. Hasta ahora, solo tenemos experiencia de producción real con campos de texto, pero los resultados experimentales muestran que el complemento también funciona con números:

 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'; 

Después de eso, a través del protocolo Memcached:

 get @@numbers.1 END set @@numbers.1 0 0 2 12 STORED get @@numbers.1 VALUE @@numbers.1 0 2 12 END 

Vemos que el complemento memcached puede devolver cualquier tipo de datos. Pero los devuelve en la forma en que se encuentran en InnoDB, por lo que, por ejemplo, en el caso de marca de tiempo / fecha / hora / float / decimal / JSON, se devuelve una cadena binaria. Pero los enteros se devuelven cuando los vemos a través de SQL.

Multiget


El protocolo memcached le permite solicitar varias claves con una sola solicitud:

 get @@numbers.2 @@numbers.1 VALUE @@numbers.2 0 2 12 VALUE @@numbers.1 0 2 13 END 

El hecho de que el multiget funciona ya es bueno. Pero funciona dentro del marco de un table_id:

 get @@auth.ivan@example.com @@numbers.2 VALUE @@auth.ivan@example.com 0 10 qwerty|xxx END 

Este punto se describe en la documentación aquí: https://dev.mysql.com/doc/refman/8.0/en/innodb-memcached-multiple-get-range-query.html . Resulta que en multiget puede especificar table_id solo para la primera clave, si todas las demás claves se toman del valor predeterminado table_id (ejemplo de la documentación):

 get @@aaa.AA BB VALUE @@aaa.AA 8 12 HELLO, HELLO VALUE BB 10 16 GOODBYE, GOODBYE END 

En este ejemplo, la segunda clave se toma del valor predeterminado table_id. Podríamos especificar muchas más claves del valor predeterminado table_id, y para la primera clave especificamos un table_id separado, y esto es posible solo en el caso de la primera clave.

Podemos decir que multiget funciona dentro del marco de una tabla, porque no tiene ganas de confiar en esa lógica en el código de producción: no es obvio, es fácil olvidarse de eso, cometer un error.

Si se compara con HandlerSocket, entonces, también, multiget funcionó en la misma tabla. Pero esta restricción parecía natural: el cliente abre el índice en la tabla y le solicita uno o más valores. Pero cuando se trabaja con el plugin multiget memcached en varias teclas con diferentes prefijos, esta es una práctica normal. Y espera lo mismo del complemento de MySQL memcached. Pero no :(

INCR, DEL


Ya he dado ejemplos de solicitudes GET / SET. Las consultas INCR y DEL tienen una función. Se basa en el hecho de que solo funcionan cuando se usa el valor predeterminado table_id:

 DELETE @@numbers.1 ERROR get @@numbers VALUE @@numbers 0 24 test/numbers END delete 1 DELETED 

Limitaciones de protocolo de Memcached


Memcached tiene un protocolo de texto, que impone algunas limitaciones. Por ejemplo, las teclas memcached no deben contener caracteres de espacio en blanco (espacio, avance de línea). Si vuelve a mirar la descripción de la tabla de nuestro ejemplo:

 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 

Esto significa que en el campo "correo electrónico" no debería haber tales caracteres.

Además, las claves de memoria caché deben tener menos de 250 bytes (bytes, no caracteres). Si envía más, recibirá un error:

 "CLIENT_ERROR bad command line format" 

Además, se debe tener en cuenta el hecho de que el complemento memcached agrega su propia sintaxis al protocolo memcached. Por ejemplo, usa el carácter "|" como un separador de campo en la respuesta. Debe asegurarse de que este símbolo no se use en su tabla. El separador se puede configurar, pero la configuración se aplicará a todas las tablas en todo el servidor MySQL.

Delimitador de campo value_columns


Si necesita devolver varias columnas a través del protocolo memcached, como en nuestro primer ejemplo:

 get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END 

entonces los valores de columna están separados por el separador estándar "|". Surge la pregunta: "¿Qué pasará si, por ejemplo, el carácter" | "está en el primer campo de la línea?" El complemento memcached en este caso devolverá la cadena como está, algo como esto: 1234 | 567 | 89. En el caso general, es imposible entender dónde hay un campo.

Por lo tanto, es importante elegir el separador correcto de inmediato. Y dado que se usará para todas las claves de todas las tablas, debe ser un carácter universal que no se encontrará en ningún campo con el que trabaje a través del protocolo memcached.

Resumen


Esto no quiere decir que el complemento memcached sea malo. Pero parece que fue escrito para un esquema de trabajo específico: un servidor MySQL con una tabla a la que se puede acceder utilizando el protocolo memcached, y este table_id se establece de manera predeterminada. Los clientes establecen una conexión persistente con el complemento Memcached y realizan solicitudes al valor predeterminado table_id. Probablemente, en tal esquema, todo funcionará sin problemas. Si te alejas de él, te encuentras con varios inconvenientes.

Es posible que haya esperado ver algunos informes de rendimiento del complemento. Pero aún no hemos decidido usarlo en lugares muy cargados. Lo usamos solo en algunos sistemas no muy cargados y allí funciona a la misma velocidad que el HandlerSocket, pero no hicimos puntos de referencia honestos. Sin embargo, el complemento proporciona una interfaz con la que el programador puede cometer fácilmente un error: debe tener en cuenta muchos matices. Por lo tanto, todavía no estamos listos para usar este complemento en forma masiva.

Hicimos algunas solicitudes de funciones en el rastreador de errores de MySQL:

https://bugs.mysql.com/bug.php?id=95091
https://bugs.mysql.com/bug.php?id=95092
https://bugs.mysql.com/bug.php?id=95093
https://bugs.mysql.com/bug.php?id=95094

Esperemos que el equipo de desarrollo del complemento memcached mejore su producto.

Source: https://habr.com/ru/post/453742/


All Articles