Après la migration des données d'un cluster ClickHouse autogéré vers le cloud, vous pouvez rencontrer des problèmes de compatibilité et de performance. Pour garantir une migration fluide, nous vous recommandons d'effectuer cette analyse dans un environnement de test avant de basculer le trafic de production.
Informations contextuelles
Les clusters ClickHouse autogérés migrés vers ApsaraDB for ClickHouse peuvent rencontrer les problèmes suivants :
-
Problèmes de compatibilité des versions :
Problèmes de compatibilité du moteur MaterializedMySQL
Problèmes de compatibilité SQL
Épuisement des ressources CPU et insuffisance de mémoire après la migration vers ApsaraDB for ClickHouse
Pour résoudre ces problèmes, concentrez-vous sur la compatibilité et les performances lors de la migration.
Ce guide couvre les points suivants :
Compatibilité des paramètres — comparez les configurations entre les clusters et alignez-les
Compatibilité de MaterializedMySQL — gérez les différences de moteur après la migration
Vérification de la compatibilité SQL — testez par lots vos requêtes existantes sur le nouveau cluster
Analyse des goulots d'étranglement de performance — identifiez les tables et les requêtes à l'origine des problèmes de CPU ou de mémoire
Optimisation SQL — appliquez des correctifs ciblés après avoir identifié la cause racine
Analyse de compatibilité
Compatibilité des paramètres
Exécutez cette requête sur votre cluster autogéré et sur votre cluster ApsaraDB for ClickHouse afin d'exporter tous les paramètres de configuration :
SELECT
name,
groupArrayDistinct(value) AS value
FROM clusterAllReplicas(`default`, system.settings)
GROUP BY name
ORDER BY name ASC
Utilisez ensuite un outil de comparaison (tel que la fonction de comparaison de fichiers intégrée à VS Code) pour identifier les divergences. Pour tout paramètre différent, mettez à jour le cluster ApsaraDB for ClickHouse afin qu'il corresponde à la valeur du cluster autogéré.
Paramètres influant sur la compatibilité :
compatibilityprefer_global_in_and_joindistributed_product_mode
Paramètres influant sur les performances :
max_threadsmax_bytes_to_merge_at_max_space_in_poolprefer_global_in_and_join

