← Journal technique

backend

ClickHouse : « revenu sans lever d'exception » n'est pas « fait »

ClickHouse : « revenu sans lever d'exception » n'est pas « fait »

J'ai rencontré ces deux cas en faisant une contribution à un produit d'analytique open source. Assez généraux pour tenir seuls.

Un dictionnaire ClickHouse se pose sur une table normale et expose des recherches rapides clé-vers-valeur via dictGet(...). Bien utilisé, ça rend un moteur de règles vraiment piloté par les données — le SQL appelle dictGet une fois, et chaque nouvelle ligne dans la table sous-jacente est prise en compte sans aucun changement SQL.

Le bord tranchant

Un dictionnaire HASHED ou COMPLEX_KEY_HASHED ne définit pas de comportement quand deux lignes partagent une clé. Une migration qui reclasse une clé existante avec un simple INSERT laisse les deux lignes, et celle que le dictionnaire renvoie est indéfinie — rien ne lève d'exception, les requêtes continuent de marcher, elles renvoient juste l'une ou l'autre valeur de façon imprévisible.

Si une migration peut toucher une clé qui existe déjà, supprime d'abord l'ancienne ligne. Ne suppose pas que l'insertion-seule est sûre juste parce que la table autorise techniquement les doublons.

Le bord plus tranchant

ALTER TABLE ... DELETE est une mutation asynchrone par défaut. L'instruction revient une fois la mutation mise en file, pas une fois appliquée — donc une suppression immédiatement suivie d'une insertion dépendante peut s'exécuter contre des données qui n'ont pas réellement encore été supprimées.

J'ai confirmé que c'était réel en le testant directement plutôt qu'en lisant la doc et en décidant : inséré une ligne périmée, exécuté suppression-puis-insertion, requêté juste après — la ligne périmée était toujours là. system.mutations montrait is_done = 1 peu après, cohérent avec un décalage d'achèvement ordinaire, mais la requête dans l'intervalle voyait des données périmées.

ALTER TABLE my_table DELETE WHERE key IN (...) SETTINGS mutations_sync = 2

mutations_sync = 1 attend la réplique locale ; 2 attend toutes les répliques avant de revenir. Avec ça, suppression-puis-insertion est vraiment sûre. J'ai reproduit le même test de course avec le paramètre ajouté — le doublon était fiablement parti, à chaque fois.

La leçon transférable : « revenu sans lever d'exception » est une affirmation bien plus faible que « fait », pour toute mutation ClickHouse — et honnêtement pour beaucoup d'opérations asynchrones. Si la justesse dépend d'un ordre entre une mutation et une instruction ultérieure, impose cet ordre explicitement. Être séquentiel dans le même script ne l'implique pas. Et teste-le en reproduisant la vraie course, pas en lisant le code et en décidant qu'il a l'air correct.