Todos los productos
Search
Centro de documentos

ApsaraDB for ClickHouse:Compatibility and performance bottleneck analysis and solutions for self-managed ClickHouse migration to ApsaraDB for ClickHouse

Última actualización: Sep 05, 2026

Después de migrar datos de un clúster de ClickHouse autogestionado a la nube, es posible que encuentre problemas de compatibilidad y rendimiento. Para garantizar una migración sin contratiempos, se recomienda ejecutar este análisis en un entorno de pruebas antes de cambiar el tráfico de producción.

Contexto

Los clústeres de ClickHouse autogestionados que se migran a ApsaraDB for ClickHouse pueden presentar los siguientes problemas:

  • Problemas de compatibilidad de versiones:

    • Problemas de compatibilidad del motor MaterializedMySQL

    • Problemas de compatibilidad de SQL

  • Agotamiento de recursos de CPU y memoria insuficiente después de la migración a ApsaraDB for ClickHouse

Para resolver estos problemas, enfóquese en la compatibilidad y el rendimiento durante la migración.

Esta guía aborda:

  1. Compatibilidad de parámetros: comparación y alineación de configuraciones entre clústeres

  2. Compatibilidad de MaterializedMySQL: gestión de diferencias del motor tras la migración

  3. Verificación de compatibilidad SQL: prueba por lotes de consultas existentes en el nuevo clúster

  4. Análisis de cuellos de botella de rendimiento: localización de tablas y consultas que causan problemas de CPU o memoria

  5. Optimización de SQL: aplicación de correcciones específicas tras identificar la causa raíz

Análisis de compatibilidad

Compatibilidad de parámetros

Ejecute esta consulta tanto en su clúster autogestionado como en su clúster de ApsaraDB for ClickHouse para exportar todos los parámetros de configuración:

SELECT
    name,
    groupArrayDistinct(value) AS value
FROM clusterAllReplicas(`default`, system.settings)
GROUP BY name
ORDER BY name ASC

A continuación, utilice una herramienta de comparación (como la comparación de archivos integrada de VS Code) para identificar discrepancias. Para cualquier parámetro que difiera, actualice el clúster de ApsaraDB for ClickHouse para que coincida con el valor del clúster autogestionado.

Parámetros que afectan la compatibilidad:

  • compatibility

  • prefer_global_in_and_join

  • distributed_product_mode

Parámetros que afectan el rendimiento:

  • max_threads

  • max_bytes_to_merge_at_max_space_in_pool

  • prefer_global_in_and_join

image.png

Compatibilidad de MaterializedMySQL

Si su clúster autogestionado sincroniza datos desde MySQL mediante el motor MaterializedMySQL, tenga en cuenta cómo gestiona esto ApsaraDB for ClickHouse después de la migración.

La versión comunitaria del motor MaterializedMySQL ya no recibe mantenimiento. ApsaraDB for ClickHouse utiliza Data Transmission Service (DTS) para sincronizar los datos de MySQL en su lugar. DTS crea una tabla local ReplacingMergeTree en cada nodo del clúster y una tabla distribuida que enruta las escrituras a esos nodos, lo cual reemplaza por completo la tabla MaterializedMySQL.

Este cambio arquitectónico provoca dos problemas comunes:

Problema 1: Fallos en consultas IN y JOIN sobre la tabla distribuida

En el clúster autogestionado, MaterializedMySQL replica los datos directamente a cada shard. Tras la migración, los datos fluyen a través de una tabla distribuida, lo que modifica la forma en que se resuelven las subconsultas IN y JOIN. Para obtener detalles y soluciones, consulte What do I do if an error occurs when I use a subquery on a distributed table?.

Problema 2: Datos duplicados en los resultados de las consultas

La tabla ReplacingMergeTree fusiona filas duplicadas de forma asíncrona en segundo plano. Si las fusiones se retrasan, las consultas devuelven más duplicados de los que generaba el clúster autogestionado. Existen dos soluciones disponibles:

Solución 1: Forzar la deduplicación al momento de la consulta

Ejecute el siguiente comando para habilitar el modificador final de manera global. Esto deduplica los resultados durante cada consulta, pero incrementa el uso de CPU y memoria:

SET global final = 1;

Solución 2: Ajustar la frecuencia de fusión en la tabla destino

