La ingestión eficiente de datos es la base de las implementaciones de ClickHouse de alto rendimiento. Seleccionar la estrategia de inserción adecuada puede afectar drásticamente al rendimiento, el coste y la fiabilidad. Esta sección describe las prácticas recomendadas, las ventajas e inconvenientes y las opciones de configuración para ayudarle a tomar la decisión correcta para su carga de trabajo.
Inserciones sincrónicas por defecto
Por defecto, las inserciones en ClickHouse son sincrónicas. Cada consulta de inserción crea inmediatamente una parte de almacenamiento en disco, incluidos sus metadatos e índices.
Utilice inserciones sincrónicas si puede agrupar los datos en lotes en el clienteSi no es posible, consulte Inserciones asincrónicas a continuación.
A continuación, resumimos brevemente cómo funcionan las inserciones de MergeTree en ClickHouse:
Para un rendimiento óptimo, los datos deben ① agruparse en lotes, por lo que el tamaño del lote es la primera decisión.
ClickHouse almacena los datos insertados en disco, ordenados por las columnas de clave primaria de la tabla. La segunda decisión es si ② ordenar previamente los datos antes de transmitirlos al servidor. Si un lote llega preordenado por las columnas de clave primaria, ClickHouse puede omitir el paso ⑩ de ordenación, acelerando la ingestión.
Si los datos que se van a ingestar no tienen un formato predefinido, la decisión clave es elegir un formato. ClickHouse admite la inserción de datos en más de 70 formatos. Sin embargo, al usar el cliente de línea de comandos de ClickHouse o clientes para lenguajes de programación, esta elección suele gestionarse automáticamente. Si es necesario, esta selección automática también puede sustituirse explícitamente.
La siguiente decisión importante es ④ si se deben comprimir los datos antes de transmitirlos al servidor de ClickHouse. La compresión reduce el tamaño de la transferencia y mejora la eficiencia de la red, lo que se traduce en transferencias de datos más rápidas y un menor uso de ancho de banda, especialmente en conjuntos de datos grandes.
Los datos se ⑤ transmiten a una interfaz de red de ClickHouse, ya sea la interfaz nativa o la interfaz HTTP (que comparamos más adelante en esta publicación).
Después de ⑥ recibir los datos, ClickHouse ⑦ los descomprime si se utilizó compresión y luego ⑧ los interpreta a partir del formato en el que se enviaron originalmente.
Usando los valores de esos datos con formato y la sentencia DDL de la tabla de destino, ClickHouse ⑨ construye en memoria un bloque en formato MergeTree, ⑩ ordena las filas por las columnas de la clave primaria si aún no están preordenadas, ⑪ crea un índice primario disperso, ⑫ aplica compresión por columna y ⑬ escribe los datos en disco como una nueva ⑭ parte de datos.
Inserciones por lotes en modo síncrono
Los mecanismos descritos anteriormente ilustran una sobrecarga constante independientemente del tamaño de la inserción, lo que convierte el tamaño del lote en la optimización más importante para el rendimiento de ingesta. Agrupar las inserciones en lotes reduce la proporción de sobrecarga respecto al tiempo total de inserción y mejora la eficiencia del procesamiento.
Recomendamos insertar los datos en lotes de al menos 1.000 filas, e idealmente de entre 10.000 y 100.000 filas. Un menor número de inserciones de mayor tamaño reduce la cantidad de partes escritas, minimiza la carga de fusión y disminuye el uso general de recursos del sistema.
Para que una estrategia de inserción síncrona sea eficaz, este procesamiento por lotes en el lado del cliente es imprescindible.
Si no puede agrupar los datos en lotes en el lado del cliente, ClickHouse admite inserciones asíncronas que trasladan el procesamiento por lotes al servidor (consulte Inserciones asíncronas).
Independientemente del tamaño de las inserciones, recomendamos mantener el número de consultas de inserción en torno a una consulta de inserción por segundo. El motivo de esta recomendación es que las partes creadas se fusionan en segundo plano en partes más grandes (para optimizar los datos para las consultas de lectura), y enviar demasiadas consultas de inserción por segundo puede dar lugar a situaciones en las que la fusión en segundo plano no pueda seguir el ritmo del número de partes nuevas. No obstante, puede usar una frecuencia mayor de consultas de inserción por segundo cuando utilice inserciones asíncronas (consulte Inserciones asíncronas).
Garantizar reintentos idempotentes
Las inserciones síncronas también son idempotentes. Al usar motores MergeTree, ClickHouse deduplica las inserciones de forma predeterminada. Esto protege frente a casos de fallo ambiguos, como:
- La inserción se realizó correctamente, pero el cliente nunca recibió una confirmación debido a una interrupción de red.
- La inserción falló en el servidor y se agotó el tiempo de espera.
En ambos casos, es seguro reintentar la inserción, siempre que el contenido y el orden del lote se mantengan idénticos. Por este motivo, es fundamental que los clientes reintenten de forma consistente, sin modificar ni reordenar los datos.
Elige el destino de inserción adecuado
Para clústeres segmentados, tienes dos opciones:
- Insertar directamente en una tabla MergeTree o ReplicatedMergeTree. Esta es la opción más eficiente cuando el cliente puede realizar balanceo de carga entre segmentos. Con
internal_replication = true, ClickHouse se encarga de la replicación de forma transparente.
- Insertar en una tabla distribuida. Esto permite a los clientes enviar datos a cualquier nodo y dejar que ClickHouse los reenvíe al segmento correcto. Es más sencillo, pero ligeramente menos eficiente debido al paso adicional de reenvío. Sigue siendo recomendable usar
internal_replication = true.
En ClickHouse Cloud, todos los nodos leen y escriben en un único segmento. Las inserciones se equilibran automáticamente entre nodos. Puedes enviar las inserciones directamente al endpoint expuesto.
Elegir el formato de entrada adecuado es crucial para una ingestión de datos eficiente en ClickHouse. Con más de 70 formatos compatibles, seleccionar la opción de mayor rendimiento puede afectar significativamente la velocidad de inserción, el uso de CPU y memoria, y la eficiencia general del sistema.
Aunque la flexibilidad es útil para la ingeniería de datos y las importaciones basadas en archivos, las aplicaciones deben priorizar los formatos orientados al rendimiento:
- formato Native (recomendado): El más eficiente. Orientado a columnas, con un análisis mínimo requerido en el servidor. Se usa de forma predeterminada en los clientes de Go y Python.
- RowBinary: Formato eficiente basado en filas, ideal si la transformación a formato columnar es difícil del lado del cliente. Lo utiliza el cliente de Java.
- JSONEachRow: Fácil de usar, pero costoso de analizar. Adecuado para casos de uso de bajo volumen o integraciones rápidas.
La compresión desempeña un papel fundamental para reducir la sobrecarga de red, acelerar las inserciones y disminuir los costos de almacenamiento en ClickHouse. Si se utiliza de forma eficaz, mejora el rendimiento de la ingestión sin requerir cambios en el formato de los datos ni en el esquema.
Comprimir los datos de inserción reduce el tamaño de la carga útil que se envía por la red, lo que minimiza el uso de ancho de banda y acelera la transmisión.
En las inserciones, la compresión es especialmente eficaz cuando se utiliza con el formato Native, que ya se ajusta al modelo interno de almacenamiento columnar de ClickHouse. En esta configuración, el servidor puede descomprimir y almacenar los datos directamente de forma eficiente, con una transformación mínima.
Usa LZ4 por velocidad y ZSTD por relación de compresión
ClickHouse admite varios códecs de compresión durante la transmisión de datos. Dos opciones habituales son:
- LZ4: Rápido y ligero. Reduce significativamente el tamaño de los datos con una sobrecarga mínima de CPU, lo que lo hace ideal para inserciones de alto rendimiento y lo convierte en la opción predeterminada en la mayoría de los clientes de ClickHouse.
- ZSTD: Ofrece una mayor relación de compresión, pero consume más CPU. Es útil cuando los costes de transferencia de red son altos, como en escenarios entre regiones o entre proveedores Cloud, aunque aumenta ligeramente el consumo de cómputo del lado del cliente y el tiempo de descompresión en el servidor.
Práctica recomendada: usa LZ4, salvo que tengas un ancho de banda limitado o costes de salida de datos; en ese caso, considera ZSTD.
En las pruebas del benchmark FastFormats, las inserciones Native comprimidas con LZ4 redujeron el tamaño de los datos en más de un 50 %, lo que redujo el tiempo de ingestión de 150 s a 131 s para un conjunto de datos de 5.6 GiB. Al cambiar a ZSTD, ese mismo conjunto de datos se comprimió hasta 1.69 GiB, pero aumentó ligeramente el tiempo de procesamiento en el servidor.
La compresión reduce el uso de recursos
La compresión no solo reduce el tráfico de red, sino que también mejora la eficiencia en el uso de CPU y memoria en el servidor. Con datos comprimidos, ClickHouse recibe menos bytes y dedica menos tiempo a procesar entradas grandes. Este beneficio es especialmente importante al ingestar desde varios clientes concurrentes, como en escenarios de observabilidad.
El impacto de la compresión en la CPU y la memoria es modesto con LZ4 y moderado con ZSTD. Incluso bajo carga, la eficiencia del lado del servidor mejora debido al menor volumen de datos.
Combinar la compresión con la agrupación en lotes y un formato de entrada eficiente (como Native) ofrece el mejor rendimiento de ingestión.
Al usar la interfaz nativa (p. ej., clickhouse-client), la compresión LZ4 está habilitada de forma predeterminada. Opcionalmente, puedes cambiar a ZSTD mediante la configuración.
Con la interfaz HTTP, usa el encabezado Content-Encoding para aplicar compresión (p. ej., Content-Encoding: lz4). Todo el payload debe comprimirse antes de enviarlo.
Ordenar previamente si el costo es bajo
Ordenar previamente los datos por clave primaria antes de la inserción puede mejorar la eficiencia de la ingestión en ClickHouse, especialmente en lotes grandes.
Cuando los datos llegan ya ordenados, ClickHouse puede omitir o simplificar el paso interno de ordenación durante la creación de partes, lo que reduce el uso de CPU y acelera el proceso de inserción. El ordenamiento previo también mejora la eficiencia de la compresión, ya que los valores similares quedan agrupados, lo que permite que códecs como LZ4 o ZSTD logren una mejor relación de compresión. Esto resulta especialmente beneficioso cuando se combina con inserciones en lotes grandes y compresión, ya que reduce tanto la sobrecarga de procesamiento como la cantidad de datos transferidos.
Dicho esto, ordenar previamente es una optimización opcional, no un requisito. ClickHouse ordena los datos con gran eficiencia mediante procesamiento en paralelo y, en muchos casos, la ordenación del lado del servidor es más rápida o más práctica que ordenarlos previamente del lado del cliente.
Recomendamos ordenar previamente solo si los datos ya están casi ordenados o si los recursos del lado del cliente (CPU, memoria) son suficientes y están infrautilizados. En casos de uso sensibles a la latencia o de alto throughput, como la observabilidad, donde los datos llegan desordenados o desde muchos agentes, a menudo es mejor omitir el ordenamiento previo y confiar en el rendimiento nativo de ClickHouse.
Las inserciones asíncronas en ClickHouse ofrecen una potente alternativa cuando la agrupación por lotes del lado del cliente no es viable. Esto resulta especialmente valioso en cargas de trabajo de observabilidad, donde cientos o miles de agentes envían datos continuamente —logs, métricas y trazas—, a menudo en cargas útiles pequeñas y en tiempo real. Almacenar temporalmente los datos en el lado del cliente en estos entornos aumenta la complejidad, ya que requiere una cola centralizada para garantizar que se puedan enviar lotes lo bastante grandes.
No se recomienda enviar muchos lotes pequeños en modo síncrono, ya que esto provoca la creación de muchas partes. Esto dará lugar a un rendimiento deficiente de las consultas y a errores de “too many part”.
Las inserciones asíncronas trasladan la responsabilidad de la agrupación por lotes del cliente al servidor al escribir los datos entrantes en un búfer en memoria y, después, vaciarlos al almacenamiento según umbrales configurables. Este enfoque reduce significativamente la sobrecarga de creación de partes, disminuye el uso de CPU y garantiza que la ingestión siga siendo eficiente, incluso con alta concurrencia.
El comportamiento principal se controla mediante la configuración async_insert.
Las inserciones asíncronas son compatibles tanto con las interfaces HTTP como con native TCP.
Cuando están habilitadas (async_insert = 1), las inserciones se almacenan en búfer y solo se escriben en disco una vez que se cumple una de las condiciones de vaciado:
El primer umbral que se alcanza desencadena el vaciado.
Este proceso de agrupación por lotes es invisible para los clientes y ayuda a ClickHouse a fusionar de forma eficiente el tráfico de inserción procedente de múltiples fuentes. Sin embargo, hasta que no se produce un vaciado, los datos no pueden consultarse. Es importante destacar que existen múltiples búferes por combinación de forma de inserción y configuración y que, en los clusters, los búferes se mantienen por nodo, lo que permite un control detallado en entornos multiinquilino. Por lo demás, la mecánica de inserción es idéntica a la descrita para las inserciones síncronas.
Elegir un modo de retorno
El comportamiento de las inserciones asíncronas se ajusta aún más mediante la configuración wait_for_async_insert.
Cuando se establece en 1 (el valor predeterminado), ClickHouse solo confirma la inserción después de que los datos se hayan vaciado correctamente en disco. Esto garantiza sólidas garantías de durabilidad y simplifica el manejo de errores: si algo sale mal durante el vaciado, el error se devuelve al cliente. Este modo se recomienda para la mayoría de los escenarios de producción, especialmente cuando los fallos de inserción deben rastrearse de forma fiable.
Los benchmarks muestran que escala bien con la concurrencia, tanto si ejecuta 200 como 500 clientes, gracias a las inserciones adaptativas y a un comportamiento estable en la creación de partes.
Configurar wait_for_async_insert = 0 habilita el modo “fire-and-forget”. En este caso, el servidor confirma la inserción en cuanto los datos se almacenan en el búfer, sin esperar a que lleguen al almacenamiento.
Esto ofrece inserciones de latencia ultrabaja y máximo rendimiento, ideal para datos de alta velocidad y baja criticidad. Sin embargo, esto implica concesiones: no hay garantía de que los datos se persistan, los errores solo aparecen durante el vaciado y no existe una cola dead-letter para las inserciones fallidas; para rastrear fallos es necesario inspeccionar los registros del servidor y las tablas del sistema posteriormente. Use este modo solo si su workload puede tolerar la pérdida de datos.
Los benchmarks también demuestran una reducción sustancial de partes y un menor uso de CPU cuando los vaciados del búfer son poco frecuentes (por ejemplo, cada 30 segundos), pero el riesgo de fallo silencioso sigue presente.
Nuestra recomendación firme es usar async_insert=1,wait_for_async_insert=1 si utiliza inserciones asíncronas. Usar wait_for_async_insert=0 es muy arriesgado porque es posible que su cliente de INSERT no detecte si hay errores, y además puede provocar una posible sobrecarga si su cliente sigue escribiendo rápidamente en una situación en la que el ClickHouse server necesita ralentizar las escrituras y crear cierta contrapresión para garantizar la fiabilidad del service.
Inserciones asíncronas adaptativas
Desde la versión 24.2, ClickHouse usa tiempos de espera adaptativos para el vaciado de forma predeterminada (async_insert_use_adaptive_busy_timeout). En lugar de un intervalo de vaciado fijo, el tiempo de espera se ajusta dinámicamente entre un mínimo (async_insert_busy_timeout_min_ms, 50 ms de forma predeterminada) y un máximo (async_insert_busy_timeout_max_ms, 200 ms de forma predeterminada o 1000 ms en Cloud) en función de la tasa de llegada de datos.
Cuando los datos llegan con frecuencia, el tiempo de espera se mantiene más cerca del mínimo para vaciar antes y reducir la latencia de extremo a extremo. Cuando los datos son dispersos, aumenta hacia el máximo para acumular lotes más grandes. Esto resulta especialmente útil en el modo predeterminado (wait_for_async_insert=1), donde un tiempo de espera fijo alto obligaría a los clientes a quedar bloqueados durante todo el intervalo incluso cuando los datos ya están listos para vaciarse.
La validación del esquema y el análisis de los datos se realizan durante el vaciado del búfer, no cuando se recibe el insert. Si alguna fila de una consulta de inserción tiene un error de análisis o de tipo, no se vacía ningún dato de esa consulta — se rechaza toda la carga útil de la consulta. En el modo predeterminado (wait_for_async_insert=1), el error se devuelve al cliente. En el modo fire-and-forget, los errores se escriben en los logs del servidor y en la tabla system.asynchronous_inserts.
Cada vaciado crea al menos una parte por cada valor distinto de la clave de partición en el búfer. Incluso en tablas sin clave de partición, un solo vaciado puede producir varias partes si los datos almacenados en el búfer superan max_insert_block_size (valor predeterminado: ~1 millón de filas).
Aunque uses inserciones asíncronas, puedes seguir encontrándote con errores de “too many parts” si la clave de particionamiento tiene una cardinalidad alta.
Deduplicación y fiabilidad
De forma predeterminada, ClickHouse realiza la deduplicación automática en las inserciones síncronas, lo que hace que los reintentos sean seguros en caso de fallo. Sin embargo, esto está deshabilitado para las inserciones asíncronas, a menos que se habilite explícitamente (no debe habilitarse si tiene vistas materializadas dependientes; consulte el issue).
En la práctica, si la deduplicación está activada y se reintenta la misma inserción —debido, por ejemplo, a un timeout o a una caída de red—, ClickHouse puede ignorar el duplicado de forma segura. Esto ayuda a mantener la idempotencia y evita escribir los datos dos veces.
Habilitación de inserciones asíncronas
Las inserciones asíncronas se pueden habilitar para un usuario concreto o para una consulta específica:
-
Habilitación de inserciones asíncronas a nivel de usuario. Este ejemplo usa el usuario
default; si crea un usuario distinto, sustituya ese nombre de usuario:
ALTER USER default SETTINGS async_insert = 1
-
Puede especificar la configuración de las inserciones asíncronas mediante la cláusula SETTINGS de las consultas de inserción:
INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
-
También puede especificar la configuración de las inserciones asíncronas como parámetros de conexión al usar un cliente de ClickHouse para un lenguaje de programación.
Por ejemplo, así puede hacerlo dentro de una connection string de JDBC cuando usa el JDBC driver de Java de ClickHouse para conectarse a ClickHouse Cloud:
"jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,wait_for_async_insert=1"
Las inserciones asíncronas no se aplican a las consultas INSERT INTO ... SELECT. Cuando la inserción contiene una cláusula SELECT, la consulta siempre se ejecuta de forma síncrona, independientemente de la configuración de async_insert.
Vaciado de búferes al apagar
Para vaciar todos los búferes pendientes de inserción asíncrona —por ejemplo, durante un apagado controlado o antes de una tarea de mantenimiento—, ejecute:
SYSTEM FLUSH ASYNC INSERT QUEUE
Esto garantiza que los datos almacenados en el búfer se escriban en el almacenamiento antes de que el servidor se detenga.
Comparación con las tablas Buffer
Las inserciones asíncronas son el sustituto moderno de las tablas Buffer. Diferencias clave:
- No se requieren cambios de DDL. Las inserciones asíncronas son transparentes: se habilita una configuración, no se crean tablas adicionales.
- Búfer por forma. Las inserciones asíncronas mantienen búferes independientes para cada combinación única de forma de consulta y configuración, lo que permite aplicar políticas de vaciado granulares. Las tablas Buffer usan un único búfer por tabla de destino.
- Durabilidad. En el modo predeterminado (
wait_for_async_insert=1), los datos se confirman en disco antes de que el cliente reciba la confirmación. Las tablas Buffer funcionan en modo fire-and-forget: los datos almacenados en el búfer se pierden si se produce un fallo.
- Comportamiento del clúster. En los clústeres, los búferes de inserción asíncrona se mantienen por nodo. Las tablas Buffer requieren crearse explícitamente en cada nodo.
Elija una interfaz: HTTP o nativa
ClickHouse ofrece dos interfaces principales para la ingestión de datos: la interfaz nativa y la interfaz HTTP, cada una con sus ventajas e inconvenientes en términos de rendimiento y flexibilidad. La interfaz nativa, utilizada por clickhouse-client y por algunos clientes para lenguajes como Go y C++, está diseñada específicamente para ofrecer el máximo rendimiento. Siempre transmite los datos en el formato Native, muy eficiente, de ClickHouse, admite compresión por bloques con LZ4 o ZSTD y minimiza el procesamiento del lado del servidor al trasladar al cliente tareas como el análisis y la conversión de formato.
Incluso permite calcular en el lado del cliente los valores de las columnas MATERIALIZED y DEFAULT, lo que permite al servidor omitir por completo estos pasos. Esto hace que la interfaz nativa sea ideal para escenarios de ingestión de alto rendimiento en los que la eficiencia es fundamental.
A diferencia de muchas bases de datos tradicionales, ClickHouse también admite una interfaz HTTP. Esta, en cambio, prioriza la compatibilidad y la flexibilidad. Permite enviar datos en cualquier formato compatible, incluidos JSON, CSV, Parquet y otros, y tiene amplio soporte en la mayoría de los clientes de ClickHouse, incluidos los de Python, Java, JavaScript y Rust.
A menudo se prefiere al protocolo nativo de ClickHouse, ya que permite gestionar fácilmente el tráfico con balanceadores de carga. Cabe esperar pequeñas diferencias en el rendimiento de las inserciones con el protocolo nativo, que tiene algo menos de sobrecarga.
Sin embargo, carece de la integración más profunda del protocolo nativo y no puede realizar optimizaciones del lado del cliente, como el cálculo de valores materializados o la conversión automática al formato Native. Aunque las inserciones HTTP pueden seguir comprimiéndose mediante encabezados HTTP estándar (p. ej., Content-Encoding: lz4), la compresión se aplica a toda la carga útil en lugar de a bloques de datos individuales. Esta interfaz suele preferirse en entornos donde la simplicidad del protocolo, el balanceo de carga o una amplia compatibilidad de formatos son más importantes que el rendimiento puro.
Para obtener una descripción más detallada de estas interfaces, consulte aquí.