Script para optimizar tablas InnoDB en MySQL

Cuando utilizamos MySQL es común optimizar tablas con muchos registros con cierta periodicidad, esto para solventar problemas de fragmentación, entre otros. La verdad esta es una de las cosas del modelo de PostgreSQL que echo en falta, quizás no es tan “amigable” pero todo queda claro desde el inicio.

En PostgreSQL hay un proceso de aspiradora (vacuum) que va eliminando periódicamente registros inutilizados en tablas, su configuración, pan nuestro de cada día para un admin de BBDD que debe ajustarlo con frecuencia.

Bueno…. Volviendo a MySQL, si necesita optimizar tablas InnoDB, lo mejor que puede utilizar son ALTER nulos, estas son instrucciones DDL de tipo ALTER sin parámetros que permiten seguir trabajando con las BBDD, porque realiza copias temporales en disco. La cuestión es que esta herramienta “reconstruye” la tabla y elimina, entre otros, los problemas de fragmentación.

Aquí les dejo un script para optimizar de “un sólo golpe” varias tablas InnoDB:

#!/bin/bash
 
if [ $# -lt 2 ]; then
        echo "You must specify database host"
        echo "Eg. script.sh MY_DATABSE 192.168.10.1"
        exit
fi
 
db="$1"
host="$2"
user="root"
declare -a tables=(Table1 Table2 Table3)
 
stty -echo
read -p "Enter MySQL's Admin password: " password
stty echo
 
for table in ${tables[@]}; do
        echo $table &&
        time mysql -u $user --password=$password -h $host $db -e "ALTER TABLE $table ENGINE=INNODB"
done

Básicamente optimizamos las tablas especificadas (en un arreglo) e imprimimos el tiempo que toma cada instrucción (time).

Si tiene la certeza de que todas las tablas de una BD son InnoDB y quiere optimizarlas todas aún más rápido, puede hacerlo valiéndose del comando “show tables”…

#!/bin/bash
 
if [ $# -lt 2 ]; then
        echo "You must specify database host"
        echo "Eg. script.sh MY_DATABSE 192.168.10.1"
        exit
fi
 
db="$1"
host="$2"
user="root"
 
stty -echo
read -p "Enter MySQL's Admin password: " password
stty echo
 
mysql -u $user --password=$password -h $host --batch --skip-column-names $db -e "SHOW TABLES" |
while read table; do
        echo $table &&
        time mysql -u $user --password=$password -h $host $db -e "ALTER TABLE $table ENGINE=INNODB"
done

La única diferencia es que las tablas ya no son especificadas a través de un arreglo (que recomiendo para BBDD grandes, donde optimizar todas las tablas podría demorar toda la vida), sino que se toman directamente del comando “SHOW TABLES” para una BD especificada.

Script para descargar videos flash en linux sin /tmp

Como todos sabemos la nueva versión de adobe flash plugin ya no deja los videos en la carpeta /tmp de linux, así que ese método ya no sirve para tal propósito. Por eso he creado un sencillo script para compartir aquí en mi blog, el cual explicaré como funciona de manera rápida y simple. Recuerda que si no te funciona bien o te gustaría ampliarlo lo puedes hacer de manera libre.
Continuar leyendo “Script para descargar videos flash en linux sin /tmp”

Script en bash para poner en color el Netstat

Aqui os dejo este script feo en bash que me he encontrado en un foro ruso mas raro que un perro verde, por si a alguien le sirve.

El script muestra un netstat (concretamente netstat -natp) con distintos colores según el estado de cada conexión (established, listen, syn_sent, fyn_wait, etc.). Claramente no es un script muy útil (ni muy bien hecho), pero lo publico. Quizás a alguno le sirve de disparador para hacer algo verdaderamente útil.

#!/bin/bash
cyan="\E[1;36m\E[1m";
normal="\E[m";
blue="\E[34m\E[1m";
violet="\E[35m\E[1m";
red="\E[31m\E[1m";
yellow="\E[33m\E[1m";
green="\E[37m\E[32m\E[1m";
text="\E[1;37m\E[1m";

if [ "$UID" != "0" ]; then
	echo -e "$red$0: You will get more information if you have root privileges. Try sudo $0$normal"
fi

netstat -natp | \
while read line; do

	if [ `echo $line | awk '{print($1)}'` = "Proto" ]; then
		echo -e "$yellow=====================================================================================================$normal"
		echo -e "$text$line$normal"
		echo -e "$yellow=====================================================================================================$normal"
	else

		state=`echo $line | awk '{print($6)}'`
		color=$normal
		case $state in
			"ESTABLISHED")
				color=$green;;
			"SYN_SENT" | "SYN_RECV")
				color=$yellow;;
			"FIN_WAIT1" | "FIN_WAIT2" |"TIME_WAIT")
				color=$violet;;
			"CLOSE" | "CLOSE_WAIT" | "LAST_ACK" | "CLOSING" )
				color=$blue;;
			"LISTEN")
				color=$cyan;;
			"UNKNOWN")
				color=$red;;
			*)
		esac
		echo -e "$color$line$normal"

	fi