Incremente la frecuencia con la que ApsaraDB for ClickHouse fusiona partes en la tabla ReplacingMergeTree:

ALTER TABLE <AIM_TABLE>
    MODIFY SETTING
        min_age_to_force_merge_on_partition_only = 1,
        min_age_to_force_merge_seconds = 60;

Parámetro

Descripción

Valor predeterminado

min_age_to_force_merge_on_partition_only

Cuando se establece en 1, fuerza la fusión de datos dentro de las particiones

0 (deshabilitado)

min_age_to_force_merge_seconds

Intervalo de tiempo entre fusiones forzadas de partes (en segundos)

3600

Verificación de compatibilidad SQL

Utilice un script de Python para extraer sentencias SELECT del registro de consultas de su clúster autogestionado y reproducirlas en ApsaraDB for ClickHouse. Las consultas con errores indican problemas de compatibilidad SQL que deben resolverse antes de la migración.

Requisitos previos

Antes de empezar, asegúrese de contar con:

Verificar la compatibilidad SQL

  1. Instale la biblioteca cliente de Python para ClickHouse:

    pip3 install clickhouse_driver
  2. Cree un archivo de Python con el siguiente script. Reemplace los valores de marcador de posición con sus detalles de conexión reales:

    from clickhouse_driver import connect
    import datetime
    import logging
    # pip3 install clickhouse_driver is required.
    
    # Connection settings for the self-managed cluster
    host_old = 'HOST_OLD'          # VPC endpoint
    port_old = TCP_PORT_OLD        # TCP port
    user_old = 'USER_OLD'          # Username
    password_old = 'PASSWORD_OLD'  # Password
    
    # Connection settings for ApsaraDB for ClickHouse
    host_new = 'HOST_NEW'          # VPC endpoint
    port_new = TCP_PORT_NEW        # TCP port
    user_new = 'USER_NEW'          # Username
    password_new = 'PASSWORD_NEW'  # Password
    
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
    
    def create_connection(host, port, user, password):
        """Establish a connection to ClickHouse."""
        return connect(host=host, port=port, user=user, password=password)
    
    def get_query_hashes(cursor):
        """Query the query_hash list of the last two days."""
        get_queryhash_sql = '''
        select distinct normalized_query_hash from system.query_log
        where type='QueryFinish'
          and `is_initial_query`=1
          and `user` not in ('default', 'aurora')
          and lower(`query`) not like 'select 1%'
          and lower(`query`) not like 'select timezone()%'
          and lower(`query`) not like '%dms-websql%'
          and lower(`query`) like 'select%'
          and `event_time` > now() - INTERVAL 2 DAY;
        '''
        cursor.execute(get_queryhash_sql)
        return cursor.fetchall()
    
    def get_sql_info(cursor, queryhash):
        """Query the SQL information for the specified query_hash in the last 2 days (search range is 3 days)."""
        get_sqlinfo_sql = f'''
        select `event_time`, `query_duration_ms`, `read_rows`, `read_bytes`, `memory_usage`, `databases`, `tables`, `current_database`, `query`
        from system.query_log
        where `event_time` > now() - INTERVAL 3 DAY and `type`='QueryFinish' and `normalized_query_hash`='{queryhash}' limit 1
        '''
        cursor.execute(get_sqlinfo_sql)
        sql_info = cursor.fetchone()
        if sql_info:
            return [info.strftime('%Y-%m-%d %H:%M:%S') if isinstance(info, datetime.datetime) else info for info in sql_info[:-1]], sql_info[-1]
        return None
    
    def execute_sql_on_new_db(cursor, current_database, query_sql, execute_failed_sql):
        """Execute the SQL query on the new cluster and record any failures."""
        try:
            cursor.execute(f"USE {current_database};")
            cursor.execute(query_sql)
        except Exception as error:
            logging.error(f'query_sql execute in new db failed: {query_sql}')
            execute_failed_sql[query_sql] = error
    
    def main():
        conn_old = create_connection(host=host_old, port=port_old, user=user_old, password=password_old)
        conn_new = create_connection(host=host_new, port=port_new, user=user_new, password=password_new)
    
        cursor_old = conn_old.cursor()
        cursor_new = conn_new.cursor()
    
        # Extract query hashes and SQL from the self-managed cluster
        old_query_hashes = get_query_hashes(cursor_old)
        old_db_execute_dir = {}
        for queryhash in old_query_hashes:
            sql_info, query = get_sql_info(cursor_old, queryhash[0])
            if sql_info:
                old_db_execute_dir[query] = sql_info
    
        cursor_old.close()
        conn_old.close()
    
        # Replay queries against ApsaraDB for ClickHouse
        execute_failed_sql = {}
        keys_list = list(old_db_execute_dir.keys())
        for query_sql in old_db_execute_dir:
            position = keys_list.index(query_sql)
            current_database = old_db_execute_dir[query_sql][-1]
            logging.info(f"new db test the {position + 1}th/{len(old_db_execute_dir)}, running sql: {query_sql}\n")
            execute_sql_on_new_db(cursor_new, current_database, query_sql, execute_failed_sql)
    
        # Collect execution results from ApsaraDB for ClickHouse
        new_query_hashes = get_query_hashes(cursor_new)
        new_db_execute_dir = {}
        for queryhash in new_query_hashes:
            sql_info, query = get_sql_info(cursor_new, queryhash[0])
            if sql_info:
                new_db_execute_dir[query] = sql_info
    
        cursor_new.close()
        conn_new.close()
    
        # Log results
        for query_sql in new_db_execute_dir:
            if query_sql in old_db_execute_dir:
                logging.info(f'succeed sql: {query_sql}')
                logging.info(f'old sql info: {old_db_execute_dir[query_sql]}')
                logging.info(f'new sql info: {new_db_execute_dir[query_sql]}\n')
    
        for query_sql in execute_failed_sql:
            logging.error('\033[31m{}\033[0m'.format(f'failed sql: {query_sql}'))
            logging.error('\033[31m{}\033[0m'.format(f'failed error: {execute_failed_sql[query_sql]}\n'))
    
    if __name__ == "__main__":
        main()
  3. Ejecute el script y revise la salida. Cada consulta con errores se registra en rojo junto con su mensaje de error. Investigue y corrija los fallos según el error específico antes de proceder con la migración de producción.

