¿Por qué no debes usar relaciones polimórficas?

La verdad no se como empezar este post, ya que el tema es muy delicado en el mundo de RubyOnRails y algo en Java, no quiero ofender a la comunidad, sino ayudarlos hacer un buen modelo de datos dentro de un Manejador de Bases de Datos Relacionales (RDBMS). Considero que primero debemos enterarnos que son las relaciones polimórficas, luego hablar de los problemas que conlleva y por último como podemos evitarlos.

¿Qué son las relaciones polimórficas?

Es una forma de relacionar muchas tablas con una sola mediante una única relación, esto se debe que programamos orientado a objetos y tratamos la base de datos de la misma forma, creando relaciones entre objetos, es una implementación de la librería (gema) ActiveRecord que compone el framework RubyOnRails. Depende del enfoque, esto nos puede traer un ahorro en el uso de tablas como mayor complicación del código.

polymorphism_relationship

Como vemos en el modelo anterior, explica un caso hipotético de que una imagen puede estar relacionada con uno o varios productos y/o empleados.

El problema

La razón es muy simple, hasta el día de hoy NO hay un Manejador de Bases de Datos Relacionales que tenga un una restricción de integridad referencial para las relaciones polimórficas, y en el álgebra relacional no existe forma de representarlo. El día que uses MySQL u otro para tus cosas y tenga implementado esta funcionalidad y sea un software seguro, no habrá ningún problema.

Por otro lado, se crea una discusión ideológica llamada object-relational impedance mismatch que es bastante interesante.

Al no existir una restricción que soporte este tipo de invento, nos encontramos con una serie de inconvenientes técnicos:

Se presenta una inconsistencia de datos porque no puede gestionarlos el RDBMS, y se tiende a delegar en la aplicación. El problema es que la aplicación se ejecuta en varios servidores, y los datos terminan en la base de datos quien tiene la última palabra si es valido o no, mientras que los servidores de aplicaciones por la concurrencia trabajan de forma aislada. Éste es uno de los típicos argumentos cuando no hay relaciones definidas dentro del RDBMS.

Por otro lado, las consultas se hacen más complicadas y difícil de mantener, porque puede que necesitemos consultas diferentes dependiendo del ROW, muestro un simple ejemplo:

SELECT *
FROM purchases p
LEFT OUTER JOIN shoes s ON s.id = p.item_id AND p.item_type = 'Shoe'
LEFT OUTER JOIN bags  b ON b.id = p.item_id AND p.item_type = 'Bag'
WHERE p.id = 1;

Como vemos en la consulta anterior, debemos hacer LEFT OUTER JOIN por cada tabla “relacionada” para obtener una lista de todos los productos comprados, se puede simplificar mucho si usamos la siguiente forma:

SELECT *
FROM purchases p
JOIN items i ON i.id = p.item_id
WHERE e.id = 1;

Para poder distinguir si nuestro item es un Zapato o un Bolso u otra cosa, simplemente podemos implementar atributos, o ir más allá y usar EAV.

Por otro lado, a nivel de programación puede ser que sea mucho más simple, porque podemos usar una misma línea para buscar en muchas otras tablas, mientras con la forma tradicional de hacer relaciones puede dependiendo del caso generar más líneas de código.

La solución

Siempre hay una forma de hacerlo bien, vamos a usar el ejemplo anterior, podemos resolverlo de varias formas, siendo la segunda la mejor alternativas.

  • Creamos una columna en la tabla pictures por cada tabla relacionada (employee_id y product_id).
  • Creamos una columna (picture_id) en las tablas employees y products que se relaciona con la tabla pictures.

Quiero mostrar varios ejemplos, el primero sería una consulta SQL usando relaciones polimórficas, y el segundo ejemplo es usando la forma correcta de normalización:

SELECT *
FROM employees e
JOIN pictures p ON p.imageable_id = e.id AND p.imageable_type = 'Employee'
WHERE e.id = 1;

Como podemos observar la consulta es mucho más compleja por tener una condición más que evaluar.

SELECT *
FROM employees e
JOIN pictures p ON p.id = e.picture_id
WHERE e.id = 1;

En este caso es la representación correcta por un correcto modelo de datos. Una consulta SQL mucho más simple.

Conclusión

Hay mucha información por Internet con las practicas más idóneas, dejo algunos ejemplos:

Desde el punto de vista del desarrollador puede ser ventajoso, pero es un gran problema para la base de datos por romper la integridad, complejidad del mododelo y menor flexibilidad, debemos pensar en función de la calidad de nuestros datos y del producto que estamos ofreciendo. Esto me recuerda que “si alimentamos nuestro sistema con datos basura, los procesamos, y obtendremos datos basura”. No queremos esto verdad?

