Comparación de esquemas normalizados frente a desnormalizados
Una técnica habitual popularizada por las soluciones NoSQL consiste en desnormalizar los datos cuando no hay soporte para
JOIN, almacenando todas las estadísticas o filas relacionadas en una fila principal como columnas y objetos anidados. Por ejemplo, en un esquema de ejemplo para un blog, podemos almacenar todos los Comments como un Array de objetos en sus publicaciones correspondientes.
Cuándo usar la desnormalización
- Desnormalice tablas que cambian con poca frecuencia o en las que se pueda tolerar un retraso antes de que los datos estén disponibles para consultas analíticas; es decir, los datos pueden recargarse por completo en un lote.
- Evite desnormalizar relaciones de muchos a muchos. Esto puede hacer necesario actualizar muchas filas si cambia una sola fila de origen.
- Evite desnormalizar relaciones de alta cardinalidad. Si cada fila de una tabla tiene miles de entradas relacionadas en otra tabla, estas deberán representarse como un
Array, ya sea de un tipo primitivo o de tuplas. En general, no se recomiendan arrays con más de 1000 tuplas. - En lugar de desnormalizar todas las columnas como objetos anidados, considere desnormalizar solo una estadística mediante vistas materializadas (consulte más abajo).
Evite la desnormalización en datos que se actualizan con frecuencia
- Ejecutar las sentencias JOIN correctas cuando cambia una fila de una tabla. Idealmente, esto no debería hacer que se actualicen todos los objetos implicados en el JOIN, sino solo aquellos que se hayan visto afectados. Modificar los JOIN para filtrar de forma eficiente las filas correctas, y lograrlo con alto throughput, requiere herramientas externas o trabajo de ingeniería.
- Las actualizaciones de filas en ClickHouse deben gestionarse cuidadosamente, lo que añade complejidad.
Por lo tanto, es más habitual un proceso de actualización por lotes, en el que todos los objetos desnormalizados se recargan periódicamente.
Casos prácticos de desnormalización
Posts que ya se ha desnormalizado con estadísticas como AnswerCount y CommentCount; los datos de origen se proporcionan en este formato. En realidad, puede que queramos normalizar esta información, ya que es probable que cambie con frecuencia. Muchas de estas columnas también están disponibles a través de otras tablas; por ejemplo, los comentarios de una publicación pueden obtenerse mediante la columna PostId y la tabla Comments. A efectos de este ejemplo, asumimos que las publicaciones se recargan en un proceso por lotes.
También nos limitamos a considerar la desnormalización de otras tablas en Posts, ya que la tomamos como nuestra tabla principal para analítica. Desnormalizar en la dirección opuesta también sería apropiado para algunas consultas, con las mismas consideraciones anteriores.
Para cada uno de los siguientes ejemplos, suponga que existe una consulta que requiere usar ambas tablas en un join.
Posts and Votes
insert para cargar los datos:
Score actual representa una de esas estadísticas, es decir, el total de votos positivos menos los votos negativos. Idealmente, bastaría con poder recuperar estas estadísticas durante la consulta con una búsqueda simple (consulta diccionarios).
Users e Insignias
Users e Insignias:
Primero insertamos los datos con el siguiente comando:
Puede que queramos desnormalizar estadísticas de badges en users, p. ej., el número de badges. Consideramos un ejemplo de este tipo al usar diccionarios para este conjunto de datos durante la inserción.
Posts y PostLinks
PostLinks conectan Posts que los usuarios consideran relacionados entre sí o duplicados. La siguiente consulta muestra el esquema y el comando de carga:
Ejemplo sencillo de estadística
INSERT INTO SELECT que combina nuestra estadística de duplicados con nuestros posts.
Aprovechar tipos complejos para relaciones uno a muchos
- Tuples con nombre: permiten representar una estructura relacionada como un conjunto de columnas.
- Array(Tuple) o Nested: un array de tuples con nombre, también conocido como Nested, donde cada entrada representa un objeto. Aplicable a relaciones uno a muchos.
PostLinks en Posts.
Cada publicación puede contener varios enlaces a otras publicaciones, como se mostró antes en el esquema de PostLinks. Como tipo Nested, podríamos representar estas publicaciones enlazadas y duplicadas de la siguiente manera:
Tenga en cuenta el uso del parámetro flatten_nested=0. Recomendamos deshabilitar el aplanado de los datos anidados.
Podemos realizar esta desnormalización mediante un INSERT INTO SELECT con una consulta OUTER JOIN:
Fíjate en el tiempo. Hemos conseguido desnormalizar 66 m de filas en unos 2 minutos. Como veremos más adelante, esta es una operación que podemos programar.Fíjate en el uso de las funciones
groupArray para agrupar PostLinks en un array para cada PostId antes del join. Después, este array se filtra en dos sublistas: LinkedPosts y DuplicatePosts, que además excluyen cualquier resultado vacío del outer join.
Podemos seleccionar algunas filas para ver nuestra nueva estructura desnormalizada:
Orquestación y planificación de la desnormalización
Lote
INSERT INTO SELECT. Esto resulta adecuado para transformaciones periódicas por lotes.
Los usuarios tienen varias opciones para orquestar esto en ClickHouse, siempre que un proceso periódico de carga por lotes sea aceptable:
- Vistas materializadas actualizables - Las vistas materializadas actualizables pueden utilizarse para programar periódicamente una consulta cuyos resultados se envían a una tabla de destino. Al ejecutar la consulta, la vista garantiza que la tabla de destino se actualice de forma atómica. Esto proporciona un mecanismo nativo de ClickHouse para programar este trabajo.
- Herramientas externas - Utilizar herramientas como dbt y Airflow para programar periódicamente la transformación. La integración de ClickHouse para dbt garantiza que esto se realice de forma atómica: se crea una nueva versión de la tabla de destino y luego se intercambia atómicamente con la versión que recibe consultas (mediante el comando EXCHANGE).