Archive of December 2009


Sun 27 Dec

Particionado Lógico (Parte II)

Para las prácticas haremos uso de una BBDD de prueba que podemos descargar aquí:

Sample database with test suite

Lo bueno de esta BBDD es que ya viene repletita de datos, por ejemplo la tabla salaries tiene en torno a dos millones de registros. La particionaremos de forma que logremos mejorar el rendimiento. Hay que tener en cuenta que las pruebas se van a hacer sobre un Netbook, por lo que los resultados no son 100% fiables. Nunca pongáis un netbook como servidor de bases de datos en producción u os quedareis ciegos.

El particionado se puede hacer por rangos, listas, hashes y keys:

RANGO


CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6),
    PARTITION p1 VALUES LESS THAN (11),
    PARTITION p2 VALUES LESS THAN (16),
    PARTITION p3 VALUES LESS THAN (21),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

Es el particionado más sencillo. En el ejemplo se divide usando el campo store_id, de forma que los valores menores de 6 van a la partición p0, los menores de 11 a la p1, los menores de 16 a la p2, etc. Es importante tener en cuenta la inclusión del MAXVALUE. Ahí es donde irán a parar aquellos registros que no encajen dentro del resto de tablas. Si no pusiéramos un MAXVALUE, MySQL no sabría donde meterlo y daría error.

LISTAS


CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY LIST(store_id) (
    PARTITION pNorth VALUES IN (3,5,6,9,17),
    PARTITION pEast VALUES IN (1,2,10,11,19,20),
    PARTITION pWest VALUES IN (4,12,13,14,18),
    PARTITION pCentral VALUES IN (7,8,15,16)
);

En este caso, el particionado se realiza en base a una lista de posibles valores. Al contrario que con RANGE, aquí no tenemos MAXVALUE. Por lo que es de suponer, según el diseño de la web, que nunca tendremos valores distintos a los indicados.

HASHES


CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;

Como podéis comprobar, estamos haciendo uso de una columna que no es INT, pero mediante la función YEAR la convertiremos y hacemos uso de ella en la partición. Este truco se suele utilizar muy a menudo para evitar la limitación de particiones en INT. A parte de todo eso, el uso de HASH nos permite dividir los datos de forma equitativa entre todas las particiones, cosa que con otros tipos de particiones podría no pasar. De esta forma, si estamos trabajando una tabla enorme y la queremos dividir en 10 particiones, estas tendrán un número de valores muy similar.

KEY


CREATE TABLE k1 (
    id INT NOT NULL PRIMARY KEY,
    name VARCHAR(20)
)
PARTITION BY KEY()
PARTITIONS 2;

Con Key no es necesario indicar la columna que deseamos para particionar, en ese caso hará uso de la clave primaria. KEY es muy parecida a HASH, solo que en lugar de indicarle nosotros el HASH mediante una expresión, lo hará el propio MySQL usando MD5.

Dicho esto, comenzamos a particionar nuestra BBDD. La tabla en cuestión será, como ya indique, la de salarios.


mysql [localhost] {msandbox} (employees) > select count() from salaries;
+----------+
| count() |
+----------+
|  2844047 |
+----------+
1 row in set (1.66 sec)

Casi 3 millones de registros, no está nada mal :) Vamos a hacer una búsqueda para saber cuantos corresponden al año 2000.


mysql [localhost] {msandbox} (employees) > select count() from salaries where from_date between
'2000-01-01' and '2000-12-31';
+----------+
| count() |
+----------+
|   255785 |
+----------+
1 row in set (2.39 sec)

Fijaros en el tiempo que ha tardado MySQL en realizar la consulta, 2.39 segundos. Bastante tiempo, si en lugar de una sola querie fuesen 100 concurrentes estaríamos ante un verdadero problema de rendimiento.


mysql [localhost] {msandbox} (employees) > explain select count(*) from salaries where from_date
between '2000-01-01' and '2000-12-31'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: salaries
         type: index
