Upserts automáticos de filas insertadas
ALTER o DELETE ineficientes, ya que permite insertar varias copias de la misma fila y marcar una como la versión más reciente. A su vez, un proceso en segundo plano elimina de forma asíncrona las versiones anteriores de la misma fila, imitando de forma eficiente una operación de actualización mediante inserciones inmutables.
Esto se basa en la capacidad del motor de tabla para identificar filas duplicadas. Esto se logra usando la cláusula ORDER BY para determinar la unicidad; es decir, si dos filas tienen los mismos valores en las columnas especificadas en ORDER BY, se consideran duplicadas. Una columna version, especificada al definir la tabla, permite conservar la versión más reciente de una fila cuando se identifican dos filas como duplicadas; es decir, se conserva la fila con el valor de versión más alto.
Ilustramos este proceso en el ejemplo siguiente. Aquí, las filas se identifican de forma única por la columna A (el ORDER BY de la tabla). Suponemos que estas filas se han insertado en dos lotes, lo que da lugar a la creación de dos partes de datos en disco. Más adelante, durante un proceso asíncrono en segundo plano, estas partes se fusionan.
ReplacingMergeTree también permite especificar una columna deleted. Esta puede contener 0 o 1, donde un valor de 1 indica que la fila (y sus duplicados) se ha eliminado, y en caso contrario se usa 0. Nota: Las filas eliminadas no se quitarán en el momento de la fusión.
Durante este proceso, ocurre lo siguiente durante la fusión de partes:
- La fila identificada por el valor 1 de la columna A tiene tanto una fila de actualización con versión 2 como una fila de eliminación con versión 3 (y un valor de 1 en la columna deleted). Por lo tanto, se conserva la fila más reciente, marcada como eliminada.
- La fila identificada por el valor 2 de la columna A tiene dos filas de actualización. Se conserva la fila más reciente, con un valor de 6 en la columna price.
- La fila identificada por el valor 3 de la columna A tiene una fila con versión 1 y una fila de eliminación con versión 2. Se conserva esta fila de eliminación.
Tenga en cuenta que las filas eliminadas nunca se quitan. Se pueden eliminar forzosamente con un
OPTIMIZE table FINAL CLEANUP. Esto requiere la configuración experimental allow_experimental_replacing_merge_with_cleanup=1. Esto solo debe ejecutarse en las siguientes condiciones:
- Puede estar seguro de que no se insertarán filas con versiones antiguas (de aquellas que se están eliminando con la limpieza) después de ejecutar la operación. Si se insertan, se conservarán incorrectamente, ya que las filas eliminadas ya no estarán presentes.
- Asegúrese de que todas las réplicas estén sincronizadas antes de ejecutar la limpieza. Esto puede lograrse con el comando:
Gestionar eliminaciones con ReplacingMergeTree solo se recomienda en tablas con un número bajo o moderado de eliminaciones (menos del 10 %), a menos que puedan programarse periodos de limpieza con las condiciones anteriores.
Consejo: También puede emitir OPTIMIZE FINAL CLEANUP en particiones específicas que ya no estén sujetas a cambios.
Elegir una clave primaria/de deduplicación
ORDER BY deben identificar de forma única una fila a lo largo de sus cambios. Por lo tanto, si se migra desde una base de datos transaccional como Postgres, la clave primaria original de Postgres debe incluirse en la cláusula ORDER BY de ClickHouse.
Los usuarios de ClickHouse ya estarán familiarizados con la elección de las columnas de la cláusula ORDER BY de sus tablas para optimizar el rendimiento de las consultas. En general, estas columnas deben seleccionarse en función de sus consultas más frecuentes y listarse en orden de cardinalidad creciente. Es importante destacar que ReplacingMergeTree impone una restricción adicional: estas columnas deben ser inmutables; es decir, si se replica desde Postgres, solo deben añadirse columnas a esta cláusula si no cambian en los datos subyacentes de Postgres. Aunque otras columnas puedan cambiar, estas deben mantenerse constantes para permitir la identificación única de cada fila.
Para cargas de trabajo analíticas, la clave primaria de Postgres suele tener poca utilidad, ya que rara vez realizará búsquedas puntuales de filas. Dado que recomendamos ordenar las columnas por cardinalidad creciente, y que además las coincidencias en las columnas listadas antes en ORDER BY normalmente serán más rápidas, la clave primaria de Postgres debe añadirse al final de ORDER BY (salvo que tenga valor analítico). Si en Postgres la clave primaria está formada por varias columnas, estas deben añadirse a ORDER BY, respetando la cardinalidad y la probabilidad de que aporten valor a la consulta. También puede optar por generar una clave primaria única mediante la concatenación de valores en una columna MATERIALIZED.
Considere la tabla posts del conjunto de datos Stack Overflow.
ORDER BY de (PostTypeId, toDate(CreationDate), CreationDate, Id). La columna Id, única para cada publicación, garantiza que las filas se puedan deduplicar. Se añaden al esquema las columnas Version y Deleted, según sea necesario.
Consultando ReplacingMergeTree
ORDER BY como identificador único, y conserva solo la versión más alta o elimina todos los duplicados si la versión más reciente indica una eliminación. Sin embargo, esto solo ofrece corrección eventual: no garantiza que las filas se dedupliquen, y no debe confiar en ello. Por lo tanto, las consultas pueden producir respuestas incorrectas, ya que las filas de actualización y eliminación se tienen en cuenta en las consultas.
Para obtener respuestas correctas, deberá complementar las fusiones en segundo plano con deduplicación en tiempo de consulta y la eliminación de filas borradas. Esto puede lograrse mediante el operador FINAL.
Considere la tabla posts anterior. Podemos usar el método habitual para cargar este conjunto de datos, pero especificando además una columna deleted y una columna version con valor 0. A efectos del ejemplo, cargamos solo 10000 filas.
INSERT INTO SELECT:
INSERT INTO SELECT.
FINAL a la tabla devuelve el resultado correcto.
Rendimiento de FINAL
FINAL sí añade una pequeña sobrecarga de rendimiento a las consultas.
Esto se nota más cuando las consultas no filtran por columnas de clave primaria,
ya que se leen más datos y aumenta la sobrecarga de deduplicación. Si
filtra por columnas clave mediante una condición WHERE, se reducirán los datos cargados y enviados para
la deduplicación.
Si la condición WHERE no utiliza una columna clave, actualmente ClickHouse no aprovecha la optimización PREWHERE al usar FINAL. Esta optimización busca reducir las filas leídas de las columnas no filtradas. Puede encontrar ejemplos de cómo emular este PREWHERE y, por tanto, mejorar potencialmente el rendimiento aquí.
Aprovechar las particiones con ReplacingMergeTree
do_not_merge_across_partitions_select_final=1 para mejorar el rendimiento de las consultas con FINAL. Esta configuración hace que las particiones se fusionen y se procesen de forma independiente al usar FINAL.
Considere la siguiente tabla posts, donde no usamos particionado:
FINAL tenga que hacer algo de trabajo, actualizamos 1m filas, incrementando su AnswerCount al insertar filas duplicadas.
FINAL:
do_not_merge_across_partitions_select_final=1.
Consideraciones sobre el comportamiento de las operaciones de merge
Lógica de selección de fusiones
Comportamiento de la fusión en partes grandes
max_bytes_to_merge_at_max_space_in_pool, deja de seleccionarse para futuras fusiones, incluso si min_age_to_force_merge_seconds está configurado. Como resultado, ya no se puede depender de las fusiones automáticas para eliminar los duplicados que puedan acumularse con la inserción continua de datos.
Para resolver esto, puedes ejecutar OPTIMIZE FINAL para fusionar manualmente las partes y eliminar los duplicados. A diferencia de las fusiones automáticas, OPTIMIZE FINAL omite el umbral max_bytes_to_merge_at_max_space_in_pool y fusiona las partes basándose únicamente en los recursos disponibles, en particular el espacio en disco, hasta que solo quede una parte en cada partición. Sin embargo, este enfoque puede consumir mucha memoria en tablas grandes y puede requerir ejecutarse repetidamente a medida que se añaden nuevos datos.
Como solución más sostenible para mantener el rendimiento, se recomienda particionar la tabla. Esto puede ayudar a evitar que las partes de datos alcancen el tamaño máximo de fusión y reducir la necesidad de optimizaciones manuales continuas.
Particionado y fusiones entre particiones
Ajuste de las fusiones para mejorar el rendimiento de las consultas
Ajustes recomendados
min_age_to_force_merge_seconds en un valor bajo, significativamente menor que el período de la partición. Esto minimiza el número de partes y evita fusiones innecesarias en tiempo de consulta con el operador FINAL.
Por ejemplo, considere una partición mensual que ya se ha fusionado en una sola parte. Si una pequeña inserción aislada crea una nueva parte dentro de esta partición, el rendimiento de la consulta puede verse afectado porque ClickHouse debe leer varias partes hasta que se complete la fusión. Establecer min_age_to_force_merge_seconds puede garantizar que estas partes se fusionen de forma agresiva, evitando una degradación del rendimiento de la consulta.