El teorema CAP en base de datos

Éste post me parece muy importante para poder entender como funcionan y hacer grandes sistemas pensados para escalar, sea por tráfico, por multi-sitio, multi-país, y más… todos estos sistemas distribuidos presentan la misma particularidad, y en el año 2000 un señor llamado Eric Brewer, pudo definir tres importantes propiedades y desarrollo un teorema. El se dio cuenta que mientras más aplicaciones basadas en la WEB existan, menos debemos preocuparnos por la consistencia de los datos, si queremos alta disponibilidad de nuestras aplicaciones entonces no podemos garantizar la consistencia de los datos.

El teorema CAP, también llamado formalmente Teorema de Brewer, dice que un sistema de datos distribuido pude asegurar dos de estas tres propiedades: Consistencia, Disponibilidad y Tolerancia al particionado. Bien, que significa cada una:

  • La consistencia (Consistency), Todos los nodos deben ver los mismos datos al mismo tiempo, esto quiere decir que; cualquier cambios en los datos se debe aplicar en todos los nodos, y cuando se recupere el dato tiene que ser el mismo en todos los nodos. Esto se le llama consistencia atómica, y se consigue replicando la información en todos los nodos.
  • La disponibilidad (Availability), Cada petición en un nodo debe recibir y garantizar una confirmación si ha sido resuelta satisfactoriamente. En pocas palabras, se debe leer y escribir en todos los nodos.
  • La tolerancia al particionado (Partition Tolerance), El sistema debe funcionar a pesar de que haya sido dividido por un fallo de comunicación, garantizando la disponibilidad a pesar que un nodo se separe del grupo sin importar la causa.

CAPTheorem

El teorema solo nos puede garantizar las siguientes combinaciones:

  • CP (Consistency & Partition): El sistema aplicara los cambios de forma forma consistente y aunque se pierda la comunicación entre nodos ocacionando el particionado, no se asegura que haya disponibilidad.
  • AP (Availability & Partition): El sistema siempre estará disponible a las peticiones aunque se pierda la comunicación entre los nodos ocacionando el particionado, y en consecuencia por la perdida de comunicación existirá inconsistencia porque no todos los nodos serán iguales.
  • CA (Consistency & Availability): El sistema siempre estará disponible respondiendo las peticiones y los datos procesados serán consistentes. En este caso no se puede permitir el particionado.

La correcta decisión de que combinación necesitamos depende de nuestras necesidades de negocio. Nunca olvide que lo más importante en una base de datos relacional es la Consistencia. Conociendo el teorema CAP, nos puede ayudar aún más para saber que Sistemas de Base de Datos debemos escoger, si un SQL o un NoSQL. Si queremos profundizar más en el tema, recomiendo este post.

¿Cómo actualizar los datos de una tabla sin bloquearla y desde un archivo?

Hace un tiempo escribí un post similar, la diferencia con éste es que los datos a actualizar vienen de un archivo de texto. Hay muchas formas de hacerlo, para variar quise buscar una alternativa rápida y eficaz para evitar escribir un nuevo script, y se me ocurrió usar los comandos de linux, la verdad que resulto muy bien, aquí les dejo un ejemplo:

cat tokens.csv \
| \
awk -F ',' '{ print "UPDATE users SET token = \"" $2 "\" WHERE token IS NULL AND email = \"" $1 "\";"; system("sleep 0.1");}' \
| \
mysql --login-path=foo \
--safe-updates \
--batch \
--silent \
--database=foo

Explico un poco, básicamente se lee el archivo con cat, luego se crea la sentencia UPDATE usando el awk, por cada update hacemos una pausa de 100 milisegundo para no saturar, y se lo mandamos al cliente de MySQL.

Monitorizar y resolver problemas de replicación en MySQL

La replicación de datos con slaves es algo muy fácil de configurar y aporta muchos beneficios, pero aveces trae sus grandes problemas si no tenemos en cuenta algunos aspectos de configuración, hardware y red entre otras cosas, vamos a comentar un buen grupo de ellos y ayudarte a saber donde puede estar el problema, se que es muy largo el POST, hay mucho que cubrir, pero tratare de dar la idea.

Ventajas de usar Slaves

  • Divide las escrituras en un master y lecturas en los slaves para distribuir las cargas de trabajo. Esto no lo hace MySQL de forma transparente, la aplicación debe saber que sentencias van al master y al slave.
  • Los Slaves que son los servidores de lectura, se pueden optimizar para hacer minería de datos o procesos de reportes.
  • Se puede usar un slave para remplazar el master en caso de algún desastre como una medida de prevención.
  • Como respaldo usando el retraso en la replicación para recuperar datos, ver la variable MASTER_DELAY.
  • Incluso podemos configurar el MultiMaster de dos nodos, pero esto aveces no funciona bien por como esta programada la aplicación.

Los problemas que surgen por la replicación no son nuevos, podemos clasificarlos en dos grandes grupos; Cuando se interrumpe la replicación por alguna inconsistencia, o un retraso de la replicación a pesar que no esté interrumpida.

Para saber el estado de una replicación ejecute este comando en el slave:

SHOW SLAVE STATUS\G

Recuerde que el estado de la replicación es individual en cada slave. A partir de aquí, tenemos una serie de variables que nos indica que está pasando y también les explico como resolver cualquiera de estos dos grandes problemas.

Como identificar una replicación interrumpida:

Este problema se puede presentar por una serie de motivos:

  • Inconsistencia de datos entre el Master y el Slave.
  • Diferentes configuraciones.
  • Diferente hora.
  • Problemas de red.
  • Bloqueos de tablas o de registros.
  • Muchos INSERT, UPDATE o DELETES.

Cuando se ejecuta el comando “SHOW SLAVE STATUS” que mencionamos anteriormente, entre todas las variables que nos muestra, debemos hacerle caso a estas:

  • Slave_IO_Running: Si es “Yes”, entonces el esclavo está conectado al master.
  • Slave_SQL_Running: Si es “Yes”, entonces el esclavo está procesando las consultas SQL.
  • Seconds_Behind_Master: Indica la cantidad de posiciones retrasadas del binlog, si es 0 es que todo está bien.
  • Last_Error: Si “Slave_SQL_Running” es “No”, entonces muestra la última sentencia SQL que produjo un error y se detiene la replicación.

Para resolver la mayoría de los casos, solo debemos ejecutar estos tres comandos:

STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE;

Y volver a ejecutar este comando para verificar que no hayan más errores:

SHOW SLAVE STATUS\G

Es muy importante saber porque pasó esto y como prevenirlo, el echo de saltar el error y continuar nos crea una inconsistencia de los datos, debemos tomar nota del mensaje de error y resolverlo a mano.

Como identificar el retraso de la replicación:

Primero debemos entender como funciona y cuales son los indicadores que se pueden encontrar en el estado de la replica. La replicación en MySQL trabaja con dos hilos, IO_THREAD y SQL_THREAD.

  • El primer hilo es IO_THREAD, se conecta a un maestro, lee los eventos del binarios del maestro y los va guardando en un log local llamado relay log (registro retrasado). Corresponde este hilo con la variable de estado Slave_IO_Running.
  • El segundo hilo es SQL_THREAD, lee los eventos del relay log almacenado localmente en el esclavo, el cual fue escrito por el hilo IO_THREAD y luego los aplica lo más rápido posible en el slave. Corresponde este hilo con la variable de estado Slave_SQL_Running.

Siempre que se presenta un retraso en la replicación, es importante saber cual de los dos hilos se encuentra el problema. Normalmente, los hilos de E/S no causarían enormes retrasos de replicación, ya que una vez que se leen del master, se aplican en el slave. Sin embargo, el retraso se debe a una limitación del hardware o por alguna configuración:

  • Si es por el rendimiento de la red, afecta al hilo IO_THREAD, aquí debemos evaluar si es un tema de saturación de la red, la NIC no es suficiente para el trafico, u otro problema de red.
  • Si es la velocidad del Hardware del slave al procesar el relay log es afectado por el SQL_THREAD. Diferentes configuraciones del servicio, indice de los esquemas, carga de trabajo, hardware lento.

Todo estos detalles hay que verlos con mucho cuidado, se requiere paciencia, pero en líneas generales, aquí se han mencionado todos los puntos que debemos evitar para tener problemas en la replicación Master/Slave.

Les deseo suerte.

Buscar registros huérfanos

Los registros huérfanos son aquellos que se almacenan en un modelo relacional padre-hijo, los registros padres hacen referencia a filas primarias, y los registros hijos hacen referencia a filas secundarías, esto puede pasar por varias razones en una DDBB, básicamente sino existe claves foráneas definidas, o alguien desactivo de forma momentánea el check foreign_key_checks, y para mantener la integridad de los datos debemos de vez en cuando identificar estos registros huérfanos, podríamos eliminarlos o asignarlos a un padre de forma provisional. Yo opto más por lo segundo, conservar los datos es nuestra prioridad.

Los registros huérfanos pueden tener un Foreign Key igual a NULL o tener un ID de un registro primario que no exista, pero debemos tener bastante cuidado con el NULL, ya que puede hacer referencia a una relación “PUEDE” y no “DEBE”, lo cual eliminamos algo que no debemos.

Hay varias formas de hacer esta consulta, pero solo una es la más apropiada por temas de rendimiento, seguramente la condición que pensamos de primero es NOT IN, pues esa no, ni tampoco usar un LEFT OUTER JOIN, deberías de usar NOT EXISTS que es mucho más eficiente, a continuación muestro los tres ejemplos, el último es el bueno:

A) Primer ejemplo incorrecto:

SELECT count(*)
FROM payment AS child
WHERE rental_id NOT IN (SELECT rental_id FROM rental);

B) Segundo ejemplo incorrecto:

SELECT count(*)
FROM payment AS child
LEFT OUTER JOIN rental AS parent ON parent.rental_id = child.rental_id
WHERE child.rental_id IS NOT NULL AND parent.rental_id IS NULL;

C) Primer ejemplo correcto:

SELECT count(*)
FROM payment AS child
WHERE rental_id IS NOT NULL
AND NOT EXISTS (
SELECT NULL FROM rental AS parent WHERE parent.rental_id = child.rental_id
);

Hay algo curioso en el ejemplo A y C, a ver si se dan cuenta? La única pista, es que tiene que ver con los NULL.

Ahora que los hemos identificado, que hacemos con ellos? bueno, la forma correcta sería la siguiente:

  • Agregar un registro primario (padre) para agrupar los registros secundarios (hijos) que están huérfanos.
  • Hacer un update en el Foreign Key de todos los registros secundarios con el valor correspondiente al registro primario.
  • Verificar que todo este bien.

Espero que les haya sido útil.