Análisis y optimización del rendimiento

Después de cambiar el tráfico a ApsaraDB for ClickHouse, podría observar saturación de CPU o agotamiento de memoria. Los siguientes pasos le ayudarán a localizar sistemáticamente la causa raíz, desde la identificación de las tablas problemáticas hasta el aislamiento y análisis de consultas específicas para su corrección.

Si ya sabe qué tabla o consulta está causando el problema de rendimiento, vaya directamente a Step 3: Analyze SQL performance.

Paso 1: Identificar las tablas que causan el cuello de botella de rendimiento

Existen dos enfoques disponibles: gráficos de llama (flame graphs) y análisis de query_log. Los gráficos de llama requieren más configuración, pero ofrecen un desglose visual e intuitivo del uso de CPU. El análisis de query_log es más sencillo y no necesita herramientas externas, aunque requiere que usted mismo interprete los datos. Utilice ambos métodos conjuntamente para obtener una visión más completa.

Crear un gráfico de llama

  1. Conéctese a ApsaraDB for ClickHouse mediante clickhouse-client. Para instrucciones de conexión, consulte Connect to a ClickHouse cluster using the command-line interface.

  2. Exporte trace_log correspondiente a la ventana de tiempo en la que ocurrió el problema de rendimiento. Reemplace <IP>, <port> y los valores de event_time con sus valores reales:

    Parámetro

    Descripción

    <IP>

    Endpoint de Virtual Private Cloud (VPC) de ApsaraDB for ClickHouse

    <port>

    Puerto TCP de ApsaraDB for ClickHouse

    # trace_type = 'CPU' collects CPU call stacks.
    # trace_type = 'Real' collects wall-clock call stacks.
    /clickhouse/bin/clickhouse-client -h <IP> --port <port> -q \
      "SELECT arrayStringConcat(arrayReverse(arrayMap(x -> concat( addressToLine(x), '#', demangle(addressToSymbol(x)) ), trace)), ';') AS stack, count() AS samples \
      FROM system.trace_log \
      WHERE trace_type = 'CPU' \
        AND event_time >= '2025-01-08 19:31:00' \
        AND event_time < '2025-01-08 19:33:00' \
      GROUP BY trace \
      ORDER BY samples DESC \
      FORMAT TabSeparated \
      SETTINGS allow_introspection_functions=1" > cpu_trace_log.txt
  3. Instale clickhouse-flamegraph. Para instrucciones de descarga e instalación, consulte clickhouse-flamegraph.

  4. Genere el gráfico de llama:

    cat cpu_trace_log.txt | flamegraph.pl > cpu_trace_log.svg

    Abra el archivo SVG para identificar las funciones que consumen más CPU. Por ejemplo, si observa que ReplacingSortedMerge aparece de forma destacada, centre su investigación en las consultas dirigidas a tablas ReplacingMergeTree.

    image

