# rm-rf.es | Administración de sistemas

Bitácora personal de un SysAdmin Gnu/Linux, Windows, BSD...

MySQL tools: benchmarking con mysqlslap

MySQLMySQL, a partir de la versión 5.1 integra una utilidad llamada mysqlslap que permite estresar y hacer benchmarking del servidor MySQL. Gracias a mysqlslap podremos comparar el rendimiento del servidor ante cambios realizados en las bases de datos, motores de almacenamiento y configuraciones así como con X número de clientes interactuando con las bases de datos, cierto tipo de consultas SQL, etc.

Si queremos comenzar haciendo tests básicos podemos dejar que sea la propia utilidad la que genere de forma aleatoria las consultas SQL, únicamente tenemos que especificar el usuario MySQL con el cual se van a realizar:

$ mysqlslap -u foo -pxxxx --auto-generate-sql
Benchmark
	Average number of seconds to run all queries: 0.007 seconds
	Minimum number of seconds to run all queries: 0.007 seconds
	Maximum number of seconds to run all queries: 0.007 seconds
	Number of clients running queries: 1
	Average number of queries per client: 0

En este test se ha realizado la conexión de un único usuario (foo) que ha realizado pruebas aleatorias entre las que se incluye la creación de una tabla, la inserción y consulta de datos en ella y la eliminación de la tabla. Si queréis ver con detalle cada una de las consultas que se realizan en el test podéis añadir la cantidad de verbose con el parámetro -v (a más v más verbose):

$ mysqlslap -vvv -u foo -pxxxx --auto-generate-sql

Estos test realmente no sirven de mucho, así que conviene saber como personalizarlos, añadir más usuarios concurrentes en las pruebas y consultas SQL personalizadas. El número de usuarios concurrentes se especifica con el parámetro

--concurrency=

, vamos a realizar la misma prueba pero con 50 usuarios concurrentes:

$ mysqlslap -u foo -pxxxx --concurrency=50 --auto-generate-sql
Benchmark
	Average number of seconds to run all queries: 0.389 seconds
	Minimum number of seconds to run all queries: 0.389 seconds
	Maximum number of seconds to run all queries: 0.389 seconds
	Number of clients running queries: 50
	Average number of queries per client: 0

Otro parámetro interesante es

--iterations=

, mediante el cual podemos especificar el número de veces que se repetirá el test. Vamos a ejecutarlo 4 veces:

$ mysqlslap -u foo -pxxxx --concurrency=50 >--iterations=4 --auto-generate-sql
Benchmark
	Average number of seconds to run all queries: 0.389 seconds
	Minimum number of seconds to run all queries: 0.389 seconds
	Maximum number of seconds to run all queries: 0.389 seconds
	Number of clients running queries: 50
	Average number of queries per client: 0

También podemos modificar el número de consultas a realizar en el test con el parámetro

--number-of-queries=

, serán repartidas entre el número de usuarios que ejecutan el test:

$ mysqlslap -u foo -pxxxx --concurrency=50 >--iterations=4 --number-of-queries=300 --auto-generate-sql
Benchmark
	Average number of seconds to run all queries: 0.233 seconds
	Minimum number of seconds to run all queries: 0.217 seconds
	Maximum number of seconds to run all queries: 0.242 seconds
	Number of clients running queries: 50
	Average number of queries per client: 6

Otro parámetro de extrema utilidad es

--engine=

, que nos permite especificar el tipo de motor a utilizar para la creación de la tabla (MyISAM, Innodb…) y así poder realizar varios tests y verificar cual es el más adecuado.

$ mysqlslap -u foo -pxxxx --concurrency=50 --iterations=4 --number-of-queries=300 --engine=innodb --auto-generate-sql
Benchmark
	Running for engine innodb
	Average number of seconds to run all queries: 1.873 seconds
	Minimum number of seconds to run all queries: 1.724 seconds
	Maximum number of seconds to run all queries: 2.197 seconds
	Number of clients running queries: 50
	Average number of queries per client: 6

Para poder personalizar al máximo el test de benchmarking conviene poder especificar la creación de tablas y consultas a realizar. Con

--create=

especificamos la creación de la tabla y con

--query=

la consulta a realizar. Este es el ejemplo disponible en la documentación de MySQL y el resultado de la prueba:

$ mysqlslap --delimiter=";" \
  --create="CREATE TABLE a (b int);INSERT INTO a VALUES (23)" \
  --query="SELECT * FROM a" --concurrency=50 --iterations=200
Benchmark
	Average number of seconds to run all queries: 0.038 seconds
	Minimum number of seconds to run all queries: 0.011 seconds
	Maximum number of seconds to run all queries: 0.061 seconds
	Number of clients running queries: 50
	Average number of queries per client: 1