possible_keys: NULL
          key: emp_no
      key_len: 4
          ref: NULL
         rows: 2844513
        Extra: Using where; Using index
1 row in set (0.00 sec)

La querie se tiene que recorrer los 3 millones de registros usando un índice (lo cual no está mal) y aún así el rendimiento es pobre. Entonces, pidamos ayuda a nuestras amigas las particiones. Vamos a particionar por año :)


ALTER TABLE salaries 
PARTITION by range (year(from_date))
(
  partition p001 VALUES LESS THAN (1986)
, partition p002 VALUES LESS THAN (1987)
, partition p003 VALUES LESS THAN (1988)
, partition p004 VALUES LESS THAN (1989)
, partition p005 VALUES LESS THAN (1990)
, partition p006 VALUES LESS THAN (1991)
, partition p007 VALUES LESS THAN (1992)
, partition p008 VALUES LESS THAN (1993)
, partition p009 VALUES LESS THAN (1994)
, partition p010 VALUES LESS THAN (1995)
, partition p011 VALUES LESS THAN (1996)
, partition p012 VALUES LESS THAN (1997)
, partition p013 VALUES LESS THAN (1998)
, partition p014 VALUES LESS THAN (1999)
, partition p015 VALUES LESS THAN (2000)
, partition p016 VALUES LESS THAN (2001)
, partition p017 VALUES LESS THAN (2002)
, partition p018 VALUES LESS THAN (2003)
);

Si ahora hacemos la misma select de antes:


mysql [localhost] {msandbox} (employees) > select count() from salaries where
from_date between '2000-01-01' and '2000-12-31';
+----------+
| count() |
+----------+
|   255785 |
+----------+
1 row in set (0.22 sec)

La búsqueda de los salarios del año 2000. Antes 2,39 segundos, ahora 0,22. La mejora es impresionante.

Un explain partitions nos indica que partición ha usado para la select:

mysql [localhost] {msandbox} (employees) > explain partitions select count(*) from salaries where from_date between '2000-01-01' and '2000-12-31'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: salaries partitions: p016 type: index possible_keys: NULL key: emp_no key_len: 4 ref: NULL rows: 2830488 Extra: Using where; Using index 1 row in set (0.00 sec)


Wed 23 Dec

Particionado Lógico (Parte I)

Desde la versión 5.1 existe la posibilidad de particionar nuestras tablas de forma horizontal (en líneas), algo que nos puede ayudar en casos puntuales a mejorar el rendimiento de nuestra base de datos. Resumiendo, este sistema nos permite dividir lógicamente una tabla muy grande en otras más pequeñas, dentro de un rango de valores que nosotros indiquemos, de forma que la consulta de datos sea más rápida. Su uso es muy sencillo pero... ¿cuando debemos utilizarlo?

  • Cuando la tabla sea tan grande que los índices no entren en RAM.
  • Cuando tengamos una tabla realmente grande (no hablo de megas).
  • Cuando almacenamos datos históricos.
  • Cuando queremos rotar datos.
  • Cuando los datos no paran de crecer y crecer...

Hay que tener en cuenta que este particionado es totalmente transparente para el usuario (y por lógica también para nuestra aplicación) por lo que en el caso de decidirnos por esta solución el cambio será poco dramático. Solamente tendremos que tener en cuenta estos detalles:

  • La columna que utilicemos para definir el rango de las particiones debe ser un INT, no se acepta cualquier otro valor.
  • Si tenemos una clave única o una primary key, esta debe usarse para particionar.
  • Como máximo se permiten 1024 particiones.
  • No se permiten claves externas.
  • No se permiten busquedas FULL TEXT.

Si tenemos una tabla con millones de registros y hacemos una select, MySQL se deberá recorrer toda la tabla (en caso de no usar índices, si estos ocupan más que la RAM) o se tendrá que recorrer todos los índices. Esto, contra más grande es la tabla, mas ineficiente es:

Gracias al particionado es posible hacer una busqueda en una fracción mucho más pequeña de nuestra tabla. Si la dividimos de forma que cada partición incluya 100 filas (particionando por ID), MySQL sabe que el dato se tiene que encontrar en la segunda partición, por lo que se evita tener que buscar en los restantes X millones de registros.

La mejora es clara, pero no siempre particionar nos va a dar mayor rendimiento. Si la tabla no es lo suficientemente grande incluso podemos degradar el rendimiento, ya que añadimos overhead a una base de datos que no necesita grandes recursos para funcionar. Más adelante veremos como implementar el sistema y también como benchmarkear la solución para conocer si nos puede salvar la vida o no.

· Tags: ,

Mon 21 Dec

Bug en Plesk. Evitando los filtros de Spam.

Hace ya algunos meses me puse en contacto con Parallels desde su web y no he recibido respuesta, por lo que supongo que no estarán interesados en solucionar el fallo.

Es posible enviar correos con publicidad no deseada (SPAM) a servidores que tengan instalado Plesk para Windows, la combinación es MailEnable + SpamAssassin + Plesk 9.2.3. Para evitar el filtro AntiSpam únicamente hay que:

  • No poner la cabecera Subject.
  • Entre la cabecera Subject y el resto del cuerpo del mensaje no dejar una línea en blanco.

Listo, con esto tu sistema de correo se tragará completamente el correo de Spam sin siquiera analizarlo. ¿Bonito verdad?

No he probado con otras versiones ni con otros sistemas operativos. Si alguien lo prueba y tiene resultados parecidos (o no) que me comente :)


Thu 17 Dec

Ya soy 1/2 CCNA

Pasé el primer examen de los dos que hacen falta para ser CCNA. El examen ha sido bastante difícil en algunas partes, más aún cuando te encuentras con preguntas sobre Frame Relay que son del segundo libro. Me tiré como 20 minutos probando comandos show hasta que logré entender la dichosa pregunta y saber que leches tenia que responder... Era algo de saber la dirección en capa 2 de Frame Relay cuando el destino es una ip especifica.

¡Yo que coño se! ¡Eso es para el segundo examen cabrones!

Si soy sincero, cuando estaba por la mitad de examen tenia 0 de confianza en un aprobado, más bien pensé que iba como el culo. Llegó un momento en el que casi no me paraba a leer la pregunta, lo miraba un poco por encima y respondía "total... he suspendido". Que cara de idiota que se me quedó cuando en la pantalla ponía "PASS" XD

La parte de subnetting he tenido un 100% de aciertos, es gracioso verme haciendo las operaciones matemáticas más simples con los dedos... "a ver... mmm 192 + 32 + ... mmm... 128 + 32... mmm no... esto... ufff...". Pero bueno, a mi velocidad, poco a poco, pero los saqué todos. Quiero aprovechar para dar las gracias a Mikel por a increíble paciencia que ha tenido explicándome su método para hacer subnetting, mil veces mejor que el del libro :)

Ahora me tomaré un descanso de certificados hasta enero o febrero... Hoy cuando vuelva a casa de trabajar y pueda tocarme las pelotas a dos manos, sin tener que estudiar nada, será la puta ostia xD


Wed 9 Dec

Transparencias del curso "Replicación en MySQL"

Os presento las transparencias del curso de Replicación de MySQL que acabo de terminar y subir a Slideshare.

Contenido:

Maestro/Esclavo Maestro/Maestro Circular MMM MySQL Proxy


Thu 3 Dec

Alta disponibilidad en replicación con Mysql-MMM

Montar un sistema de replicación es sencillo y rápido. Nos ofrece muchas ventajas, siempre y cuando funcione correctamente y no fallen los equipos. Ahora imagina con las siguientes características:

  • Dos equipos en maestro-maestro.
  • 50 equipos esclavos, 25 colgando del maestro 1 y otros 25 del maestro 2.