Bases de datos relacionales y no relacionales

El gran dilema, bases de datos relacionales (RDBMS) y no relacionales (NoSQL), todos preguntan, todos hablan de ello, estamos comparando cual es mejor, en fin, hay una gran incertidumbre en el tema, muchos apuntan a un extremo o al otro, cometen errores y nos olvidamos de ver con objetividad. Quiero explicar de que va todo esto de una forma simple para entendernos. Quiero recordar que grandes volúmenes de datos no son un simple millón de rows, son mucho más, billones por ejemplo, ahora imagina billones de rows que interactúan con otros billones de rows para generar información más significativa, cuando hay grandes volúmenes de información se aprecia todo de una forma diferente, esto impacta en tiempo y dinero.

Cada tipo de base de datos nació en una época y con unas necesidades diferentes, y aún ambas son necesarias hoy en día. Todo el problema parte por los grandes volúmenes de datos, ¿Cómo los procesamos?, ¿Como puedo mantener mi infraestructura tecnológica?, ¿Cómo hago para que millones de usuarios puedan usar mi aplicación de forma muy rápida? y quien sabe cuantas preguntas más…

En la década de los 70 nacen las bases de datos relacionales que le dan uso a un lenguaje de consulta estructurado llamado SQL, la idea era organizar la información (normalizar) en grupos de datos bajo una semejanza, y así poder mantener una coherencia entre ellos (integridad), fue creciendo el volumen de información pero pocos tenían acceso a manipularla, mientras Internet fue expandiéndose y cada vez más personas acceden a los datos, nos dimos cuenta que los RDBMS son muy lentos, y como fueron diseñados traerían problemas, se inventaron muchas soluciones, pero como todo, se llega a un límite. Y aquí entran las NoSQL, una forma de almacenar y manipular los datos sin necesidad de ser restrictivo como el SQL, con un objetivo muy básico, sacrificar integridad por velocidad.

La causa principal del rendimiento es mantener la integridad de los datos, cuesta mucho, por otro lado, no podemos decir que la humanidad es ordenada, hay un desorden “natural” en ella, a pesar que algunos seamos unos maniacos de clasificar y etiquetar todo con un nombre, el problema con las RDBMS, es que cada ves que debemos organizar la información es algo muy costoso y directamente proporcional al volumen de datos, mientras más información, más tiempo y recursos necesitamos para ordenarla y procesarla, por eso que no se normaliza y se permite la redundancia.

Ahora imagina que tienes una empresa con información distribuida en muchos servidores de bases de datos, sea del tipo que sea, necesitamos procesar todos estos volúmenes de información sin importar que cambien, con esta simple idea entramos en el mundo del BigData, ¿Cómo funciona? imagina muchos servidores buscando información al mismo tiempo, organizando y procesando para dar un par de números en unos segundos, esto es lo que hacen las NoSQL, es maravilloso no?

Todo se resumen a cuales son tus necesidades, y a partir de allí posiblemente tengas una infraestructura híbrida. Lo importante es saber que información debe ser consistente y que no. Lo que quiero decir, por ejemplo, es que todos los datos son importantes, pero hay unos que son más importantes que otros, lo que podemos llamar la base de la información, que está almacenada en una RDBMS, luego esa base es consumida por los usuarios generando grandes volúmenes de datos que son almacenadas en las NoSQL.

Ejemplos:

  • Una tienda de artículos, todo el detalle del artículo está en una RDBMS, pero hay una serie de funcionalidades para el usuario que generan grandes volúmenes de información que pueden estar en una NoSQL, como las; lista de deseos, favoritos, comentarios, puntuaciones y el motor de búsqueda.
  • Un video juegos online, guarda información del estado de tu partida, se reanuda cada vez que entras en la aplicación, y cambia a medida que la uses. Éste es un buen caso para guardar los datos en una NoSQL.
  • Centralizas todos los logs generados por tus aplicaciones para luego buscar indicadores de seguridad, errores, y de más, también es una buena opción usar una NoSQL.

Usar una NoSQL tampoco es así de simple, tenemos varios tipos (Clave Valor, Documentales y Grafos) y cada una con un propósito diferente. Inclusive, el modelado de datos entre ambas es diferente; para una RDBMS se usa la normalización y para una NoSQL de tipo Clave Valor se usa arreglo asociativo.