done;

Reparar todas las tablas de todas las bases de datos de MySQL en Plesk

Para reparar todas las tablas de todas las bases de datos de MySQL en Plesk (Linux) tenemos que poner en una sola linea este pedazo churro:

for database in $(mysql --skip-column-names -uadmin -p`cat /etc/psa/.psa.shadow` -e
"show databases" ); do echo "optmizing tables from $database";
for table in $(mysql --skip-column-names -uadmin -p`cat /etc/psa/.psa.shadow` -e
"show tables" $database ); do echo "-> $table " ;
mysql -uadmin -p`cat /etc/psa/.psa.shadow` -e "OPTIMIZE TABLE $table" $database ;
done ; done ;

Script en BASH para comparar tráfico de los access_logs de Apache

Antes que nada debo decir que este script fue algo que hice en un rato tratando de solucionar un problema, y tiene varios errores y cosas mejorables. De más está decir que no cuenta con ninguna garantía, sino que lo comparto porque quizás pueda ayudar a alguien que enfrente un problema similar.

Mi problema es que necesitaba saber cuáles eran los dominios de un servidor que alojaba más de 300 que estaban teniendo mayor tráfico. Como no tenía ninguna herramienta en tiempo real que me pudiera reportar esta información para el Apache, lo que hice es un script que revisa los access_log de cada dominio y obtiene la cantidad de bytes traficados por cada uno.

En este caso, cada VirtualHost define un archivo de logs diferente para cada dominio. Los access_log usan el formato combined (que incluye User-Agent, etc.) y no especifican nada más. El script no hace ninguna validación al respecto, así que podría no funcionar con logs en otros formatos.

Debe tenerse en cuenta que si los archivos de logs rotan en base a su tamaño, es probable que se presente el siguiente caso: El dominio web1.com tiene gran cantidad de tráfico y el dominio web2.com todo lo contrario. El access_log correspondiente al primer dominio puede reportar el tráfico de 1 Gb en un día y el access_log del segundo puede reportar tráfico también de 1 Gb pero en 6 meses. Por lo tanto, es importante mantener referencia de las fechas extremas de los logs, para poder dar cuenta de estas circunstancias.

Hechas estas aclaraciones, pasamos a la explicación del script.

¿Cómo funciona?

Muy sencillo. El script recorre todos los archivos de logs (1 por dominio). Por cada uno obtiene la fecha inicial (la del primer request registrado) y la final (la del último). Luego suma la cantidad de bytes de todos los requests registrados, e imprime una fila con esa cifra en megabytes, kilobytes, bytes y el rango de fechas que abarca. El conjunto de todas esas líneas es enviada al sort de GNU para ordenar las líneas de menor a mayor cantidad de tráfico.

Quizás suene medio complejo, pero se entenderá mejor viendo el código.

#!/bin/bash

BASE_DIR=/tmp/domain_logs