Ahora imagina que el maestro 1 se cae. Bien, recuerda que todo esto está en tu imaginación, no te intentes suicidar, aunque he de admitir que sería la solución mas razonable. Con la caida de ese Master, 25 equipos esclavos se han quedado desincronizados. Tenemos usuarios que ni pueden escribir y lo que leen está posiblemente anticuado. Cuando pones de nuevo el servidor en marcha compruebas que no se replican correctamente ya que alguna transacción quedó a medías. Tienes 26 ordenadores desincronizados y tienes que entrar uno a uno parando el proceso Slave, ejecutando el change_master, arranco de nuevo el slave, comprobando si se resincroniza o no. En el peor de los casos tienes que borrar la base de datos importándola de nuevo... El infierno convertido en SQL.

La solución

Para ayudarnos en esta tarea tenemos Mysql MMM (Multi-Master Replication Manager). Son una serie de scripts y demonios que se encargan de hacer esta tarea más sencilla. Entre sus características tenemos:

- Monitorización de la replicación
- Monitorización de los hosts
- Gestión del failover
- Balanceo de IPs entre nodos
- Gestión de grupos de escritura/lectura

El esquema de nuestra red cambia poco, si antes teniamos 52 servidores, ahora serán 53. El nuevo será un sistema de control que se encargará de conectarse a cada nodo y comprobar el estado y ejecutar las ordenes que le indiquemos mediante las utilidades de consola. Además será necesario más IPs que se asignarán al vuelo de un host a otro dependiendo de los hosts que se encuentren online.

A la hora de gestionar las IPs virtuales tenemos dos formas de hacerlo:

Exclusivo: Una única IP para muchos hosts. Si el host que la tiene se cae se balancea a otro. Generalmente se usa en los nodos de escritura.

Balanceado: Una IP por cada host. Si uno de los hosts se cae la IP se balancea a cualquier otro, pasando a tener dos IPs virtuales. Se usa para nodos en lectura.

Yo no voy a hacer el ejemplo con 52 hosts, siendo sincero me da mucha pereza. Así que el esquema de nuestro sistema de replicación en alta disponibilidad es el siguiente:

Al turrón

La parte de montar la arquitectura maestro/esclavo la damos por hecha :P Nos centraremos únicamente en MMM. Como vemos, el host de control tendrá la IP 10.100.1.14. Las IPs virtuales que usaremos serán 10.100.1.10 en modo exclusivo para las escrituras (DB1 y DB2) y 10.100.1.11 y 10.100.1.12 en modo balanceado para la lectura.

De esta forma, ya sea mediante un balanceador hardware o software (en la propia aplicación), los usuarios leerán de 10.100.1.11 y 10.100.1.12 (round robin) y escribirán en 10.100.1.10.

En el sistema de control se debe instalar:

  • mysql-mmm-common_2.0.10-1_all.deb
  • mysql-mmm-monitor_2.0.10-1_all.deb

Mientras que en los servidores de MySQL instalaremos:

  • mysql-mmm-common_2.0.10-1_all.deb
  • mysql-mmm-agent_2.0.10-1_all.deb

Sin olvidarnos de todas sus dependencias.

Los ficheros de configuración se guardan en /etc/mysql-mmm. Todos tienen uno en común llamado mmm_common.conf con este contenido:


active_master_role  writer
<host default>
    cluster_interface       eth1
    pid_path                /var/run/mmmd_agent.pid
    bin_path                /usr/bin/mysql-mmm/
        replication_user        replication
        replication_password    slave
    agent_user              mmm_agent
    agent_password          RepAgent
</host>
<host db1>
    ip                  10.100.1.1
    mode                    master
    peer                    db2
</host>
<host db2>
    ip                  10.100.1.2
    mode                    master
    peer                    db1
</host>
<host db3>
    ip                  10.100.1.3
    mode                    slave
</host>
<host db4>
    ip                  10.100.1.4
    mode                    slave
</host>
<role writer>
    hosts                   db1, db2
    ips                 10.100.1.10
    mode                    exclusive
</role>
<role reader>
    hosts                   db3, db4
    ips                 10.100.1.11, 10.100.1.12
    mode                    balanced