Si no queremos introducir nuestras propias consultas SQL pero sí que queremos personalizar la creación de la tabla, podemos especificar parámetros como el número de columnas VARCHAR con

--number-char-cols

, el número de columnas INT con

--number-int-cols=

, etc.

Esta es una pequeña muestra de las posibilidades que nos brinda esta utilidad de MySQL y su gran utilidad para las pruebas de rendimiento de bases de datos en entornos de desarrollo y pre-producción. Si queréis profundizar más en las posibilidades de mysqlslap os recomiendo revisar la documentación de MySQL.

MySQL: resolución de nombres (DNS) y problemas con conexiones remotas

Durante un rato, hoy me ha traído de cabeza un problema con un servidor MySQL en el que se estaban encolando todas las conexiones, las cuales no llegaban a conectar y se bloqueaban del siguiente modo:

| 64 | unauthenticated user | 10.0.3.17:37723 |               | Connect |      | login |                  |
| 65 | unauthenticated user | 10.0.3.6:45973  |               | Connect |      | login |                  |
| 66 | unauthenticated user | 10.0.3.6:45974  |               | Connect |      | login |                  |
| 67 | unauthenticated user | 10.0.3.6:45975  |               | Connect |      | login |                  |
| 68 | unauthenticated user | 10.0.3.6:45976  |               | Connect |      | login |                  |
| 69 | unauthenticated user | 10.0.3.6:45977  |               | Connect |      | login |                  |
| 70 | unauthenticated user | 10.0.3.6:45978  |               | Connect |      | login |                  |
| 71 | unauthenticated user | 10.0.3.6:45979  |               | Connect |      | login |                  |
| 72 | unauthenticated user | 10.0.3.6:45980  |               | Connect |      | login |                  |
| 73 | unauthenticated user | 10.0.3.6:45981  |               | Connect |      | login |                  |

En el momento que he verificado que el problema surgía únicamente con las conexiones remotas he comenzado a revisar las distintas posibilidades, entre las que se encontraba por ejemplo revisar que los usuarios MySQL estuvieran correctamente configurados, que no hubiera problemas en las bases de datos, etc. Pero realmente el problema surgía mucho antes, en el momento en el que se establecía la conexión, antes de siquiera pedir credenciales.

En este caso el problema radicaba en que los servidores DNS configurados para la máquina (en /etc/resolv.conf) no funcionaban correctamente e impedían a MySQL resolver los hosts remotos que intentaban conectar. MySQL siempre que recibe una conexión remota vía IP intenta revisar a que nombre resuelve para posteriormente hacer el paso contrario y verificar/comparar que el host también resuelve a dicha IP original. El servidor no pasaba de aquí y generaba el cuello de botella.

Si queremos evitar que esto suceda una de dos, o nos aseguramos que los servidores DNS resuelven rápida y correctamente o deshabilitamos la resolución de nombres con –skip-name-resolve, en este caso únicamente podremos usar IPs y no hosts en la tabla de privilegios.

Backup de todas las bases de datos MySQL con una sola línea de comandos

Probablemente la mayoría de vosotros trabajéis con bases de datos MySQL y tengáis scripts personalizados para hacer copias de seguridad de las mismas. Por ello, y por si os fuera de utilidad os dejo esta línea de comandos  que hace un dump de todas las bases de datos de un servidor MySQL separadas en ficheros .sql independientes.

for DB in `mysql -e "show databases" -u USUARIO -pPASSWORD | grep -v Database`; do mysqldump -u USUARIO -pPASSWORD $DB > "$DB.sql"; done

Como podéis ver es bastante sencillo, hacemos un bucle con la salida del comando “show databases” que vuelca por pantalla todas las bases de datos del servidor, eliminamos la única línea que no nos interesa (la que contiene Database) y vamos haciendo una a una la copia de seguridad de cada una de las bases de datos con mysqldump. Únicamente tendréis que modificar el usuario y clave con el que conectar al servicio.

MySQL Query Cache: configuración y optimización

Query Cache” es una de las funcionalidades más interesantes que ofrece MySQL. A grandes rasgos, Query cache almacena en memoria el contenido y resultado de una consulta tipo SELECT, de modo que cuando un cliente vuelva a ejecutar la misma consulta (tiene que ser exactamente la misma), esta no tendrá que procesarse y se servirá directamente de la memoria sin necesidad de utilizar recursos de MySQL.

Según esta explicación, lo primero que viene a la mente es que la Query Cache será efectiva en bases de datos en las que las tablas no tienen demasiados cambios y predominan los SELECT frente a los INSERT. Vamos a explicar de forma sencilla como activar la caché y revisar sus resultados. Recomiendo leer la documentación de MySQL para quien quiera profundizar en el tema.

Lo primero que debemos hacer para activar la caché de consultas es indicarlo en el fichero de configuración my.cnf, a través de la directiva