Para terminar, es importante no confundir términos usados exclusivamente en las RDBMS con las NoSQL, por ejemplo el acrónimo ACID, en las NoSQL no se aplica porqué no hay relaciones, pero si se garantiza que los datos han sido grabados correctamente.

Niveles de aislamiento en Base de Datos

El aislamiento es una parte importante de la propiedad ACID que garantiza que las transacciones sean fiables. Esto permite que las transacciones que se ejecutan simultáneamente no interfieran con otras, garantizando la integridad de los datos, al no existir aislamiento en una transacción podría modificar los datos que otra transacción está leyendo, por lo que se crea una inconsistencia cuando se crean datos.

Ahora que entendemos que es el aislamiento en términos generales, vamos a conocer cuales son los niveles de aislamiento, estos determinan como las transacciones se comportan con otras transacciones, es como ser más o menos restrictivo. Escoger cual es el mejor nivel depende de las necesidades de la aplicación, primero debe entender cuales son los beneficios y consecuencias de cada una de ellas.

InnoDB soporta los cuatro niveles de aislamiento estándar, a continuación se describen los estándares usados en muchos maneadores de bases de datos relacionales, no solo se aplica para MySQL, sino para Oracle, SQL Server y PostgreSQL.

  • READ-UNCOMMITTED (LECTURA NO CONFIRMADA): La ejecución de la instrucciones SELECT se llevan a cabo sin bloqueo, puede utilizar una versión antigua de una fila que ya no existe. Por lo tanto, el uso de este nivel no tiene aislamiento y no garantiza la trasacción, tales lecturas no son consistentes. Esto también se le llama una lectura sucia. De lo contrario, este nivel de aislamiento funciona igual que “READ COMMITTED”.
  • READ-COMMITTED (LECTURA CONFIRMADA): Está es la opción favorita de Oracle y la que se recomienda para muchos casos. Con éste nivel de aislamiento se evita el fenómeno de la lectura sucia, porque los cambios no confirmados no son visibles para cualquier otra transacción, hasta que se confirme el cambio. Dentro de este nivel de aislamiento, cada SELECT utiliza su propia instantánea de los datos que se confirmo (commit) antes de la ejecución de la instrucción SELECT. Ahora, ya que cada SELECT tiene su propia instantánea, por lo que el mismo SELECT cuando se ejecuta varias veces durante la misma transacción podría regresar diferentes conjuntos de resultados. Este fenómeno se le llama lectura no repetible.
  • REPEATABLE-READ (LECTURA REPETIBLE): Lee todos los datos de forma coherente dentro de la misma transacción, es como hacer una foto instantánea de los datos desde la primera lectura. Con este nivel de aislamiento se evita el fenómeno de la lectura no repetible. Este nivel de aislamiento devuelve el mismo conjunto de resultados para diferentes SELECT dentro de una misma transacción. Una instantánea de la SELECT se toma la primera vez que se ejecuta durante la transacción y la misma instantánea se utiliza dentro de la transacción cada vez que se ejecuta el mismo SELECT. Una transacción que se ejecuta en este nivel de aislamiento no tiene en cuenta los cambios de los datos realizados por otras transacciones, independientemente de si los cambios se han confirmado (commit) o no. Esto asegura que las lecturas siempre son consistentes (repetible). Este nivel de aislamiento es el predeterminado para InnoDB. Aunque este nivel de aislamiento resuelve el problema de lectura no repetible, pero hay otro fenómeno fantasma.
  • SERIALIZABLE: Con éste nivel de aislamiento se evita el fenómeno de fantasma. Coloca un bloqueo de rango en el conjunto de datos, cuando las transacciones se ejecuta en este nivel de aislamiento se bloquean todos los registros y recursos que se tiene acceso, así bloquea todo cambio, impidiendo que otros usuarios actualizar o insertar filas en el conjunto de datos hasta que la transacción se ha completado. Este nivel de aislamiento es el más fuerte posible.

Cada uno de estos niveles de aislamiento tienen sus beneficios y consecuencias, vamos a explicar cada situación:

Se puede evitar el nivel de aislamiento SERIALIZABLE

Si, Como vemos es el más restrictivo sacrificando consistencia por rendimiento, un nivel de aislamiento parecido es REPEATABLE-READ, tiene una forma de bloqueo especial que ayuda a evitar los fenómenos fantasmas, podemos asegurarnos de hacer un buen bloqueo sin fenómenos fantasmas usando la estructura SELECT con FOR UPDATE o LOCK IN SHARE MODE. El bloqueo compartido permite a otras transacciones leer los registros examinados, pero no actualizar o borrar mientras que otra transacción esté interviniendo.