</role>

Es bastante descriptivo :) Se identifican los hosts con su nombre, su ip y las IPs virtuales que compartirán (y en que modo lo harán). También se indica el usuario y contraseña para el usuario de replicación así como del del agente (serán usuarios de MySQL).

El nodo de control tendrá el fichero mmm_mon.conf:


include mmm_common.conf
<monitor>
    ip                  127.0.0.1
    pid_path                /var/run/mmmd_mon.pid
    bin_path                /usr/bin/mysql-mmm/
    status_path             /var/lib/misc/mmmd_mon.status
    ping_ips                10.100.1.1, 10.100.1.2, 10.100.1.3, 10.100.1.4
</monitor>
<host default>
    monitor_user            mmm_monitor
    monitor_password        RepMonitor
</host>
debug 0

Y cada de los servidores de mysql tendrá un mmm_agent.conf.


include mmm_common.conf
this db1

En cada host, se cambiará dbi por el nombre que corresponda.

Crear usuarios en Mysql

Hay que crear los siguiente usuarios:

GRANT REPLICATION CLIENT                 ON *.* TO 'mmm_monitor'@'10.100.1.%' IDENTIFIED BY 'RepMonitor';
GRANT SUPER, REPLICATION CLIENT, PROCESS ON *.* TO 'mmm_agent'@'10.100.1.%'   IDENTIFIED BY 'RepAgent';
GRANT REPLICATION SLAVE                  ON *.* TO 'replication'@'10.100.1.%' IDENTIFIED BY 'slave';

- El usuario monitor se usa para comprobar el estado de los servidores Mysql.
- El usuario agent se usa para cambiar el read only mode, poner offline un equipo, ejecutar un change_master, etc.
- El usuario replication slave... para replicación ;)

Una vez todo configurado y montado, arrancarmos los servicios en cada host, uno el monitor y los demás los agentes.

Jugar con la consola

Una vez puesto en marcha todos los servidores saldrán en modo AWAITING_RECOVERY. La primera vez es necesario ponerlos online a mano:


MMM:~# mmm_control show
  db1(10.100.1.1) master/AWAITING_RECOVERY. Roles: 
  db2(10.100.1.2) master/AWAITING_RECOVERY. Roles: 
  db3(10.100.1.3) slave/AWAITING_RECOVERY. Roles: 
  db4(10.100.1.4) slave/AWAITING_RECOVERY. Roles:

Asi que los ponemos online :P

MMM:~# mmm_control set_online db1
OK: State of 'db1' changed to ONLINE. Now you can wait some time and check its new roles!
MMM:~# mmm_control set_online db2
OK: State of 'db2' changed to ONLINE. Now you can wait some time and check its new roles!
MMM:~# mmm_control set_online db3
OK: State of 'db3' changed to ONLINE. Now you can wait some time and check its new roles!
MMM:~# mmm_control set_online db4
OK: State of 'db4' changed to ONLINE. Now you can wait some time and check its new roles!

MMM:~# mmm_control show
  db1(10.100.1.1) master/ONLINE. Roles: writer(10.100.1.10)
  db2(10.100.1.2) master/ONLINE. Roles: 
  db3(10.100.1.3) slave/ONLINE. Roles: reader(10.100.1.12)
  db4(10.100.1.4) slave/ONLINE. Roles: reader(10.100.1.11)

Como podemos ver, ya están online. La IP virutal de escritura la tiene el nodo db1 mientras que los otros están en lectura. A partir de ahora el funcionamiento ya es automático. Si db1 se cae, la IP pasará a db2 y los esclavos db3 y db4 pasarán a tener como maestro a db2.

Si es necesario hacer alguna parada de servicio (cambiar hardware, mover el equipo de ubicación física, etc.) podemos dejar offline la máquina en cuestión de nuestro cluster. Por ejemplo, voy a dar de baja temporalmente db1:


MMM:~# mmm_control set_offline db1
OK: State of 'db1' changed to ADMIN_OFFLINE. Now you can wait some time and check all roles!

MMM:~# mmm_control show db1(10.100.1.1) master/ADMIN_OFFLINE. Roles: db2(10.100.1.2) master/ONLINE. Roles: writer(10.100.1.10) db3(10.100.1.3) slave/ONLINE. Roles: reader(10.100.1.12) db4(10.100.1.4) slave/ONLINE. Roles: reader(10.100.1.11)

¿Mola verdad? Has parado un servidor maestro, no te has tenido que preocupar de los esclavos y ningún cliente se ha enterado de nada :)

· Tags: , , , , ,

Wed 2 Dec

Manifiesto: En defensa de los derechos fundamentales en internet

Ante la inclusión en el Anteproyecto de Ley de Economía Sostenible de modificaciones legislativas que afectan al libre ejercicio de las libertades de expresión, información y el derecho de acceso a la cultura a través de Internet, los periodistas, bloggers, usuarios, profesionales y creadores de internet manifestamos nuestra firme oposición al proyecto, y declaramos que…

1.- Los derechos de autor no pueden situarse por encima de los derechos fundamentales de los ciudadanos, como el derecho a la privacidad, a la seguridad, a la presunción de inocencia, a la tutela judicial efectiva y a la libertad de expresión.

2.- La suspensión de derechos fundamentales es y debe seguir siendo competencia exclusiva del poder judicial. Ni un cierre sin sentencia. Este anteproyecto, en contra de lo establecido en el artículo 20.5 de la Constitución, pone en manos de un órgano no judicial -un organismo dependiente del ministerio de Cultura-, la potestad de impedir a los ciudadanos españoles el acceso a cualquier página web.

3.- La nueva legislación creará inseguridad jurídica en todo el sector tecnológico español, perjudicando uno de los pocos campos de desarrollo y futuro de nuestra economía, entorpeciendo la creación de empresas, introduciendo trabas a la libre competencia y ralentizando su proyección internacional.

4.- La nueva legislación propuesta amenaza a los nuevos creadores y entorpece la creación cultural. Con Internet y los sucesivos avances tecnológicos se ha democratizado extraordinariamente la creación y emisión de contenidos de todo tipo, que ya no provienen prevalentemente de las industrias culturales tradicionales, sino de multitud de fuentes diferentes.

5.- Los autores, como todos los trabajadores, tienen derecho a vivir de su trabajo con nuevas ideas creativas, modelos de negocio y actividades asociadas a sus creaciones. Intentar sostener con cambios legislativos a una industria obsoleta que no sabe adaptarse a este nuevo entorno no es ni justo ni realista. Si su modelo de negocio se basaba en el control de las copias de las obras y en Internet no es posible sin vulnerar derechos fundamentales, deberían buscar otro modelo.

6.- Consideramos que las industrias culturales necesitan para sobrevivir alternativas modernas, eficaces, creíbles y asequibles y que se adecuen a los nuevos usos sociales, en lugar de limitaciones tan desproporcionadas como ineficaces para el fin que dicen perseguir.

7.- Internet debe funcionar de forma libre y sin interferencias políticas auspiciadas por sectores que pretenden perpetuar obsoletos modelos de negocio e imposibilitar que el saber humano siga siendo libre.

8.- Exigimos que el Gobierno garantice por ley la neutralidad de la Red en España, ante cualquier presión que pueda producirse, como marco para el desarrollo de una economía sostenible y realista de cara al futuro.

9.- Proponemos una verdadera reforma del derecho de propiedad intelectual orientada a su fin: devolver a la sociedad el conocimiento, promover el dominio público y limitar los abusos de las entidades gestoras.

10.- En democracia las leyes y sus modificaciones deben aprobarse tras el oportuno debate público y habiendo consultado previamente a todas las partes implicadas. No es de recibo que se realicen cambios legislativos que afectan a derechos fundamentales en una ley no orgánica y que versa sobre otra materia.