Introducción
Conceptos de ClickHouse
| Data type | Table |
|---|---|
| Logs | otel_logs |
| Trazas | otel_traces |
| Métricas (gauges) | otel_metrics_gauge |
| Métricas (sumas) | otel_metrics_sum |
| Métricas (histograma) | otel_metrics_histogram |
| Métricas (histogramas exponenciales) | otel_metrics_exponentialhistogram |
| Métricas (resumen) | otel_metrics_summary |
| Sesiones | hyperdx_sessions |
default; esto se puede modificar en el collector de OpenTelemetry.
Como mínimo, deberías comprender los siguientes conceptos fundamentales de ClickHouse:
| Concepto | Descripción |
|---|---|
| Tablas | Cómo las fuentes de datos en ClickStack se corresponden con las tablas subyacentes de ClickHouse. Las tablas en ClickHouse usan principalmente el motor MergeTree. |
| Partes | Cómo los datos se escriben en partes inmutables y se fusionan con el tiempo. |
| Particiones | Las particiones agrupan las partes de una tabla en unidades lógicas organizadas. Estas unidades son más fáciles de administrar, consultar y optimizar. |
| Merges | El proceso interno que fusiona partes para reducir la cantidad de partes que hay que consultar. Esencial para mantener el rendimiento de las consultas. |
| Gránulos | La unidad más pequeña de datos que ClickHouse lee y descarta durante la ejecución de consultas. |
| Claves primarias (de ordenación) | Cómo la clave ORDER BY determina la disposición de los datos en disco, la compresión y el descarte de datos en las consultas. |
- Creación de tablas en ClickHouse - Una introducción sencilla a las tablas.
- Parts
- Partitions
- Merges
- Primary keys/indexes
- Cómo almacena datos ClickHouse: partes y gránulos - Guía más avanzada sobre cómo se estructuran y consultan los datos en ClickHouse, con explicaciones detalladas de los gránulos y las claves primarias.
- MergeTree- Guía avanzada de referencia de MergeTree útil para comandos y detalles internos.
Optimización 1. Materializar atributos consultados con frecuencia
LogAttributes, ScopeAttributes y ResourceAttributes, y convertirlos en columnas de nivel superior mediante columnas materializadas.
Por sí sola, esta optimización suele ser suficiente para escalar despliegues de ClickStack a decenas de terabytes al día y debe aplicarse antes de considerar técnicas de ajuste más avanzadas.
Por qué materializar atributos
Map(String, String). Aunque esto aporta flexibilidad, consultar subclaves de un mapa tiene una implicación importante en el rendimiento.
Al consultar una sola clave de una columna Map, ClickHouse debe leer del disco toda la columna del mapa. Si el mapa contiene muchas claves, esto genera E/S innecesaria y hace que las consultas sean más lentas en comparación con leer una columna dedicada.
Materializar los atributos a los que se accede con frecuencia evita esta sobrecarga, ya que extrae el valor en el momento de la inserción y lo almacena como una columna de primera clase.
Columnas materializadas:
- Se calculan automáticamente durante las inserciones
- No se pueden establecer explícitamente en sentencias INSERT
- Admiten cualquier expresión de ClickHouse
- Permiten convertir String en tipos numéricos o de fecha más eficientes
- Permiten usar skip indexes y la clave primaria
- Reducen las lecturas de disco al evitar acceder al mapa completo
ClickStack detecta automáticamente las columnas materializadas extraídas de mapas y las utiliza de forma transparente durante la ejecución de consultas, incluso cuando los usuarios siguen consultando la ruta del atributo original.
Ejemplo
ResourceAttributes:
ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c":
Esto genera un predicado SQL similar a:
ResourceAttributes completa para cada fila coincidente, lo que puede ser muy grande si el Map contiene muchas claves.
Si este atributo se consulta con frecuencia, debe materializarse como una columna de nivel superior.
Para extraer el nombre del pod de Kubernetes en el momento de la inserción, añada una columna materializada:
PodName.
Ahora los usuarios pueden consultar los nombres de los pods de Kubernetes de forma eficiente con sintaxis Lucene; por ejemplo, PodName:"checkout-675775c4cc-f2p9c"
En los datos recién insertados, esto evita por completo el acceso al mapa y reduce significativamente la E/S.
Sin embargo, incluso si los usuarios siguen consultando la ruta del atributo original, por ejemplo ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c", ClickStack reescribirá automáticamente la consulta internamente para usar la columna materializada PodName, es decir, usando el predicado:
De forma predeterminada, las columnas materializadas se excluyen de las
SELECT * queries. Esto preserva la garantía de que los resultados de las consultas siempre pueden reinsertarse en la tabla.Materialización de datos históricos
system.mutations, por ejemplo.
is_done = 1 en la mutación correspondiente.
Optimización 2. Añadir índices de omisión
- Filtrado de cadenas con alta cardinalidad, como TraceId, identificadores de sesión, claves de atributo o valores
- Filtrado de subclaves de mapas acelerado por índices de texto en las columnas
*AttributeItems - Filtrado por rangos numéricos, como la duración del span
text(tokenizer = 'array') en todo el esquema en lugar de filtros de Bloom, y añade un índice text(tokenizer = 'splitByNonAlpha') en lower(Body) para la búsqueda de texto completo. Consulte “Tables and schemas used by ClickStack” para ver el DDL completo.
Filtros de Bloom
PodName:"checkout-675775c4cc-f2p9c".
Los filtros de Bloom son más eficaces cuando la distribución de los valores hace que un valor dado aparezca en un número relativamente pequeño de partes. Esto suele ocurrir de forma natural en cargas de trabajo de observabilidad, donde metadatos como los nombres de pods de Kubernetes, los ID de trace o los identificadores de sesión se correlacionan con el tiempo y, por tanto, se agrupan según la clave de ordenación de la tabla.
Como ocurre con todos los índices de omisión, los filtros de Bloom deben añadirse de forma selectiva y validarse con patrones de consulta reales para garantizar que aportan un beneficio medible; consulte “Evaluar la eficacia del índice de omisión.”
Índices de texto
WHERE. Los índices de texto son índices invertidos que asignan tokens a desplazamientos exactos dentro de una parte. Como evalúan desplazamientos en lugar de gránulos y no producen falsos positivos, normalmente pueden resolver la condición WHERE sin cargar la columna subyacente. Esta es una optimización conocida como lectura directa. Dado que la carga de datos suele ser el principal factor en el tiempo de consulta, la lectura directa puede reducir de forma significativa la latencia de la consulta.
Además, los índices de texto también se pueden consultar directamente, lo que permite el autocompletado y otras funciones de introspección en ClickStack.
Dos tokenizadores cubren la mayoría de los patrones de ClickStack:
| Tokenizer | Se usa para | Columna típica |
|---|---|---|
array | Indexación de elementos Array(String) como tokens completos | mapKeys(...), *AttributeItems |
splitByNonAlpha | Búsqueda de texto completo a nivel de palabra en cadenas de texto libre | Body, lower(Body), SpanName |
Tokenizador array para columnas Map y columnas de tipo array
mapKeys y los arrays materializados de elementos con
el tokenizador array:
splitByNonAlpha para el cuerpo de los logs
Body se beneficia de un índice de texto
splitByNonAlpha. ClickStack define este índice en lower(Body) para que las búsquedas de
Lucene que no distinguen entre mayúsculas y minúsculas puedan usarlo:
text(tokenizer = 'splitByNonAlpha') en
lower(Body), reescribe consultas de Lucene sobre columnas implícitas como error o
"connection refused" como hasAllTokens(lower(Body), lower(...)), que el
índice puede resolver sin leer la columna Body completa. Para la mayoría de
las cargas de trabajo de logs de observabilidad, esta es, con diferencia, la mayor mejora de velocidad de filtrado disponible.
Índices de texto frente a
tokenbf_v1El antiguo tipo de índice tokenbf_v1 (que todavía se usa en el esquema predeterminado de trazas para
lower(SpanName)) es funcionalmente similar, pero está en desuso en ClickHouse 26.2
y versiones posteriores. Los nuevos índices de búsqueda de texto deben usar text(tokenizer = ...).Índices de texto en el esquema predeterminado de logs
otel_logs, sincronizado con upstream, incluye todos los índices de texto mencionados anteriormente: text(tokenizer = 'array') en TraceId, en cada arreglo mapKeys(...) y *AttributeItems, y text(tokenizer = 'splitByNonAlpha') en lower(Body) para la búsqueda de texto completo. Para ver el DDL canónico, consulta “Tables and schemas used by ClickStack”; a continuación se reproduce el mismo esquema.
Índices MinMax
SpanAttributes:
Materializar el índice de omisión
Materialización de índices de omisiónMaterializar un índice de omisión suele ser una operación ligera y segura, especialmente en el caso de los índices MinMax. En el caso de índices de filtro de Bloom sobre conjuntos de datos grandes, puede ser preferible materializarlos por partición para controlar mejor el uso de recursos; por ejemplo:
is_done = 1 para la mutación correspondiente.
Una vez completada, confirme que se han creado los datos del índice:
0.01 a 0.05 produce un índice más pequeño que se evalúa más rápido, a costa de una poda menos agresiva. Aunque pueden omitirse menos gránulos, la latencia global de la consulta puede mejorar gracias a una evaluación más rápida del índice.
Por lo tanto, ajustar los parámetros del filtro de Bloom es una optimización que depende de la carga de trabajo y debe validarse con patrones de consulta reales y volúmenes de datos similares a los de producción.
Para obtener más información sobre los índices de omisión, consulta la guía “Comprender los índices de omisión de datos de ClickHouse.”
Evaluación de la eficacia de los skip indexes
EXPLAIN indexes = 1, que muestra cuántas partes y gránulos se descartan en cada etapa de la planificación de la consulta. En la mayoría de los casos, conviene ver una reducción significativa del número de gránulos en la etapa Skip, idealmente después de que la clave primaria ya haya reducido el espacio de búsqueda. Los skip indexes se evalúan después de la poda de particiones y de la poda por clave primaria, por lo que su impacto se mide mejor en relación con las partes y los gránulos que quedan.
EXPLAIN confirma si se está aplicando la poda, pero no garantiza una mejora neta del rendimiento. Los skip indexes tienen un coste de evaluación, especialmente si el índice es grande. Haga siempre benchmark de las consultas antes y después de añadir y materializar un índice para confirmar mejoras reales de rendimiento.
Por ejemplo, considere el skip index predeterminado de filtro Bloom para TraceId incluido en el esquema Traces predeterminado:
EXPLAIN indexes = 1 para ver lo eficaz que es en una consulta selectiva:
FORMAT Null para evitar la sobrecarga de la serialización de resultados y desactiva la caché de condiciones de consulta para que las ejecuciones sean repetibles:
use_query_condition_cache garantiza que los resultados no se vean afectados por decisiones de filtrado almacenadas en caché, y establecer use_skip_indexes = 0 proporciona una base de referencia limpia para la comparación. Si la poda es efectiva y el coste de evaluar el índice es bajo, la consulta indexada debería ser notablemente más rápida, como en el ejemplo anterior.
Cuándo añadir índices de omisión de datos
minmax casi siempre es una buena opción. Es ligero, barato de evaluar y puede ser eficaz para predicados de rango, especialmente cuando los valores están poco ordenados o quedan confinados a rangos estrechos dentro de las partes. Incluso cuando minmax no ayuda con un patrón de consulta concreto, su sobrecarga suele ser lo bastante baja como para que siga siendo razonable mantenerlo.
En las columnas de texto, prefiera los índices de texto cuando sean compatibles; en caso contrario, recurra a filtros Bloom. Los índices de texto aceleran los mismos filtros de igualdad e IN que los filtros Bloom y, además, habilitan predicados basados en tokens (hasToken, hasAllTokens, has) que se usan en la búsqueda de texto completo y en la optimización de lectura directa de Map. En clústeres más antiguos que todavía no admiten índices de texto, los filtros Bloom siguen siendo una opción sólida.
Los filtros Bloom son más eficaces en columnas de texto con alta cardinalidad en las que cada valor tiene una frecuencia relativamente baja, lo que significa que la mayoría de las partes y los gránulos no contienen el valor buscado. Como regla general, los filtros Bloom son más prometedores cuando la columna tiene al menos 10.000 valores distintos, y a menudo ofrecen el mejor rendimiento con más de 100.000 valores distintos. También son más eficaces cuando los valores coincidentes se agrupan en un número reducido de partes secuenciales, lo que normalmente ocurre cuando la columna está correlacionada con la clave de ordenación. De nuevo, esto puede variar según el caso; nada sustituye a las pruebas en el mundo real.
Optimización 3. Lectura directa de Map
LogAttributes['k8s.pod.name'] = 'checkout', ClickHouse debe leer la columna Map LogAttributes completa desde
disco y desempaquetar cada fila para evaluar el predicado. Materializar los atributos consultados con frecuencia
resuelve esto para las claves que conoce de antemano, pero no escala bien para
atributos arbitrarios por los que los usuarios filtran de forma ad hoc.
Incluso si un esquema tiene índices sobre mapKeys y mapValues, esos índices pueden indicarle si una fila tiene una clave determinada y si tiene un valor determinado, pero no si la clave y el valor pertenecen a la misma entrada. En otras palabras, mapKeys responde mapContainsKey(ResourceAttributes, 'foo') y mapValues responde mapContainsValue(ResourceAttributes, 'bar'), pero ninguno responde ResourceAttributes['foo'] = 'bar'.
Al concatenar las claves y los valores en una única columna Array(String), la
optimización de lectura directa de Map permite responder
ResourceAttributes['foo'] = 'bar' sin cargar el Map subyacente. Los Maps suelen ser grandes y aumentan
de tamaño a medida que crece el volumen. En combinación con una reescritura de
consultas a nivel de aplicación, los filtros de igualdad sobre cualquier subclave de Map se convierten en una única llamada has(...)
respaldada por ese índice, sin deserialización del Map en tiempo de consulta. Además,
el único coste de almacenamiento es el del text index, ya que la columna subyacente es
una columna ALIAS y no se almacena.
Esta optimización es automática. ClickStack incluye las columnas e
índices necesarios en las tablas predeterminadas de logs y trazas, y reescribe los filtros con subíndice de Map
en tiempo de ejecución cuando el ClickHouse server conectado admite la primitiva
subyacente. Si su esquema no contiene estas columnas, o si tiene
columnas Map adicionales que quiere acelerar además de las predeterminadas, siga leyendo para
habilitarlas.
Esquema
Map que quieras acelerar, ClickStack define una
columna Array(String) ALIAS que combina cada clave con su valor mediante =:
text(tokenizer = 'array') sobre
la columna ALIAS almacena un token por cada par key=value, que ClickHouse usa
para descartar gránulos sin tocar el Map de origen:
| Tabla | columnas ALIAS | Índices de texto |
|---|---|---|
otel_logs | ResourceAttributeItems, ScopeAttributeItems, LogAttributeItems | idx_res_attr_items, idx_scope_attr_items, idx_log_attr_items |
otel_traces | ResourceAttributeItems, SpanAttributeItems | idx_res_attr_items, idx_span_attr_items |
Reescritura de consultas
LogAttributeItems, descarta filas
enteras que no contienen el token key=value y nunca deserializa el
Map de origen LogAttributes en las filas que no coinciden. Para cargas de trabajo de
observabilidad con alta cardinalidad, esto suele ofrecer una reducción de un orden de magnitud
en E/S respecto al acceso mediante subíndice al Map.
La reescritura se aplica automáticamente: las consultas guardadas, dashboards y alertas que
hacen referencia a LogAttributes['key'] se benefician de esta mejora de velocidad sin ningún cambio.
Requisitos de versión de ClickHouse
SELECT version(), almacenada en caché por conexión) y solo
emite la forma reescrita cuando el servidor alcanza o supera el umbral. Los
servidores más antiguos vuelven automáticamente a la forma original de subíndice de Map.
| Rama de ClickHouse | Versión mínima |
|---|---|
| 26.2 | 26.2.19.43 |
| 26.3 | 26.3.12.3 |
| 26.4 | 26.4.3.37 |
| 26.5+ | Todas las versiones |
Por qué ALIAS y no MATERIALIZEDEl array
items es una vista de datos que ya están en la columna Map.
Almacenarlo dos veces —una vez en el Map y otra en el array— duplicaría la E/S de escritura
sin aportar nuevos patrones de consulta. El índice de texto de la columna ALIAS se
construye en el momento de la inserción a partir de los mismos datos de origen, por lo que la optimización solo añade
la huella del índice en disco.Optimización 4. Modificar la clave primaria
Nota sobre la terminologíaA lo largo de este documento, el término “clave de ordenación” se usa indistintamente con “primary key”. En sentido estricto, estos conceptos difieren en ClickHouse, pero en ClickStack normalmente se refieren a las mismas columnas especificadas en la cláusula
ORDER BY de la tabla. Para más información, consulta la documentación de ClickHouse sobre cómo elegir una clave primaria distinta de la clave de ordenación.- Logs (
otel_logs) -(toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp) - Trazas (
otel_traces) -(ServiceName, SpanName, toDateTime(Timestamp))
Cómo elegir una clave primaria
Modificar la clave primaria predeterminadaLas claves primarias predeterminadas son suficientes en la mayoría de los casos. Los cambios deben hacerse con cautela y solo con una comprensión clara de los patrones de consulta. Modificar una clave primaria puede degradar el rendimiento de otros flujos de trabajo, por lo que es fundamental realizar pruebas.
- Seleccione columnas que se ajusten a sus filtros habituales y patrones de acceso. Si normalmente inicia investigaciones de observabilidad filtrando por una columna específica, p. ej., el nombre del pod de Kubernetes, esta columna se usará con frecuencia en las cláusulas
WHERE. Priorice incluirlas en la clave frente a otras que se utilicen con menos frecuencia. - Prefiera columnas que ayuden a excluir un gran porcentaje del total de filas al filtrar, reduciendo así la cantidad de datos que es necesario leer. Los nombres de servicio y los códigos de estado suelen ser buenos candidatos; en este último caso, solo si filtra por valores que excluyen la mayoría de las filas. Por ejemplo, filtrar por códigos 200 coincidirá, en la mayoría de los sistemas, con la mayor parte de las filas, mientras que los errores 500 corresponderán a un subconjunto pequeño.
- Prefiera columnas que probablemente estén muy correlacionadas con otras columnas de la tabla. Esto ayudará a garantizar que esos valores también se almacenen de forma contigua, lo que mejora la compresión.
- Las operaciones
GROUP BY(agregaciones para gráficos) yORDER BY(ordenación) sobre columnas incluidas en la clave de ordenación pueden ser más eficientes en el uso de memoria.
Cambio de la clave primaria
SeverityText antes de ServiceName.
Crear una nueva tabla
Clave de ordenación frente a clave primariaTen en cuenta que, en el ejemplo anterior, es necesario especificar
PRIMARY KEY y ORDER BY.
En ClickStack, casi siempre son iguales.
ORDER BY controla la disposición física de los datos, mientras que PRIMARY KEY define el índice disperso.
En casos poco frecuentes, con cargas de trabajo muy grandes, pueden diferir, pero la mayoría de los usuarios debería mantenerlos alineados.Rara vez compensa hacer backfill de los datos existentes en una nueva tabla a gran escala. El coste de cómputo y E/S suele ser alto y no justifica las ventajas de rendimiento. En su lugar, deja que los datos más antiguos expiren mediante TTL, mientras que los datos más recientes se benefician de la clave mejorada.
SeverityText como primera columna de la clave primaria. En este caso, se crea una tabla para los datos nuevos y se conserva la tabla anterior para el análisis histórico.
Crear una nueva tabla
Crea la nueva tabla con la clave primaria deseada. Ten en cuenta el sufijo_23_01_2025: adáptalo para que corresponda a la fecha actual. Por ejemplo:Crear una tabla Merge
El motor Merge (no debe confundirse con MergeTree) no almacena datos por sí mismo, pero permite leer simultáneamente de cualquier número de tablas.currentDatabase() asume que el comando se ejecuta en la base de datos correcta. De lo contrario, especifica explícitamente el nombre de la base de datos.otel_logs.Actualizar la UI de ClickStack para leer desde la tabla Merge
Configura la UI de ClickStack para usarotel_logs_merge como tabla de la fuente de datos de logs.En este punto, las escrituras continúan en otel_logs con la clave primaria original, mientras que las lecturas usan la tabla Merge. No hay cambios visibles para los usuarios ni impacto en la ingestión.Intercambiar las tablas
Ahora se utiliza una sentenciaEXCHANGE para intercambiar atómicamente los nombres de las tablas otel_logs y otel_logs_23_01_2025.otel_logs con la clave primaria actualizada. Los datos existentes permanecen en otel_logs_23_01_2025 y siguen siendo accesibles a través de la tabla Merge. El sufijo indica la fecha en que se aplicó el cambio y representa la marca temporal más reciente contenida en esa tabla.Este proceso permite cambiar la clave primaria sin interrumpir la ingestión y sin impacto visible para el usuario.SeverityNumber debería formar parte de la clave primaria en lugar de SeverityText. El siguiente proceso puede adaptarse tantas veces como sea necesario para aplicar cambios en la clave primaria.
Crear una nueva tabla
Crea la nueva tabla con la clave primaria deseada. En el ejemplo siguiente,30_01_2025 se usa como sufijo para indicar la fecha de la tabla. Por ejemplo:Intercambiar las tablas
Ahora se utiliza una sentenciaEXCHANGE para intercambiar atómicamente los nombres de las tablas otel_logs y otel_logs_30_01_2025.otel_logs con la clave primaria actualizada. Los datos antiguos permanecen en otel_logs_30_01_2025, accesibles a través de la tabla Merge.Tablas redundantesSi hay políticas TTL configuradas, lo cual se recomienda, las tablas con claves primarias antiguas que ya no reciben escrituras se irán vaciando gradualmente a medida que caduquen los datos. Deben supervisarse y limpiarse periódicamente cuando ya no contengan datos. Por ahora, este proceso de limpieza es manual.
Aceleración de búsquedas por fila con columnas de bloque
(_block_number, _block_offset) que la identifica de forma única dentro de una
parte. Cuando haces clic en una fila de logs en la UI de ClickStack para abrir el panel de detalles, ClickStack
emite una consulta de seguimiento para obtener esa única fila. Sin columnas de bloque, la
cláusula WHERE de la fila debe incluir suficientes columnas — normalmente la clave primaria
más Body y SeverityText — para distinguirla. Con columnas de bloque,
la clave primaria más _block_number más _block_offset es suficiente. Las
columnas grandes como Body nunca se leen para la búsqueda, lo que acelera efectivamente la consulta.
ClickStack detecta la configuración a partir de la instrucción CREATE de la tabla y genera
automáticamente la cláusula WHERE más concisa cuando ambas columnas están habilitadas. No
se requiere ningún cambio en la configuración de la aplicación.
Para habilitar esta optimización en una tabla existente de logs o trazas:
ALTER. Las partes existentes siguen
usando el método anterior de búsqueda por fila hasta que una fusión las reescriba.
Optimización 5. Uso de vistas materializadas
Optimización 6. Aprovechar las proyecciones
ORDER BY de la tabla base, lo que permite a ClickHouse descartar datos de forma más eficaz para patrones de acceso que no se ajustan al orden original.
Las vistas materializadas pueden lograr un efecto similar al escribir explícitamente filas en una tabla de destino independiente con una clave de ordenación distinta. La diferencia principal es que ClickHouse mantiene las proyecciones de forma automática y transparente, mientras que las vistas materializadas son tablas explícitas que ClickStack debe registrar y seleccionar de forma intencionada.
Cuando una consulta se ejecuta sobre la tabla base, ClickHouse evalúa la disposición base y las proyecciones disponibles, examina sus índices primarios y selecciona la disposición que puede producir el resultado correcto leyendo la menor cantidad de gránulos. Esta decisión la toma automáticamente el analizador de consultas.
Por lo tanto, en ClickStack, las proyecciones son más adecuadas para la reordenación pura de datos, donde:
- Los patrones de acceso son sustancialmente distintos de la clave primaria predeterminada
- No resulta práctico cubrir todos los flujos de trabajo con una sola clave de ordenación
- Quiere que ClickHouse elija de forma transparente la disposición física óptima
Proyecciones de ejemplo
Usa comodinesEn la proyección de ejemplo anterior, se usa un comodín (
SELECT *). Aunque seleccionar un subconjunto de columnas puede reducir la sobrecarga de escritura, también limita cuándo puede usarse la proyección, ya que solo pueden aprovecharla las consultas que puedan resolverse por completo con esas columnas. En ClickStack, esto suele restringir el uso de la proyección a casos muy específicos. Por este motivo, en general se recomienda usar un comodín para maximizar su aplicabilidad.Materializar una proyección puede llevar mucho tiempo y consumir una cantidad considerable de recursos. Como los datos de observabilidad normalmente expiran por TTL, esto solo debe hacerse cuando sea absolutamente necesario. En la mayoría de los casos, basta con dejar que la proyección se aplique solo a los datos recién ingeridos, para que optimice los intervalos de tiempo consultados con más frecuencia, como las últimas 24 horas.
SELECT *) y los filtros de la consulta se ajustan claramente al ORDER BY de la proyección.
Las consultas que filtran por TraceId (especialmente con igualdad) e incluyen un intervalo de tiempo se beneficiarían de la proyección anterior. Por ejemplo:
TraceId, o que filtran principalmente por otras dimensiones que no ocupan las primeras posiciones en la clave de ordenación de la proyección, normalmente no se beneficiarán de ello (y en su lugar pueden leer desde la estructura base).
Las proyecciones también pueden almacenar agregaciones (de forma similar a las vistas materializadas). En ClickStack, por lo general no se recomiendan las agregaciones basadas en proyecciones, porque su selección depende del analizador de ClickHouse y su uso puede ser más difícil de controlar y comprender. En su lugar, prefiera vistas materializadas explícitas que ClickStack pueda registrar y seleccionar intencionadamente en la capa de aplicación.
TraceId específico).
Costes y recomendaciones
- Sobrecarga de inserción: Una proyección
SELECT *con una clave de ordenación distinta equivale, en la práctica, a escribir los datos dos veces, lo que incrementa la E/S de escritura y puede requerir CPU adicional y mayor rendimiento de disco para sostener la ingestión. - Úselas con moderación: Lo mejor es reservar las proyecciones para patrones de acceso realmente distintos, en los que una segunda ordenación física permita una poda significativa en una gran parte de las consultas; por ejemplo, cuando dos equipos consultan el mismo conjunto de datos de maneras fundamentalmente diferentes.
- Valídelo con benchmarks: Como con cualquier ajuste, compare la latencia real de las consultas y el uso de recursos antes y después de añadir y materializar una proyección.
Proyecciones ligeras con _part_offset
Las proyecciones ligeras están en Beta para ClickStackNo se recomiendan las proyecciones ligeras
basadas en _part_offset para las cargas de trabajo de ClickStack. Aunque reducen el almacenamiento y la E/S de escritura, pueden introducir más accesos aleatorios en tiempo de consulta, y su comportamiento en producción a escala de observabilidad aún se está evaluando. Esta recomendación puede cambiar a medida que esta funcionalidad madure y recopilemos más datos operativos._part_offset a la tabla base, en lugar de duplicar filas completas. Esto puede reducir considerablemente la sobrecarga de almacenamiento, y las mejoras recientes permiten la poda a nivel de gránulo, por lo que se comportan más como verdaderos índices secundarios. Consulte:
Alternativas
- Configurar su OpenTelemetry Collector para que escriba en dos tablas con claves
ORDER BYdiferentes y crear fuentes de ClickStack independientes para cada tabla. - Crear una vista materializada como canalización de copia; es decir, adjuntar una vista materializada a la tabla principal que seleccione filas sin procesar en una tabla secundaria con una clave de ordenación distinta (un patrón de desnormalización o enrutamiento). Cree una fuente para esta tabla de destino. Puede encontrar ejemplos aquí.