query_cache_type

, que acepta tres valores:

Query Caché desactivada:

query_cache_type = 0

Query Caché activada:

query_cache_type = 1

Query Caché activada según demanda (especificandolo en la consulta (SELECT /* SQL_CACHE */):

query_cache_type = 2

En nuestro caso la activamos con el parámetro “1″, podemos ver el estado actual desde la shell MySQL:

mysql> select @@query_cache_type;
+--------------------+
| @@query_cache_type |
+--------------------+
| ON                 |
+--------------------+
1 row in set (0.00 sec)

Una vez activada, tenemos que asignar un tamaño de memoria disponible para el almacenamiento de estas consultas. En la mayoría de los casos con un valor menor de 100M debería ser suficiente. En el fichero my.cnf tenemos:

query_cache_size=28M

Bien, con estos dos parámetros configurados ya empezaría a utilizarse la Query Cache en nuestro servidor MySQL. Pasado un tiempo debería llegar el momento de comprobar los resultados y verificar si está siendo efectiva. Para ello, revisamos el estado de Query Cache directamente desde la shell MySQL:

mysql> show status like 'Qcache%';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| Qcache_free_blocks      | 2319      |
| Qcache_free_memory      | 7119040   |
| Qcache_hits             | 143073915 |
| Qcache_inserts          | 67474731  |
| Qcache_lowmem_prunes    | 9384509   |
| Qcache_not_cached       | 71545921  |
| Qcache_queries_in_cache | 7528      |
| Qcache_total_blocks     | 17974     |
+-------------------------+-----------+
8 rows in set (0.00 sec)

La explicación de cada variable es la siguiente:

  • Qcache_free_blocks: El número de bloques de memoria disponibles en la query cache.
  • Qcache_free_memory: La cantidad de memoria libre en la query cache.
  • Qcache_hits: El número de aciertos de la query caché (consultas servidas desde la cache).
  • Qcache_inserts: El número de consultas insertadas a la cache.
  • Qcache_lowmem_prunes: El número de consultas eliminadas de la cache por falta de memoria disponible.
  • Qcache_not_cached: Número de consultas que no se han podido cachear.
  • Qcache_queries_in_cache: Número de consultas registradas en la cache.
  • Qcache_total_blocks: Número de bloques en la cache.

De estos puntos, conviene tener en cuenta que si el valor de Qcache_free_blocks es muy alto significa que hay fragmentación en la cache y conviene ejecutar FLUSH QUERY CACHE para desfragmentar (esto no elimina la cache). La eficiencia de la cache se mide mediante esta fórmula: Qcache_hits/(Com_select+Qcache_hits), evidentemente un número alto de hits y un buen ratio respecto a los Com_select (número total de consultas SELECT ejecutadas) es lo óptimo. Podéis ver el número de selects globales del siguiente modo:

mysql> show global status like 'Com_select%';
+---------------+----------+
| Variable_name | Value    |
+---------------+----------+
| Com_select    | 71742078 |
+---------------+----------+
1 row in set (0.00 sec)

Para comenzar a trastear con la Query Cache de MySQL esto puede ser suficiente, para el que quiera profundizar puede hacerlo revisando toda la documentación disponible de MySQL del enlace anterior.

MySQL Cluster (I): Instalación de nodos de datos/SQL y gestión (MGM)

Esta es la primera parte de una serie de artículos en los que aprenderemos a montar, configurar y gestionar un cluster MySQL básico. En este caso utilizaremos dos nodos de datos, dos nodos SQL y un nodo de gestión.

Hay varias formas de instalar cada uno de los nodos, ya sea con gestores de paquetes de las distribuciones (apt, yum, .deb, rpm), compilando desde el código fuente o con los paquetes binarios. En este caso vamos a utilizar los paquetes binarios por su sencillez de instalación, cada cual puede escoger la forma que estime oportuna y según sus necesidades.

No voy a detenerme a explicar como funciona un cluster MySQL en profundidad, podéis ver toda la documentación del sitio web de MySQL donde está todo perféctamente explicado (esta guía está basada en esa documentación). Simplemente explicaré las partes (nodos) que van a componer este cluster.

  • Nodos de datos: Son los nodos encargados de almacenar los datos del cluster. Cada nodo será una máquina física independiente. Tal y como indica la documentación de MySQL hay tantos nodos de datos como réplicas, multiplicado por el número de fragmentos. Por ejemplo, con dos réplicas, cada uno teniendo dos fragmentos, necesita cuatro nodos de datos. Es recomendable revisar bien la documentación oficial de MySQL para entender el concepto y las diferentes configuraciones posibles.
  • Nodos SQL: Son los nodos que serán usados para acceder a los datos que almacenan los nodos de datos, básicamente es un servidor MySQL estandar pero que utiliza el motor de almacenamiento NDBCLUSTER.
  • Nodo de gestión: También llamado nodo MGM, es el encargado de gestionar todos los nodos del cluster, entre sus funciones encontramos las de arrancar y parar nodos, aplicar configuraciones, efectuar copias de seguridad, etc.

Instalación de nodos de datos y SQL

La instalación de un nodo de datos y un nodo SQL es igual, lo único que cambia es a posteriori la configuración y modo de arranque de cada uno de ellos (lo explicaré en el siguiente artículo) .Podéis descargar el paquete binario al igual que los demás a través de este enlace:

http://dev.mysql.com/downloads/cluster/

En este caso “Generic Linux (glibc 2.3) (x86, 32-bit), Compressed TAR Archive”:

# wget http://dev.mysql.com/get/Downloads/MySQL-Cluster-7.1/mysql-cluster-gpl-7.1.3-linux-i686-glibc23.tar.gz/from/http://gd.tuwien.ac.at/db/mysql/

Procedemos a crear el usuario y grupo de sistema para ejecutar MySQL:

# groupadd mysql
# useradd -g mysql mysql

Descomprimimos el paquete y posteriormente lo renombramos a “mysql”, ubicandolo en /usr/local/:

# tar -C /usr/local -xzvf mysql-cluster-gpl-7.0.15-linux-i686-glibc23.tar.gz
# mv /usr/local/mysql-cluster-gpl-7.0.15-linux-i686-glibc23.tar.gz /usr/local/mysql

Nos dirigimos a la ruta correspondiente y ejecutamos el script preparado para crear las bases de datos de sistema:

# cd /usr/local/mysql
# scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql

Aplicamos los permisos correspondientes a las carpetas MySQL:

# cd /usr/local/mysql
# chown -R root .
# chown -R mysql data
# chgrp -R mysql .

Nota: /usr/local/mysql/data es la ruta que utilizaremos en todos los nodos para guardar los datos

Finalmente copiamos el script de arranque de MySQL a init.d, le permitimos ejecución y lo añadimos al runlevel correspondiente:

# cp support-files/mysql.server /etc/rc.d/init.d/
# chmod +x /etc/init.d/mysql.server
# chkconfig --add mysql.server

Nota: chkconfig solo funcionará en sistemas Red-Hat y derivados.

Bien, ya tenemos el nodo primer nodo de datos ó SQL instalado. Habría que repetir esta tarea en todas las máquinas que quisieramos utilizar como nodo de datos o nodo SQL.

MANAGEMENT ndb_mgm

La instalación del nodo de gestión es extremadamente sencilla. Tenemos que descargar el mismo paquete que para los nodos de datos y SQL, descomprimirlo en una ruta temporal y copiar los ejecutables correspondientes:

# cd mysql-5.1.44-ndb-7.0.15-linux-i686-glibc23
# cp bin/ndb_mgm* /usr/local/bin
# cd /usr/local/bin
# chmod +x ndb_mgm*

En el próximo artículo comenzaremos a tocar las configuraciones de los nodos de datos, gestión y mysqld. Entonces podremos arrancar el cluster y empezar a hacer las pruebas correspondientes.

Clonar una tabla MySQL de forma rápida y sencilla

Si dispones de acceso a la shell mysql, una forma sencilla de clonar una tabla de una base de datos, tanto su estructura como contenido es la siguiente:

mysql> CREATE TABLE tabla_destino SELECT * FROM tabla_origen;

Mysqldump: excluir una tabla en el backup

Si queremos hacer un backup de una base de datos MySQL excluyendo una o varias tablas del mismo, simplemente tenemos que usar el parámetro –ignore-table=bd.tabla. Unos ejemplos:

Hacemos un backup de la base de datos test excluyendo la tabla prueba1:

mysqldump --ignore-table=test.prueba1 test > test.sql 

Hacemos un backup de la base de datos test excluyendo las tablas prueba1 y prueba2:

mysqldump --ignore-table=test.prueba1 --ignore-table=test.prueba2 test > test.sql

Esta opción también puede utilizarse para ignorar vistas (views)

MySQL: Cambiar valor de variable ft_min_word_len

Una entrada rápida, para cambiar el valor de la variable ft_min_word_len de MySQL, hay que editar al fichero de configuración (my.cnf en Unix y my.ini en Windows) y añadir el siguiente parámetro:

ft_min_word_len = X

Donde X es el valor (nº entero) que queráis asignar. Por defecto creo que está a 4:

mysql> SHOW VARIABLES LIKE 'ft_min_word_len';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| ft_min_word_len | 4     |
+-----------------+-------+
1 row in set (0.00 sec)

Posteriormente reiniciamos el servicio MySQL y listo.