Realicación y Niveles de aislamiento

El tipo de replicación predeterminada en MySQL es la replicación basada en declaraciones [Statement Based Replication (SBR)], mantiene los cambios de los datos por la re-ejecución de las sentencias SQL ejecutadas en el maestro a los esclavos. Esto requiere que el nivel de aislamiento sea más estricto, de modo que los cambios de datos deben ser consistentes, de tal manera que el mismo SQL debe garantizar los mismos cambios en el esclavo. Cuando usamos este tipo de replicación puedes configurar el nivel de aislamiento en SERIALIZABLE o REPEATABLE-READ. Si usas la versión 5.1 o superior de MySQL debes usar el nivel de aislamiento READ-COMMITTED para garantizar la consistencia de datos.

Los niveles de rendimiento y aislamiento

Como hemos podido ver, mientras más bloqueos menos rendimiento y mayor consistencia, la intención es buscar un equilibrio según nuestras necesidades, ya que los extremos son perjudiciales. Usar el nivel de aislamiento SERIALIZABLE no es para nada favorable a nivel de rendimiento, pero usar READ-UNCOMMITTED mejora mucho el rendimiento pero no garantiza la integridad de los datos, por lo que nos quedan las dos opciones del medio.

El rol de un DBA

Puede que este papel hoy en día se este perdiendo, y se haya mezclado con los de Operaciones o con algún Desarrollador, está mal!, en proyectos grandes es muy importante dedicarle todo el tiempo posible, llega un momento que el volumen de datos y el tráfico se vuelve insostenible, a este punto, cuando algo falla es catastrófico y no suele arreglarse rápidamente, la prevención es bastante crucial y se vuelve una eterna lucha.

Ser Administrador de Bases de Datos es algo bastante exigente, porqué debes saber un poco de todo; de programación, de sistemas y mucho de Bases de Datos. El dominio de cada una de ellas es bastante crucial para entender como gestionar bien los recursos, prevenir incidencias e incluso resolverlas.

DBA

Debes trabajar codo a codo con los Desarrolladores, antes que ellos se pongan a programar, debes estar diseñando nuevos modelos de datos, pensando principalmente como influye en el rendimiento y mantener la integridad, una vez terminada la parte de programación, debes hacer un Code Review de la Pull Request, luego es enviada al equipo de Quality Assurance, de ellos te puedes esperar de todo, es la idea no? Una vez que todo está listo, pasa a producción y aún no puedes quedarte tranquilo, debes seguir vigilando.

Cada empresa tiene un modelos de datos particular, depende mucho del modelo del negocio, de lo que consigues cuando llegas, de las personas, incluso el lenguaje de programación puede influenciar. Por eso que hay que ser flexibles hasta cierto punto.

Mientras no estás trabajando con los desarrolladores, te dedicas más a la parte de sistemas, hacer actualizaciones, ver porque hay retraso de una replica, revisar un respaldo, revisar las gráficas buscando anomalías, tunear, probar cosas nuevas y ver que puedes ir mejorando, aquí la creatividad se te puede ir de las manos.

Para saber que funciones cumple el rol, te listos unas cuantas:

  • Instalar, actualizar e implementar parches.
  • Diseñar las bases de datos.
  • Desarrollar, automatizar y probar estrategias de respaldo y restauración.
  • Administrar los servidores de bases de datos.
  • Administrar el almacenamiento y IOPS de MySQL.
  • Administrar la seguridad de los servidores y de MySQL.
  • Administrar los objetos de MySQL, como; tablas, particiones, índices, eventos, vistas y procedimientos almacenados.
  • Desarrollar y mantener mecanismos de ofuscación de datos sensibles en los respaldos para los desarrolladores.
  • Monitorizar y configurar la base de datos para el óptimo rendimiento.
  • Investigar, y reunir información de diagnostico para realizar reportes de futuras incidencias.
  • Evaluar y probar nuevas funcionalidad de MySQL.
  • Gestionar las migraciones o cambios que se llevan sobre el modelo.
  • Documentar todo.

También podemos ir un poco más allá, mezclando lo nuestro con la parte de Operaciones y así somos más ágiles e independientes:

  • Implementar una infraestructura de monitoring y alertas, como puede ser Nagios & Cacti.
  • Implementar y mantener los balanceadores de carga como el HAProxy.
  • Ofrecer una plataforma de bases de datos para Business Intelligence con MongoDB o Hadoop.
  • Usar herramientas para gestionar de forma automática la configuración, como Puppet o Chef.

Es mucho para no seguirlos aburriendo, para la próxima.