Quelque part dans votre entreprise, un analyste bien intentionné vient de rafraîchir un tableau de bord. Désormais le paiement est lent, l’API fait des timeouts et le canal on-call ressemble à une séance de thérapie de groupe.
Ce n’est pas un problème de « mauvaise requête ». C’est un problème d’architecture : l’OLTP et l’analytique sont des animaux différents, et les mettre dans la même cage se termine comme prévu. La solution est une séparation propre — MySQL gère les transactions, ClickHouse gère l’analytique, et vous empêchez la curiosité de faire un DDoS sur votre chemin de revenu.
Le vrai problème : l’OLTP et l’analytique se battent au niveau du stockage
L’OLTP concerne la latence, la correction et la concurrence prévisible. Vous optimisez pour des milliers de petites lectures/écritures par seconde, des index serrés et des ensembles de travail chauds qui tiennent en mémoire. Le coût d’une seule requête lente est payé immédiatement — dans l’expérience client, les timeouts et les retry qui amplifient la charge.
L’analytique concerne le débit, les lectures larges et l’agrégation. Vous optimisez pour lire beaucoup de données, bien les compresser et utiliser l’exécution vectorisée pour convertir le CPU en réponses. Les requêtes analytiques sont souvent « embarrassingly parallel » et tolèrent quelques secondes de plus — jusqu’à ce qu’on les pointe sur votre base transactionnelle et qu’elles deviennent un déni de service avec un tableau croisé dynamique en prime.
Le point clé : l’OLTP et l’analytique se disputent les mêmes ressources finies — cycles CPU, I/O disque, cache de pages, buffer pools, verrous/loquets et maintenance en arrière-plan (flush, checkpoints, merges). Même si vous ajoutez une réplica en lecture, vous partagez souvent la même douleur fondamentale : latence de réplication, saturation d’I/O et performance incohérente causée par des scans imprévisibles.
Où le couteau s’enfonce : contention des ressources dans MySQL
- Pollution du buffer pool : une grosse requête de reporting lit une tranche historique froide, fait sortir des pages chaudes et soudain votre charge primaire devient liée au disque.
- Pression background d’InnoDB : scans longs + tables temporaires + tris peuvent augmenter les pages sales et la pression de redo. Les “flush storms” ne sont pas polis.
- Verrous et verrous de métadonnées : certains schémas de reporting déclenchent des interactions désagréables (pensez « ALTER TABLE en heures de bureau » rencontrant « SELECT … » qui tient un MDL).
- Latence de réplication : les lectures lourdes sur une réplica volent l’I/O et le CPU au thread SQL qui applique les changements.
Où ClickHouse s’insère
ClickHouse est conçu pour l’analytique : stockage en colonnes, compression, exécution vectorisée et parallélisme agressif. Il attend que vous lisiez beaucoup de lignes, mais seulement quelques colonnes, et il vous récompense pour le partitionnement et les clés triées bien pensées.
La discipline est simple : traitez MySQL comme le système d’enregistrement des transactions. Traitez ClickHouse comme le système de vérité pour l’analytique — vérité signifiant « dérivé de l’enregistrement, reproductible et interrogeable à l’échelle ».
Idée paraphrasée de Werner Vogels : « Tout échoue ; concevez pour l’échec. » Ça s’applique aussi aux données : concevez pour des modes de défaillance comme les tempêtes de requêtes, la latence et les backfills.
MySQL vs ClickHouse : les vraies différences qui comptent en production
Disposition du stockage : ligne vs colonne
MySQL/InnoDB est orienté ligne. Idéal pour récupérer une ligne par clé primaire, mettre à jour quelques colonnes, maintenir des index secondaires et appliquer des contraintes. Mais scanner un milliard de lignes pour calculer des agrégats signifie faire traverser des lignes entières au moteur, toucher des pages dont vous n’aviez pas besoin et brûler le cache.
ClickHouse est orienté colonne. Il lit uniquement les colonnes demandées, les compresse bien (souvent de façon spectaculaire) et les traite en vecteurs. Vous payez d’avance avec des contraintes de modélisation différentes — dénormalisation, clés d’ordre réfléchies et un processus de merge à respecter.
Modèle de concurrence : transactions vs parallélisme analytique
MySQL gère bien de nombreuses transactions courtes concurrentes — jusqu’aux limites de votre schéma, de vos index et de votre matériel. ClickHouse gère aussi de nombreuses lectures concurrentes, mais la magie vient de la parallélisation efficace des grosses lectures et agrégations. Si vous pointez un outil BI sur ClickHouse sans limites de concurrence, il essaiera de mettre votre CPU en feu. Il faut et vous devez le gouverner.
Consistance et exactitude
MySQL est ACID (avec les réserves usuelles, mais oui, c’est votre ancre transactionnelle). ClickHouse est typiquement éventuellement consistant pour les données ingérées et orienté append. Vous pouvez modéliser des mises à jour/suppressions, mais vous le faites selon les règles de ClickHouse (ReplacingMergeTree, CollapsingMergeTree, colonnes de version ou suppressions asynchrones). C’est acceptable : l’analytique veut habituellement la vérité actuelle et une série temporelle des changements, pas une sémantique transactionnelle par ligne.
Indexation et patterns de requêtes
Les index MySQL sont des B-trees adaptés aux recherches ponctuelles et aux scans de plage. ClickHouse utilise l’ordre de la clé primaire et des index épars, plus des index de saut de données (comme les filtres Bloom) quand c’est utile. La meilleure requête ClickHouse est celle qui peut sauter de gros morceaux de données parce que votre partitionnement et votre ordre correspondent aux modèles d’accès.
Posture opérationnelle
Les opérations MySQL tournent autour de la santé de la réplication, des sauvegardes, des migrations de schéma et de la stabilité des requêtes. Les opérations ClickHouse tournent autour des merges, de l’utilisation du disque, du nombre de parts, des TTL et de la gouvernance des requêtes. En d’autres termes : vous échangez un ensemble de dragons contre un autre. L’affaire en vaut la peine parce que vous empêchez l’analytique de saboter votre flux de paiement.
Blague n°1 : Un rafraîchissement de tableau de bord est le seul type de « engagement utilisateur » qui peut augmenter à la fois le taux d’erreur et le churn.
Faits et contexte historique (utile, pas du trivia)
- InnoDB est devenu le moteur par défaut dans MySQL 5.5 (ère 2010), consolidant le comportement OLTP en row-store pour la plupart des déploiements.
- ClickHouse a démarré chez Yandex pour alimenter des charges analytiques à grande échelle ; il est né dans un monde où scanner rapidement de grosses données était le métier.
- Les stores en colonnes ont pris de l’ampleur parce que le CPU est devenu plus rapide que les disques, et la compression + l’exécution vectorisée permettent de dépenser du CPU pour éviter l’I/O.
- La « pollution » du buffer pool InnoDB est un mode de défaillance classique lorsque des scans longs balayent les pages chaudes ; le moteur n’est pas « cassé », il fait ce que vous lui avez demandé.
- L’analytique basée sur la réplication existe depuis des décennies : les gens expédient les changements OLTP vers des entrepôts de données depuis avant que « data lake » ne devienne un mot-clé de CV.
- Le query cache de MySQL a été supprimé dans MySQL 8.0 car il créait de la contention et ne montait pas bien ; le caching n’est pas gratuit, et les locks globaux sont coûteux.
- La famille MergeTree de ClickHouse stocke les données en parts et les fusionne en arrière-plan — excellent pour les écritures et la compression, mais cela crée des signaux opérationnels (nombre de parts, backlog de merges) à surveiller.
- Le modèle en étoile et la modélisation dimensionnelle préexistent aux outils modernes ; ClickHouse pousse souvent les équipes vers des formes dénormalisées et favorables aux requêtes car les joins à grande échelle ont de vrais coûts.
Le plan de séparation propre : des modèles qui n’embrasent pas la prod
Principe 1 : MySQL sert les utilisateurs, pas la curiosité
Faites-en une politique : MySQL de production n’est pas une base de reporting. Pas « habituellement ». Pas « sauf pour une requête rapide ». Jamais. Si quelqu’un a besoin d’un one-off, exécutez-le sur ClickHouse ou dans un environnement snapshot contrôlé.
Vous aurez des objections. C’est normal. L’astuce consiste à remplacer le « non » par « voici la manière sûre ». Fournissez la voie sûre : accès ClickHouse, jeux de données sélectionnés et un workflow qui n’implique pas de supplier l’on-call pour autoriser un JOIN sur une année de commandes.
Principe 2 : Choisissez une stratégie de transfert de données qui correspond à votre tolérance aux pannes
Il y a trois façons courantes d’alimenter ClickHouse depuis MySQL. Chacune a des angles vifs.
Option A : ETL par lots (dump et chargement)
Vous extrayez des snapshots horaires/quotidiens (mysqldump, exports CSV, jobs Spark), chargez dans ClickHouse et acceptez une certaine staleness. C’est le plus simple opérationnellement mais pénible si vous avez besoin de métriques quasi-temps réel, et les backfills peuvent être lourds.
Option B : Ingestion pilotée par réplication (CDC)
Capturez les changements depuis le binlog MySQL et streammez-les vers ClickHouse. Cela vous donne de l’analytique quasi temps réel tout en protégeant MySQL de la charge des requêtes. Mais cela introduit la santé du pipeline comme préoccupation de production : lag, dérive de schéma et retraitement deviennent vos nouveaux hobbies.
Option C : Double écriture (l’application écrit dans les deux)
Ne le faites pas. Ou, si vous devez absolument le faire, faites-le seulement avec une idempotence robuste, une livraison asynchrone et un job de réconciliation qui suppose que la double écriture vous mentira parfois.
Le plan de séparation propre signifie généralement CDC plus modèles de données sélectionnés dans ClickHouse. L’ETL par lots est acceptable quand la staleness est tolérable. La double-écriture est un piège à moins d’aimer expliquer des divergences de données lors des postmortems d’incident.
Principe 3 : Modelez ClickHouse pour vos questions, pas pour votre schéma
La plupart des schémas OLTP sont normalisés. L’analytique veut moins de joins, des clés stables et des tables orientées événement. Votre travail consiste à construire une représentation analytique facile à interroger et difficile à mal utiliser.
- Privilégiez les tables d’événements : orders_events, sessions, payments, shipments, support_tickets. Append d’événements. Dérivez des faits.
- Partitionnez par temps : généralement par jour ou par mois. Cela vous donne un pruning prévisible et des TTL gérables.
- Order by selon les dimensions de requête : placez les clés de filtrage/group-by les plus fréquentes tôt dans ORDER BY (après la clé temporelle si vous filtrez toujours par temps).
- Pré-aggrégez quand c’est stable : les vues matérialisées peuvent produire des rollups pour que les tableaux de bord n’épluchent pas toujours les données brutes.
Principe 4 : La gouvernance bat les héros
ClickHouse peut répondre assez vite pour que les gens posent de pires questions plus souvent. Vous avez besoin de garde-fous :
- Séparez les utilisateurs et quotas : les utilisateurs BI ont des timeouts et une mémoire max. L’ETL a un profil différent.
- Fixez max threads et la concurrence : évitez un « thundering herd » de requêtes parallèles.
- Utilisez des jeux de données « gold » dédiés : vues ou tables stables sur lesquelles les tableaux de bord s’appuient, versionnées si nécessaire.
- Définissez des SLO : le SLO de latence MySQL est sacré. Le SLO de fraîcheur ClickHouse est négociable mais mesurable.
Tâches pratiques (commandes, sorties, décisions)
Voici les mouvements que vous faites réellement à 02:13. Chaque tâche inclut une commande, une sortie d’exemple, ce que cela signifie et la décision à en tirer.
Task 1: Confirm MySQL is suffering from analytic scans (top digests)
cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN ? AND ? GROUP BY customer_id
COUNT_STAR: 9421
total_s: 18873.214
*************************** 2. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE created_at > ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 110233
total_s: 8211.532
Ce que cela signifie : Votre pire temps provient d’un agrégat classique de reporting sur une plage de dates. Ce n’est pas « une requête lente », c’est une douleur répétée.
Décision : Bloquez ou redirigez le pattern de requête analytique. Ne transformez pas MySQL en moteur OLAP. Commencez par déplacer ce tableau de bord vers ClickHouse ou une table de rollup.
Task 2: Check current MySQL thread activity (is it a dogpile?)
cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head
Id User Host db Command Time State Info
31 app 10.0.2.14:51234 prod Query 2 Sending data SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
44 app 10.0.2.14:51239 prod Query 2 Sending data SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
57 app 10.0.2.14:51241 prod Query 1 Sending data SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
Ce que cela signifie : De nombreuses requêtes identiques s’exécutent en parallèle. C’est un tableau de bord ou une flotte de workers qui font le même travail coûteux.
Décision : Bridez au niveau applicatif/BI et introduisez du caching ou du pré-agrégat dans ClickHouse. Envisagez aussi des limites de connexion MySQL et des contrôles de ressources par utilisateur.
Task 3: Validate InnoDB buffer pool pressure (hot pages getting evicted)
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
Variable_name Value
Innodb_buffer_pool_read_requests 987654321
Innodb_buffer_pool_reads 12345678
Ce que cela signifie : Un nombre élevé de lectures physiques (Innodb_buffer_pool_reads) par rapport aux lectures logiques suggère que votre working set ne tient pas en mémoire — souvent à cause de gros scans.
Décision : Arrêtez les scans (déplacez l’analytique), puis seulement envisagez d’augmenter le buffer pool ou d’ajuster la charge. Le matériel ne peut pas battre un mauvais mix de charge indéfiniment.
Task 4: Catch disk I/O saturation on the MySQL host
cr0x@server:~$ iostat -xz 1 3
Linux 6.2.0 (mysql01) 12/30/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.12 0.00 6.44 31.55 0.00 43.89
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s w_await aqu-sz %util
nvme0n1 820.0 64200.0 0.0 0.0 12.4 78.3 410.0 18800.0 9.8 18.2 98.7
Ce que cela signifie : %util proche de 100% et un iowait élevé signifient que le disque est le goulot d’étranglement. Les scans analytiques adorent ce résultat.
Décision : Immédiat : réduisez la concurrence des requêtes, tuez les plus gros coupables, déplacez l’analytique vers ClickHouse. À long terme : séparez stockage et charges ; ne comptez pas sur un « NVMe plus rapide » comme stratégie.
Task 5: Identify MySQL replication lag (your “read replica” isn’t helping)
cr0x@server:~$ mysql -h mysql-replica01 -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_SQL_Running|Slave_IO_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 487
Ce que cela signifie : La réplica a ~8 minutes de retard. Les tableaux de bord qui la lisent mentent. Pire : en cas de bascule, vous pourriez perdre des transactions récentes.
Décision : N’utilisez pas la réplica comme puits analytique. Utilisez CDC vers ClickHouse, ou au minimum une réplica dédiée avec accès de requête contrôlé et ressources garanties.
Task 6: Show the actual expensive query plan (stop guessing)
cr0x@server:~$ mysql -e "EXPLAIN SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: range
possible_keys: idx_created_at
key: idx_created_at
rows: 98234123
Extra: Using where; Using temporary; Using filesort
Ce que cela signifie : Même avec un index, vous scannez ~98M de lignes et utilisez temporary/filesort. Ce n’est pas une requête OLTP ; c’est un travail OLAP.
Décision : Déplacez-la. Si vous devez conserver certains agrégats dans MySQL, utilisez des tables de synthèse mises à jour de manière incrémentale, pas des GROUP BY ad hoc sur des faits bruts.
Task 7: Confirm ClickHouse health basics (are merges or disk the issue?)
cr0x@server:~$ clickhouse-client -q "SELECT hostName(), uptime()"
ch01
345678
Ce que cela signifie : Vous pouvez vous connecter et le serveur est en ligne depuis suffisamment longtemps pour être utile.
Décision : Poursuivez avec des vérifications plus approfondies : parts/merges, charge de requêtes et disque.
Task 8: Check ClickHouse active queries and their resource usage
cr0x@server:~$ clickhouse-client -q "SELECT user, query_id, elapsed, read_rows, formatReadableSize(memory_usage) AS mem, left(query, 80) AS q FROM system.processes ORDER BY memory_usage DESC LIMIT 5 FORMAT TabSeparated"
bi_user 0f2a... 12.4 184001234 6.31 GiB SELECT customer_id, sum(total) FROM orders_events WHERE event_date >= toDate('2025-12-01')
etl 9b10... 3.1 0 512.00 MiB INSERT INTO orders_events FORMAT JSONEachRow
Ce que cela signifie : BI consomme de la mémoire. C’est acceptable si c’est budgété. C’est un problème si cela prive les merges ou déclenche un OOM.
Décision : Fixez per-user max_memory_usage, max_threads et éventuellement max_concurrent_queries. Gardez l’ETL fiable.
Task 9: Check ClickHouse merges backlog (parts growing like weeds)
cr0x@server:~$ clickhouse-client -q "SELECT database, table, sum(parts) AS parts, formatReadableSize(sum(bytes_on_disk)) AS disk FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(parts) DESC LIMIT 10 FORMAT TabSeparated"
analytics orders_events 1842 1.27 TiB
analytics sessions 936 640.12 GiB
Ce que cela signifie : Des milliers de parts peuvent indiquer une fragmentation d’insert ou des merges en retard. Les performances des requêtes se dégraderont, et le démarrage/les métadonnées deviennent plus lourds.
Décision : Ajustez le batching des inserts, paramétrez les merges prudemment et envisagez la stratégie de partitionnement. Si le nombre de parts continue de grimper, traitez cela comme un incident sur le long terme.
Task 10: Validate partition pruning (if it scans everything, you modeled it wrong)
cr0x@server:~$ clickhouse-client -q "EXPLAIN indexes=1 SELECT customer_id, sum(total) FROM analytics.orders_events WHERE event_date BETWEEN toDate('2025-12-01') AND toDate('2025-12-30') GROUP BY customer_id"
Expression ((Projection + Before ORDER BY))
Aggregating
Filter (WHERE)
ReadFromMergeTree (analytics.orders_events)
Indexes:
MinMax
Keys: event_date
Condition: (event_date in [2025-12-01, 2025-12-30])
Parts: 30/365
Granules: 8123/104220
Ce que cela signifie : Il lit 30/365 parts grâce au filtre de date. Voilà à quoi ressemble « fonctionne comme prévu ».
Décision : Si le nombre de parts lues est proche du total, changez le partitionnement et/ou exigez des filtres temporels dans les tableaux de bord.
Task 11: Monitor ClickHouse disk usage and predict capacity trouble
cr0x@server:~$ df -h /var/lib/clickhouse
Filesystem Size Used Avail Use% Mounted on
/dev/nvme1n1 3.5T 3.1T 330G 91% /var/lib/clickhouse
Ce que cela signifie : 91% utilisé. Vous n’êtes qu’à un backfill d’une journée noire, et les merges ont besoin d’espace libre.
Décision : Arrêtez les backfills non essentiels, augmentez le stockage, appliquez des TTL et optimisez le modèle. ClickHouse sous pression disque devient imprévisible et risqué.
Task 12: Verify CDC pipeline lag at the consumer (is analytics stale?)
cr0x@server:~$ clickhouse-client -q "SELECT max(ingested_at) AS last_ingest, now() AS now, dateDiff('second', max(ingested_at), now()) AS lag_s FROM analytics.orders_events"
2025-12-30 19:03:12 2025-12-30 19:03:29 17
Ce que cela signifie : ~17 secondes de lag. C’est sain pour de l’analytique « quasi temps réel ».
Décision : Si le lag augmente, mettez en pause les requêtes lourdes, vérifiez le débit du pipeline et décidez de dégrader les tableaux de bord ou de risquer l’OLTP.
Task 13: Check MySQL binary log format for CDC correctness
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'binlog_format';"
Variable_name Value
binlog_format ROW
Ce que cela signifie : Le format ROW est typiquement ce que veulent les outils CDC pour la correction. STATEMENT peut être ambigu pour les requêtes non déterministes.
Décision : Si vous n’êtes pas en ROW, planifiez une fenêtre de changement. La correction du CDC n’est pas quelque chose sur lequel on « espère ».
Task 14: Confirm MySQL has sane slow query logging (so you can prove causality)
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
Variable_name Value
slow_query_log ON
slow_query_log_file /var/log/mysql/mysql-slow.log
Variable_name Value
long_query_time 0.500000
Ce que cela signifie : Vous capturez les requêtes plus lentes que 500ms. C’est agressif, mais utile pendant une période bruyante.
Décision : Pendant les incidents, baissez temporairement long_query_time et échantillonnez. Ensuite, remettez un seuil stable et utilisez des résumés digest.
Task 15: Verify ClickHouse user limits (prevent a BI “parallelism party”)
cr0x@server:~$ clickhouse-client -q "SHOW CREATE USER bi_user"
CREATE USER bi_user IDENTIFIED WITH sha256_password SETTINGS max_memory_usage = 4000000000, max_threads = 8, max_execution_time = 60, max_concurrent_queries = 5
Ce que cela signifie : BI est cloisonné : 4GB mémoire, 8 threads, 60s d’exécution, 5 requêtes concurrentes. C’est la différence entre un tableau de bord et un test de stress.
Décision : Si vous ne pouvez pas poser de limites pour des raisons « business », vous ne faites pas de l’analytique, vous jouez à la roulette.
Mode opératoire de diagnostic rapide
Ceci est l’ordre qui trouve rapidement le goulot d’étranglement, sans transformer l’incident en débat philosophique.
Premièrement : MySQL est-il surchargé par des lectures, écritures, verrous ou I/O ?
- Top query digests (digests performance_schema ou résumés du slow log) : identifiez les familles de requêtes qui consomment du temps.
- États des threads (SHOW PROCESSLIST) : « Sending data » suggère scan/agrégation ; « Locked » suggère contention ; « Waiting for table metadata lock » suggère collision DDL.
- I/O disque (iostat) : si iowait est élevé et %util du disque est élevé, arrêtez les scans avant de tuner quoi que ce soit d’autre.
Deuxièmement : la « solution » (réplica) n’empire-t-elle pas les choses ?
- Latence de réplication (SHOW SLAVE STATUS) : si le lag est de plusieurs minutes, les utilisateurs analytiques prennent des décisions sur des données obsolètes et vous en tiennent responsable.
- Contention des ressources sur la réplica : les requêtes lourdes peuvent priver le thread SQL et augmenter encore le lag.
Troisièmement : si ClickHouse existe, est-il sain et gouverné ?
- system.processes : identifiez les requêtes BI hors de contrôle et les gros consommateurs de mémoire.
- Parts et merges (system.parts) : trop de parts signifie problème de forme d’ingestion ou backlog de merges.
- Headroom disque (df) : les merges et TTL ont besoin d’espace ; 90% plein est une dette opérationnelle avec intérêt.
Quatrièmement : la fraîcheur des données est-elle la vraie plainte ?
- Lag CDC (max ingested_at) : quantifiez l’obsolescence.
- Communiquez une solution de repli : si la fraîcheur se dégrade, dégradez les tableaux de bord — pas le checkout.
Trois mini-récits d’entreprise venus des tranchées
Incident causé par une mauvaise hypothèse : « Les réplica de lecture servent au reporting »
Une entreprise d’abonnement de taille moyenne avait un cluster MySQL primaire et deux réplica de lecture. Leur outil BI pointait une réplica parce que « les lectures n’affectent pas les écritures ». Cette phrase a causé plus d’incidents que le café n’a empêché.
Pendant la clôture du mois, la finance a lancé une série de rapports de cohortes et de revenus. Le disque de la réplica a atteint la saturation : scans lourds plus tables temporaires. Le lag de réplication est passé de secondes à dizaines de minutes. Personne n’a remarqué au début parce que le trafic applicatif allait bien ; le primaire n’était pas directement impacté.
Puis quelqu’un a fait la seconde hypothèse : « Si le primaire échoue, on peut basculer vers une réplica. » Juste au moment où le lag était le pire, le primaire a eu un incident d’hôte non lié et est devenu sain. L’automatisation a tenté de promouvoir la « meilleure » réplica — sauf que la « meilleure » avait 20 minutes de retard.
Ils n’ont pas perdu toute la base. Ils ont perdu suffisamment de transactions récentes pour créer un cauchemar support client : paiements qui « ont réussi » côté externe mais qui n’existaient pas en interne, et sessions qui ne correspondaient pas à la facturation. La récupération a été un mélange minutieux d’exploration de binlog et de réconciliation contre le fournisseur de paiement.
La correction n’a pas été héroïque. Ils ont séparé les responsabilités : une réplica dédiée au failover avec blocage strict des requêtes, et l’analytique a été déplacée vers ClickHouse via CDC. Le reporting est devenu rapide, et le failover est devenu fiable parce que la réplica n’était plus un punching-ball.
Optimisation qui s’est retournée contre eux : « Ajoutons juste un index »
Une équipe e-commerce avait une requête de reporting lente sur orders : filtre par plage de temps plus group-by. Quelqu’un a ajouté un index sur created_at et un index composite sur (created_at, customer_id). La requête est devenue plus rapide en isolation, ils ont livré et célébré.
Deux semaines plus tard, la latence d’écriture a commencé à grimper. Les inserts dans orders ont ralenti, et le taux de flush background a augmenté. Les nouveaux index ont augmenté l’amplification d’écriture — chaque insert entretenait plus de structures B-tree. Aux pics de trafic, ils payaient une taxe d’index sur chaque transaction pour rendre quelques rapports moins chers.
Puis l’outil BI a eu un nouveau tableau de bord qui exécutait la même requête chaque minute. La requête étant plus rapide, la concurrence a augmenté (les humains aiment rafraîchir quand c’est rapide). Le système a échangé une requête lente contre beaucoup de requêtes moyennes-rapides et s’est retrouvé I/O bound de toute façon.
La vraie solution a été de retirer la surcharge d’index, garder l’OLTP léger et construire une table de rollup ClickHouse mise à jour continuellement. Les tableaux de bord interrogent ClickHouse. Les transactions sont restées fluides. L’équipe a appris la leçon : indexer n’est pas « de la vitesse gratuite », c’est une facture en temps d’écriture que vous payez pour toujours.
Pratique ennuyeuse mais correcte qui a sauvé la journée : quotas et backfills étagés
Une société SaaS B2B utilisait ClickHouse pour l’analytique avec des profils d’utilisateurs stricts. Les utilisateurs BI avaient max_execution_time, max_memory_usage et des limites de concurrence. L’ETL avait des limites différentes et tournait dans une file contrôlée. Personne n’aimait ces contraintes. Tout le monde en a profité.
Un après-midi, un analyste a tenté d’exécuter une requête large sur deux années d’événements bruts sans filtre temporel. ClickHouse a commencé à scanner, a atteint la limite de temps d’exécution et a tué la requête. L’analyste s’est plaint. L’on-call n’a pas été page. C’est un bon compromis.
Plus tard dans le mois, l’équipe data a eu besoin d’un backfill à cause d’un changement de schéma dans le CDC en amont. Ils l’ont planifié : un jour à la fois, vérifiant le nombre de parts, le headroom disque et le lag après chaque tranche. Lent, soigneux, mesurable. Le backfill s’est terminé sans menacer les tableaux de bord de production.
La pratique ennuyeuse n’était pas un algorithme sophistiqué. C’était de la gouvernance et de la discipline opérationnelle : limites, files et backfills incrémentaux. Cela les a sauvés parce que le système s’est comporté de façon prévisible quand les humains ont été imprévisibles.
Blague n°2 : La seule chose plus permanente qu’un tableau de bord temporaire est le canal d’incident qu’il crée.
Erreurs courantes : symptôme → cause racine → correction
-
Symptôme : latence p95 MySQL augmente pendant les « heures de reporting »
Cause racine : scans longs et requêtes GROUP BY en compétition avec l’OLTP pour le buffer pool et l’I/O
Correction : Déplacez le reporting vers ClickHouse ; appliquez la politique ; ajoutez des rollups sélectionnés ; bloquez les utilisateurs BI sur MySQL. -
Symptôme : le lag de la réplica augmente quand les analystes lancent des rapports
Cause racine : I/O et CPU de la réplica saturés ; le thread SQL n’applique pas le binlog assez vite
Correction : Retirez l’accès analytique des réplica de failover ; utilisez CDC vers ClickHouse ; limitez la concurrence des requêtes. -
Symptôme : les requêtes ClickHouse ralentissent avec le temps sans changement de taille de données
Cause racine : explosion de parts ; merges en retard à cause d’inserts fragmentés ou de pression disque
Correction : Batch des inserts ; tunez prudemment les paramètres de merge ; surveillez les parts ; assurez-vous d’un headroom disque ; envisagez le repartitionnement. -
Symptôme : les tableaux de bord sont « rapides parfois » et font des timeouts aléatoirement sur ClickHouse
Cause racine : concurrence BI non bornée ; pression mémoire ; requêtes voisins bruyants
Correction : Fixez des limites par utilisateur (mémoire, threads, temps d’exécution, requêtes concurrentes) ; créez des tables pré-agrégées ; ajoutez du routage des requêtes. -
Symptôme : les données analytiques ont des doublons ou un « mauvais état courant »
Cause racine : CDC appliqué en append-only sans dédup/versioning ; updates/deletes mal modélisés
Correction : Utilisez des colonnes de version et ReplacingMergeTree quand approprié ; stockez des événements et dérivez l’état courant via des vues matérialisées. -
Symptôme : le disque ClickHouse ne cesse de grimper jusqu’à l’urgence
Cause racine : pas de TTL ; conservation brute éternelle ; backfills lourds ; pas de garde-fous de capacité
Correction : Appliquez des TTL pour les données froides ; downsamplez ; compressez ; archivez ; imposez des quotas et procédures de backfill. -
Symptôme : « Nous sommes passés à ClickHouse mais MySQL est toujours lent »
Cause racine : le pipeline CDC lit encore MySQL lourdement (extracts full-table, snapshots fréquents), ou l’app exécute toujours des rapports sur MySQL
Correction : Utilisez CDC basé sur binlog ; révisez les sources de requêtes MySQL ; firewall/retirez les comptes de reporting ; validez avec des données digest. -
Symptôme : fraîcheur ClickHouse en retard pendant les pics
Cause racine : goulot d’ingestion (débit du pipeline), merges ou pression disque ; parfois trop petits inserts
Correction : Batch des inserts ; scalez l’ingestion ; surveillez le lag ; réduisez temporairement la concurrence BI ; priorisez les ressources ETL.
Checklists / plan pas à pas
Étapes pas à pas : plan d’implémentation de la séparation propre
- Déclarez la frontière : MySQL de production est uniquement OLTP. Écrivez-le. Faites-le respecter avec des comptes et une politique réseau.
- Inventoriez les requêtes analytiques : utilisez les tables digest MySQL et les résumés du slow log pour lister les 20 familles de requêtes principales.
- Choisissez la méthode d’ingestion : CDC pour quasi temps réel ; batch pour quotidien/horaires ; évitez la double-écriture.
- Définissez les tables analytiques dans ClickHouse : commencez par des tables d’événements, partitionnement temporel et clés ORDER BY alignées sur les filtres.
- Construisez des jeux de données « gold » : vues matérialisées ou tables de rollup pour les tableaux de bord ; conservez les données brutes pour les analyses profondes.
- Mettez en place la gouvernance dès le premier jour : profils utilisateurs, quotas, max_execution_time, max_memory_usage, max_concurrent_queries.
- Mesurez la fraîcheur : suivez le lag d’ingestion et publiez le SLO aux parties prenantes. Les gens tolèrent l’obsolescence quand elle est explicite.
- Basculer les tableaux de bord : migrez d’abord les tableaux de bord à impact élevé (ceux qui pagent l’on-call indirectement).
- Bouchez l’ancien chemin : retirez les credentials BI de MySQL ; firewall si nécessaire ; évitez les régressions.
- Backfill en sécurité : incrémental, mesurable, avec vérifications d’espace disque ; pas de fantaisies « lancez-le juste cette nuit ».
- Load testez l’analytique : simulez la concurrence des tableaux de bord. ClickHouse acceptera volontiers votre optimisme puis vous punira.
- Opérationnalisez : alertes sur nombre de parts ClickHouse, utilisation disque, échecs de requêtes, lag d’ingestion ; et sur latence/I/O MySQL.
Checklist de release : déplacer un tableau de bord de MySQL vers ClickHouse
- La requête du tableau de bord inclut-elle un filtre temporel compatible avec le partitionnement ?
- Existe-t-il une table de rollup/vue matérialisée pour éviter de scanner les événements bruts à répétition ?
- L’utilisateur ClickHouse est-il limité (mémoire, threads, temps d’exécution, concurrence) ?
- La métrique de lag CDC est-elle visible par les utilisateurs du tableau de bord ?
- La vieille requête MySQL est-elle bloquée ou au moins retirée de l’app/outil BI ?
- Avez-vous validé les résultats pour une fenêtre temporelle connue (contrôles ponctuels des totaux et comptages) ?
Checklist opérationnelle : hygiène hebdomadaire qui prévient les catastrophes lentes
- Revoir les parts actives ClickHouse par table ; investiguer les croissances rapides.
- Revoir le headroom disque ClickHouse ; garder suffisamment d’espace libre pour merges et backfills.
- Revoir les top requêtes BI par read_rows et utilisation mémoire ; optimiser ou pré-agréger.
- Revoir les top digests MySQL pour s’assurer que l’analytique ne s’est pas infiltrée.
- Tester les chemins de restauration : sauvegardes MySQL, métadonnées ClickHouse et attentes de récupération des données.
FAQ
1) Ne puis-je pas simplement scaler verticalement MySQL et régler le problème ?
Vous le pouvez, et vous obtiendrez un soulagement temporaire. Le mode de défaillance revient quand le prochain tableau de bord ou la prochaine requête de cohortes apparaît. Le problème est l’inadéquation de la charge, pas seulement la puissance brute.
2) Si j’ai déjà des réplica MySQL — dois-je pointer le BI dessus ?
Seulement si vous êtes à l’aise avec le lag et si vous n’utilisez pas ces réplica pour le failover. Même dans ce cas, limitez la concurrence et considérez cela comme un pont temporaire, pas l’état final.
3) ClickHouse est-il « temps réel » assez pour des tableaux de bord opérationnels ?
Souvent oui, avec CDC. Mesurez explicitement le lag d’ingestion et concevez les tableaux de bord pour tolérer de petits délais. Si vous avez besoin de vérité transactionnelle sous-seconde, c’est le terrain de MySQL.
4) Comment gérer les updates et deletes de MySQL dans ClickHouse ?
Privilégiez la modélisation événementielle (append de changements). Si vous avez besoin d’un « état courant », utilisez des lignes versionnées avec des engines comme ReplacingMergeTree et concevez des requêtes/vues matérialisées en conséquence.
5) ClickHouse remplacera-t-il mon entrepôt de données ?
Parfois. Pour de nombreuses entreprises, il devient le magasin analytique principal. Mais si vous avez besoin de transformations lourdes, de gouvernance ou de modélisation cross-systèmes, vous pouvez garder une couche entrepôt. Ne forcez pas une conversion religieuse.
6) Quel est le gain le plus rapide si nous sommes en feu aujourd’hui ?
Arrêtez immédiatement d’exécuter l’analytique sur MySQL : tuez les pires requêtes, retirez l’accès BI et basculez le tableau de bord vers ClickHouse ou un rollup mis en cache. Ensuite réparez proprement.
7) Quelle est la plus grande surprise opérationnelle ClickHouse pour les équipes MySQL ?
Les merges et les parts. Les gens habitués au row-store s’attendent à « j’ai inséré, c’est fait ». ClickHouse continue de travailler en arrière-plan, et vous devez surveiller ce travail.
8) Comment empêcher les analystes d’écrire des requêtes ClickHouse coûteuses ?
Utilisez des profils utilisateur avec quotas et timeouts, fournissez des tables « gold » sélectionnées, et enseignez que l’absence de filtre temporel n’est pas « exploration », c’est de l’arson.
9) Les vues matérialisées résolvent-elles tout ?
Non. Elles sont excellentes pour les rollups stables et les agrégats communs. Mais elles peuvent ajouter de la complexité et un coût de stockage. Utilisez-les là où elles réduisent de façon mesurable le travail répété.
10) Et si mes requêtes analytiques demandent des joins complexes sur de nombreuses tables ?
Dénormalisez pour les chemins fréquents, pré-calculer les dimensions et limitez les joins. ClickHouse sait joindre, mais les meilleurs systèmes analytiques de production évitent de le faire systématiquement à l’exécution.
Conclusion : prochaines étapes pratiques
Si vous ne faites qu’une action cette semaine, faites celle-ci : supprimez la charge analytique de MySQL. Pas en suppliant les utilisateurs d’« être prudents », mais en fournissant un meilleur endroit pour poser des questions.
- Verrouillez MySQL : comptes séparés, blocage des réseaux BI et faites respecter que MySQL de production sert d’abord les utilisateurs.
- Mettez en place la gouvernance ClickHouse : limites, quotas et jeux de données sélectionnés avant d’inviter toute l’entreprise.
- Déplacez les 5 pires requêtes : répliquez les données nécessaires via CDC ou batch, puis construisez des rollups pour que les tableaux de bord restent peu coûteux.
- Operationalisez la fraîcheur : publiez le lag d’ingestion et traitez-le comme une exigence produit. Il vaut mieux être honnêtement 60 secondes en retard que incorrect sans le savoir.
- Exercez-vous aux backfills : étagés, mesurables, réversibles. Votre futur vous remerciera la retenue actuelle.
La séparation propre n’est pas glamour. C’est simplement la différence entre une base de données qui sert des clients et une base de données qui héberge un combat de cage analytique quotidien. Choisissez la vie plus calme.