dateToStamp ()
{
    if [ ${#1} -eq 19 ]; then
        date --utc --date "$1" +%s;
    else
        echo 0
    fi
}

formatDate ()
{
    echo $1 | awk 'BEGIN { \
                    dict["Jan"]=1; \
                    dict["Feb"]=2; \
                    dict["Mar"]=3; \
                    dict["Apr"]=4; \
                    dict["May"]=5; \
                    dict["Jun"]=6; \
                    dict["Jul"]=7; \
                    dict["Aug"]=8; \
                    dict["Sep"]=9; \
                    dict["Oct"]=10; \
                    dict["Nov"]=11; \
                    dict["Dec"]=12 \
                    } { \
                        split(substr($1, 2), a, /[\\\/:]+/); \
                        print a[3] "-" dict[a[2]] "-" a[1] " " a[4] ":" a[5] ":" a[6] \
                    }'
}

for file in `ls $BASE_DIR/*`;
do
    from=$(formatDate `head -1 $file | awk '{print($4)}'`)
    to=$(formatDate `tail -1 $file | awk '{print($4)}'`)
    stamp_from=$(dateToStamp "$from")
    stamp_to=$(dateToStamp "$to")
    total_time=$(($stamp_to-$stamp_from))
    bytes=`cat $file | awk '{a+=$10}END{print a}'`
    kbytes=$((bytes/1024))
    mbytes=$((kbytes/1024))
    echo  "$mbytes MB | \
           $kbytes KB | \
           $bytes Bytes | \
           From: $from ($stamp_from) | \
           To: $to ($stamp_to) | \
           $total_time secs | \
           ${file:${#BASE_DIR}+1}"
done \
| sort -nb

Como se puede ver, al principio se define la variable BASE_DIR, que es el directorio base donde el script va a buscar los access logs. Cuando ejecuto el script, para evitar problemas de cambios en tiempo real, creo un snapshot con todos los archivos de logs y los guardo en una carpeta (en este caso /tmp/domain_logs).

<pre># cp /var/log/httpd/domains/*.access.log /tmp/domain_logs/</pre>

Luego, trabajo directamente sobre el snapshot. Entonces, como se observa en el script, defino dos funciones que utilizo más abajo, dateToStamp y formatDate. Como sus respectivos nombres indican, la primera convierte una fecha en un unix timestamp y la segunda cambia el formato de la fecha que figura en el archivo de logs por el que a mi me sirve para pasarle a date. Esto seguramente es mucha complicación para algo que podría hacer usando mejor el comando date, pero fue lo que más rápido me resultó hacer.

Y luego está el for, que recorre los archivos y por cada uno obtiene las fechas límite ($from y $to), de allí obtiene los timestamps ($stamp_from, $stamp_to), el tiempo total en segundos y la cantidad de bytes (la cual sumo con awk).

Por último, verán que la salida del for tiene un pipe a sort, a quien le paso los parámetros “n” y “b”. Para que haga un ordenamiento numérico ignorando los espacios en blanco.

Ejemplo

# ./access_log_parser.sh
97 MB | 99901 KB | 102299281 Bytes | From: 2008-11-28 02:25:54 (1227839154) | To: 2008-11-28 15:34:12 (1227886452) | 47298 secs | domain1.com.ar.log
115 MB | 118260 KB | 121098817 Bytes | From: 2008-11-28 02:27:45 (1227839265) | To: 2008-11-28 15:34:26 (1227886466) | 47201 secs | domain2.com.ar.log
123 MB | 126652 KB | 129691792 Bytes | From: 2008-11-28 02:27:53 (1227839273) | To: 2008-11-28 15:33:01 (1227886381) | 47108 secs | domain3.com.ar.log
145 MB | 149279 KB | 152862681 Bytes | From: 2008-11-28 02:30:29 (1227839429) | To: 2008-11-28 15:34:31 (1227886471) | 47042 secs | domain4.com.ar.log
189 MB | 194517 KB | 199185477 Bytes | From: 2008-11-28 04:35:20 (1227846920) | To: 2008-11-28 15:32:58 (1227886378) | 39458 secs | domain5.com.log
197 MB | 202034 KB | 206883560 Bytes | From: 2008-11-28 02:35:55 (1227839755) | To: 2008-11-28 15:34:29 (1227886469) | 46714 secs | domain6.com.ar.log
499 MB | 511816 KB | 524099937 Bytes | From: 2008-11-28 02:25:44 (1227839144) | To: 2008-11-28 15:34:29 (1227886469) | 47325 secs | domain7.com.ar.log
#

NOTA: si conocen alguna forma de monitorear esto en tiempo real, también me sería de gran utilidad.