Compatibilité de MaterializedMySQL
Si votre cluster autogéré synchronise les données depuis MySQL à l'aide du moteur MaterializedMySQL, tenez compte de la manière dont ApsaraDB for ClickHouse gère cette situation après la migration.
La version communautaire du moteur MaterializedMySQL n'est plus maintenue. ApsaraDB for ClickHouse utilise Data Transmission Service (DTS) pour synchroniser les données MySQL. DTS crée une table locale ReplacingMergeTree sur chaque nœud du cluster, ainsi qu'une table distribuée qui achemine les écritures vers ces nœuds, remplaçant ainsi entièrement la table MaterializedMySQL.
Cette modification architecturale entraîne deux problèmes courants :
Problème 1 : Les requêtes IN et JOIN échouent sur la table distribuée
Dans le cluster autogéré, MaterializedMySQL réplique les données directement sur chaque shard. Après la migration, les données transitent par une table distribuée, ce qui modifie la résolution des sous-requêtes IN et JOIN. Pour plus de détails et de solutions, consultez la section Que faire en cas d'erreur lors de l'utilisation d'une sous-requête sur une table distribuée ?.
Problème 2 : Données en double dans les résultats de requête
La table ReplacingMergeTree fusionne les lignes en double de manière asynchrone en arrière-plan. Si les fusions prennent du retard, les requêtes renvoient plus de doublons que le cluster autogéré. Deux solutions sont disponibles :
Solution 1 : Forcer la déduplication au moment de la requête
Exécutez la commande suivante pour activer globalement le modificateur final. Cela déduplique les résultats lors de chaque requête, mais augmente l'utilisation du CPU et de la mémoire :
SET global final = 1;
Solution 2 : Ajuster la fréquence de fusion sur la table cible
Augmentez la fréquence à laquelle ApsaraDB for ClickHouse fusionne les parties dans la table ReplacingMergeTree :
ALTER TABLE <AIM_TABLE>
MODIFY SETTING
min_age_to_force_merge_on_partition_only = 1,
min_age_to_force_merge_seconds = 60;
| Paramètre | Description | Valeur par défaut |
|---|---|---|
min_age_to_force_merge_on_partition_only |
Lorsqu'il est défini sur 1, force la fusion des données au sein des partitions |
0 (désactivé) |
min_age_to_force_merge_seconds |
Intervalle de temps entre les fusions forcées de parties (en secondes) | 3600 |
Vérification de la compatibilité SQL
Utilisez un script Python pour extraire les instructions SELECT du journal des requêtes de votre cluster autogéré et rejouez-les sur ApsaraDB for ClickHouse. Les requêtes ayant échoué indiquent des problèmes de compatibilité SQL à résoudre avant la migration.
Prérequis
Avant de commencer, assurez-vous que :
Un environnement Python 3 est disponible sur le serveur de vérification. Une instance Alibaba Cloud Elastic Compute Service (ECS) exécutant Linux inclut déjà Python 3. Si vous n'utilisez pas ECS, installez Python 3 depuis le site officiel de Python.
La connectivité réseau est établie entre le serveur de vérification et les deux clusters. Pour le dépannage de la connectivité, consultez la section Comment résoudre les problèmes de connectivité réseau entre le cluster cible et la source de données ?
Vérifier la compatibilité SQL
-
Installez la bibliothèque cliente Python pour ClickHouse :
pip3 install clickhouse_driver -
Créez un fichier Python contenant le script suivant. Remplacez les valeurs d'espace réservé par vos détails de connexion réels :
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() Exécutez le script et examinez la sortie. Chaque requête ayant échoué est enregistrée en rouge avec son message d'erreur. Analysez et corrigez les échecs en fonction de l'erreur spécifique avant de procéder à la migration de production.
Analyse et optimisation des performances
Après le basculement du trafic vers ApsaraDB for ClickHouse, vous pouvez constater une saturation du CPU ou un épuisement de la mémoire. Les étapes ci-dessous vous aident à localiser systématiquement la cause racine, depuis l'identification des tables problématiques jusqu'à l'isolement des requêtes spécifiques, en passant par leur analyse et leur correction.
Si vous savez déjà quelle table ou quelle requête est à l'origine du problème de performance, passez directement à l'Étape 3 : Analyser les performances SQL.
Étape 1 : Identifier les tables à l'origine du goulot d'étranglement des performances
Deux approches sont possibles : les graphiques en flammes (flame graphs) et l'analyse de query_log. Les graphiques en flammes nécessitent plus de configuration, mais offrent une ventilation visuelle et intuitive de l'utilisation du CPU. L'analyse de query_log est plus simple et ne nécessite aucun outil externe, mais vous devez interpréter les données vous-même. Utilisez les deux méthodes conjointement pour obtenir une image aussi complète que possible.
Créer un graphique en flammes
Connectez-vous à ApsaraDB for ClickHouse en utilisant
clickhouse-client. Pour les instructions de connexion, consultez la section Se connecter à un cluster ClickHouse via l'interface de ligne de commande.-
Exportez le
trace_logpour la fenêtre temporelle durant laquelle le problème de performance s'est produit. Remplacez<IP>,<port>et les valeurs deevent_timepar vos valeurs réelles :Paramètre Description <IP>Endpoint Virtual Private Cloud (VPC) d'ApsaraDB for ClickHouse <port>Port TCP d'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 Installez
clickhouse-flamegraph. Pour les instructions de téléchargement et d'installation, consultez clickhouse-flamegraph.-
Générez le graphique en flammes :
cat cpu_trace_log.txt | flamegraph.pl > cpu_trace_log.svgOuvrez le fichier SVG pour identifier les fonctions consommant le plus de CPU. Par exemple, si vous voyez
ReplacingSortedMergeapparaître de manière prédominante, concentrez votre enquête sur les requêtes adressées aux tablesReplacingMergeTree.
Analyser query_log
Exécutez ces requêtes simultanément sur les deux clusters pour effectuer une comparaison Top N. Recherchez les tables dont l'utilisation du CPU ou de la mémoire est significativement plus élevée sur ApsaraDB for ClickHouse que sur le cluster autogéré ; ce sont vos candidates.
Une utilisation élevée du CPU ou de la mémoire sur une table n'entraîne pas toujours une dégradation globale du cluster. Cela suggère simplement que cette table est plus susceptible d'être à l'origine du problème. Comparez les résultats entre les deux clusters pour confirmer.
Localiser les tables présentant des problèmes 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
Localiser les tables présentant des problèmes de mémoire
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
Remplacez les valeurs de event_time par la fenêtre temporelle durant laquelle le problème de performance s'est produit.
Étape 2 : Identifier les requêtes à l'origine du goulot d'étranglement des performances
Après avoir identifié la table problématique à l'étape 1, exécutez les requêtes suivantes pour isoler les instructions SQL spécifiques à l'origine du problème. Remplacez <AIM_TABLE> par le nom de la table issu de l'étape 1 et mettez à jour event_time pour correspondre à votre fenêtre temporelle cible.
Localiser les requêtes SQL à l'origine des problèmes 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
Localiser les requêtes SQL à l'origine des problèmes de mémoire
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
Étape 3 : Analyser les performances SQL
Utilisez EXPLAIN PIPELINE et system.query_log pour comprendre pourquoi une requête spécifique est lente.
Analyser le plan d'exécution :
EXPLAIN PIPELINE <SQL with performance issues>
Pour plus de détails sur la lecture de la sortie EXPLAIN, consultez la documentation ClickHouse EXPLAIN.
Analyser les métriques d'exécution depuis 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
Remplacez <INITIAL_QUERY_ID> par l'ID de la requête que vous souhaitez examiner et mettez à jour event_time pour couvrir la fenêtre temporelle pertinente.
Concentrez-vous sur ces champs dans la sortie :
| Champ | Éléments à rechercher |
|---|---|
ProfileEvents |
Compteurs détaillés pour le temps CPU, les E/S disque, l'allocation mémoire et le réseau. Permet de comparer l'utilisation des ressources entre les nœuds. Consultez la référence des événements système. |
Settings |
Paramètres de configuration actifs pendant la requête. Aide à identifier les paramètres pouvant causer un comportement inattendu. Consultez la référence des paramètres. |
query_duration_ms |
Durée totale d'exécution de la requête. Si une sous-requête est anormalement lente, utilisez son query_id pour trouver ses détails d'exécution dans le journal. Pour ajuster la verbosité du journal, consultez Configurer les paramètres du fichier config.xml. |
Si les métriques seules n'expliquent pas la dégradation des performances, extrayez les informations d'exécution de la même requête depuis le cluster autogéré et comparez-les côte à côte avec les résultats d'ApsaraDB for ClickHouse. Les différences dans les valeurs de ProfileEvents révèlent souvent la cause racine.
Étape 4 : Optimiser le SQL
Après avoir identifié la requête lente, appliquez les optimisations pertinentes ci-dessous. La plupart des régressions de performances après une migration ont une cause racine spécifique ; concentrez-vous sur la technique correspondant à vos constatations plutôt que d'appliquer toutes les optimisations en même temps.
| Optimisation | Description |
|---|---|
| Optimisation des index | Identifiez les colonnes fréquemment utilisées dans les clauses WHERE et créez un index pour celles-ci. |
| Types de données | Utilisez les types de données les plus appropriés pour réduire la surcharge de stockage et accélérer les lectures. |
**Éviter SELECT *** |
Sélectionnez uniquement les colonnes nécessaires pour réduire les E/S et le transfert réseau. |
| Utiliser la clé primaire et les colonnes indexées dans WHERE | Filtrez sur les colonnes indexées pour permettre l'élagage (pruning). |
| Utiliser PREWHERE | Déplacez les conditions de filtre sélectives vers PREWHERE pour réduire le volume de données traité par les étapes suivantes. |
| Utiliser GLOBAL JOIN pour les tables distribuées | Remplacez JOIN standard par GLOBAL JOIN pour éviter l'inefficacité de la diffusion par shard. |
Ajuster max_threads |
L'augmentation de max_threads utilise plus de cœurs CPU, mais une valeur trop élevée provoque une contention des ressources. Consultez la référence des paramètres. |
| Créer des vues matérialisées | Pour les requêtes complexes fréquemment exécutées avec des résultats stables, pré-agrégez les données à l'aide d'une vue matérialisée. |
| Ajuster la compression des données | ClickHouse compresse les données par défaut. L'ajustement de l'algorithme et du niveau de compression peut améliorer l'efficacité du stockage et la vitesse des requêtes. |
| Étendre le cache de requêtes | Pour les requêtes stables et fréquemment répétées, augmentez uncompressed_cache_size pour réduire le recalcul. Pour les instructions de modification des paramètres, consultez Configurer les paramètres du fichier config.xml. |