Analizar query_log

Ejecute estas consultas simultáneamente en ambos clústeres para realizar una comparación de los N principales. Busque tablas donde el uso de CPU o memoria sea significativamente mayor en ApsaraDB for ClickHouse que en el clúster autogestionado; esas son sus candidatas.

Un alto uso de CPU o memoria en una tabla no siempre provoca la degradación general del clúster. Solo indica que es más probable que dicha tabla sea el origen del problema. Compare los resultados entre ambos clústeres para confirmarlo.

Localizar tablas con problemas de CPU

SELECT
    tables,
    first_value(query),
    count() AS cnt,
    groupArrayDistinct(normalizedQueryHash(query)) AS normalized_query_hash,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS sum_user_cpu,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS sum_system_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS avg_user_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS avg_system_cpu,
    sum(memory_usage) AS sum_memory_usage,
    avg(memory_usage) AS avg_memory_usage
FROM clusterAllReplicas(default, system.query_log)
WHERE (event_time > '2025-01-08 19:30:00') AND (event_time < '2025-01-08 20:30:00') AND (query_kind = 'Select')
GROUP BY tables
ORDER BY sum_user_cpu DESC
LIMIT 5

Localizar tablas con problemas de memoria

SELECT
    tables,
    first_value(query),
    count() AS cnt,
    groupArrayDistinct(normalizedQueryHash(query)) AS normalized_query_hash,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS sum_user_cpu,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS sum_system_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS avg_user_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS avg_system_cpu,
    sum(memory_usage) AS sum_memory_usage,
    avg(memory_usage) AS avg_memory_usage
FROM clusterAllReplicas(default, system.query_log)
WHERE (event_time > '2025-01-08 19:30:00') AND (event_time < '2025-01-08 20:30:00') AND (query_kind = 'Select')
GROUP BY tables
ORDER BY sum_memory_usage
LIMIT 5

Reemplace los valores de event_time con la ventana de tiempo en la que ocurrió el problema de rendimiento.

Paso 2: Identificar las consultas que causan el cuello de botella de rendimiento

Tras identificar la tabla problemática en el Paso 1, ejecute las siguientes consultas para aislar las sentencias SQL específicas que causan el problema. Reemplace <AIM_TABLE> con el nombre de la tabla obtenida en el Paso 1 y actualice event_time para que coincida con su ventana de tiempo objetivo.

Localizar consultas SQL que causan problemas de CPU

SELECT
    tables,
    first_value(query),
    count() AS cnt,
    normalizedQueryHash(query) AS normalized_query_hash,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS sum_user_cpu,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS sum_system_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS avg_user_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS avg_system_cpu,
    sum(memory_usage) AS sum_memory_usage,
    avg(memory_usage) AS avg_memory_usage
FROM clusterAllReplicas(default, system.query_log)
WHERE (event_time > '2025-01-08 19:30:00') AND (event_time < '2025-01-08 20:30:00') AND (query_kind = 'Select') AND has(tables, '<AIM_TABLE>')
GROUP BY
    tables,
    normalized_query_hash
ORDER BY sum_user_cpu DESC
LIMIT 5

Localizar consultas SQL que causan problemas de memoria

SELECT
    tables,
    first_value(query),
    count() AS cnt,
    normalizedQueryHash(query) AS normalized_query_hash,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS sum_user_cpu,
    sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS sum_system_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')]) AS avg_user_cpu,
    avg(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) AS avg_system_cpu,
    sum(memory_usage) AS sum_memory_usage,
    avg(memory_usage) AS avg_memory_usage
FROM clusterAllReplicas(default, system.query_log)
WHERE (event_time > '2025-01-08 19:30:00') AND (event_time < '2025-01-08 20:30:00') AND (query_kind = 'Select') AND has(tables, 'AIM_TABLE')
GROUP BY
    tables,
    normalized_query_hash
ORDER BY sum_memory_usage
LIMIT 5

Paso 3: Analizar el rendimiento de SQL

Utilice EXPLAIN PIPELINE y system.query_log para comprender por qué una consulta específica es lenta.

Analizar el plan de ejecución:

EXPLAIN PIPELINE <SQL with performance issues>

Para obtener detalles sobre cómo interpretar la salida de EXPLAIN, consulte la documentación de EXPLAIN de ClickHouse.

Analizar métricas de ejecución desde query_log:

SELECT
    hostname() AS host,
    *
FROM clusterAllReplicas(`default`, system.query_log)
WHERE
    event_time > '2025-01-18 00:00:00'
    AND event_time < '2025-01-18 03:00:00'
    AND initial_query_id = '<INITIAL_QUERY_ID>'
    AND type = 'QueryFinish'
ORDER BY query_start_time_microseconds

Reemplace <INITIAL_QUERY_ID> con el ID de la consulta que desea investigar y actualice event_time para cubrir la ventana de tiempo relevante.

Preste atención a estos campos en la salida:

Campo

Qué buscar

ProfileEvents

Contadores detallados de tiempo de CPU, E/S de disco, asignación de memoria y red. Permite comparar el uso de recursos entre nodos. Consulte la referencia de eventos del sistema.

Settings

Parámetros de configuración activos durante la consulta. Ayuda a identificar ajustes que podrían causar comportamientos inesperados. Consulte la referencia de configuración.

query_duration_ms

Tiempo total de ejecución de la consulta. Si una subconsulta es inusualmente lenta, utilice su query_id para encontrar sus detalles de ejecución en el registro. Para ajustar la verbosidad del registro, consulte Configurar parámetros de config.xml.

Si las métricas por sí solas no explican la degradación del rendimiento, obtenga la información de ejecución de la misma consulta desde el clúster autogestionado y compárela lado a lado con los resultados de ApsaraDB for ClickHouse. Las diferencias en los valores de ProfileEvents suelen revelar la causa raíz.

Paso 4: Optimizar SQL

Una vez identificada la consulta lenta, aplique las optimizaciones relevantes que se describen a continuación. La mayoría de las regresiones de rendimiento tras la migración tienen una causa raíz específica; enfóquese en la técnica que coincida con sus hallazgos en lugar de aplicar todas a la vez.

Optimización

Descripción

Optimización de índices

Identifique las columnas utilizadas frecuentemente en cláusulas WHERE y cree un índice para ellas.

Tipos de datos

Utilice los tipos de datos más adecuados para reducir la sobrecarga de almacenamiento y acelerar las lecturas.

**Evitar SELECT ***

Seleccione solo las columnas necesarias para reducir la E/S y la transferencia de red.

Usar clave primaria y columnas indexadas en WHERE

Filtre por columnas indexadas para habilitar la poda de datos.

Usar PREWHERE

Mueva las condiciones de filtro selectivas a PREWHERE para reducir el volumen de datos procesado por las etapas posteriores.

Usar GLOBAL JOIN para tablas distribuidas

Reemplace JOIN normal por GLOBAL JOIN para evitar la ineficiencia de la difusión por shard.

Ajustar max_threads

Incrementar max_threads utiliza más núcleos de CPU, pero establecerlo demasiado alto provoca contención de recursos. Consulte la referencia de configuración.

Crear vistas materializadas

Para consultas complejas ejecutadas frecuentemente con resultados estables, preagregue los datos con una vista materializada.

Ajustar la compresión de datos

ClickHouse comprime los datos de forma predeterminada. Ajustar el algoritmo y el nivel de compresión puede mejorar la eficiencia del almacenamiento y la velocidad de consulta.

Ampliar la caché de consultas

Para consultas estables y repetidas frecuentemente, incremente uncompressed_cache_size para reducir el recálculo. Para instrucciones de modificación de parámetros, consulte Configurar parámetros de config.xml.