Client ClickHouse : Comment j'ai apprivoisé la console et l'API HTTP dans un projet de paris
Mon premier coup sur une porte fermée
Je me souviens qu'après avoir installé ClickHouse, j'ai tapé joyeusement clickhouse-client et j'ai obtenu une erreur : Code: 210. DB::NetException: Connection refused (localhost:9000). Il s'est avéré que le serveur n'écoutait que sur 127.0.0.1, et j'essayais de me connecter depuis une autre machine. Une heure de recherches Google, d'édition de config.xml, de redémarrage — et ce n'est qu'à ce moment-là que j'ai appris que le client avait les options --host et --port.
ClickHouse propose deux méthodes de communication : le client natif (pour les humains et les scripts) et l'API HTTP (pour tout le reste). J'utilise les deux quotidiennement. Voici tout ce dont vous avez vraiment besoin, ainsi que les pièges dans lesquels je suis tombé.
Méthodes de connexion : du simple au professionnel
Méthode 1. Naïve (localhost uniquement)
clickhouse-client
Cela fonctionne uniquement si vous êtes sur la même machine que le serveur et que vous n'avez pas changé le port 9000. Personne ne fait ça en production.
Méthode 2. Professionnelle : options pour l'accès distant
clickhouse-client \
--host analytics.prod.company.com \
--port 9000 \
--user analyst \
--password 'StrongPass123' \
--database betting
Ce que j'ai appris de la production : ne jamais passer un mot de passe en ligne de commande si l'historique bash est activé. Utilisez plutôt un fichier de configuration.
Méthode 3. Correcte : fichier de configuration
Créez ~/.clickhouse-client/config.xml :
<config>
<host>clickhouse.prod.internal</host>
<port>9000</port>
<user>analyst</user>
<password>${CLICKHOUSE_PASSWORD}</password>
<database>betting</database>
<history_file>/home/user/.clickhouse-client-history</history_file>
</config>
Mot de passe via variable d'environnement :
export CLICKHOUSE_PASSWORD="StrongPass123"
clickhouse-client
Pourquoi c'est plus sûr : le mot de passe n'apparaîtra pas dans ps aux ni dans l'historique. En production, nous avons eu un cas où un développeur a exécuté clickhouse-client --password secret, et en une heure, tout le monde pouvait voir le mot de passe dans les logs de l'orchestrateur.
Méthode 4. Connexion HTTP (alternative pour CI/CD)
Pour l'automatisation, j'utilise souvent HTTP :
curl -u analyst:StrongPass123 \
"http://clickhouse.prod.internal:8123/?query=SELECT+1"
Mode interactif vs batch : quand utiliser quoi
Mode interactif (pour les humains)
clickhouse-client
Avantages : autocomplétion (appuyez sur Tab), historique des commandes, requêtes multi-lignes. Inconvénients : pas pour les scripts.
:) SELECT user_id, sum(amount) FROM bets GROUP BY user_id LIMIT 5;
Mon astuce : en mode interactif, des raccourcis comme \l (lister les bases de données), \d (lister les tables), \c betting (changer de base de données) fonctionnent. Tout le monde ne le sait pas, mais cela fait gagner un temps fou.
Mode batch (pour les scripts et cron)
# Commande unique
clickhouse-client --query "SELECT count() FROM betting.bets"
# Depuis un fichier
clickhouse-client --queries-file /path/to/analytics.sql
# Multi-lignes via heredoc
clickhouse-client <<SQL
SELECT
toDate(created_at) AS day,
count() AS bets
FROM betting.bets
WHERE created_at >= today() - 7
GROUP BY day
ORDER BY day;
SQL
Ce qui m'a brûlé : en mode batch, terminez toujours votre requête par un point-virgule. Sans cela, la commande ne s'exécute pas, mais elle n'affiche pas non plus d'erreur — elle reste bloquée. Nous avons perdu une heure à déboguer des tâches cron.
API HTTP : curl est votre meilleur ami
L'interface HTTP écoute sur le port 8123. Elle est parfaite pour les microservices, les tableaux de bord et les scripts dans n'importe quel langage.
Requêtes GET : simples et rapides
# Requête la plus simple
curl "http://localhost:8123/?query=SELECT+version()"
# Avec authentification
curl -u user:pass "http://localhost:8123/?query=SELECT+count()+FROM+betting.bets"
# Avec paramètre de base de données
curl "http://localhost:8123/?database=betting&query=SELECT+count()+FROM+bets"
Requêtes POST : pour les requêtes longues et l'insertion de données
# Requête longue via POST (pas de limite de longueur d'URL)
curl -X POST "http://localhost:8123/" \
-d "SELECT user_id, sum(amount) FROM betting.bets GROUP BY user_id"
# Insertion de données via POST
curl -X POST "http://localhost:8123/?query=INSERT+INTO+betting.bets+FORMAT+CSV" \
--data-binary @bets_data.csv
Formats de réponse : choisissez selon votre tâche
ClickHouse peut renvoyer les données dans de nombreux formats. Je les ai tous essayés — voici ce dont vous avez vraiment besoin :
# Pretty — pour les humains (lisible mais beaucoup de caractères de formatage)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=Pretty"
# JSON — pour les API (analysé partout)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=JSON"
# JSONEachRow — pour le traitement ligne par ligne (économe en mémoire)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=JSONEachRow"
# CSV — pour l'export vers Excel/Google Sheets
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=CSV"
# TabSeparated — pour le pipe vers d'autres outils (grep, awk)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=TSV"
Exemple concret : Nous envoyons des agrégats à un bot Telegram. Nous utilisons JSONEachRow, nous l'analysons en Python avec un simple response.json(), et nous le formatons en message.
Création d'une base de données pour une plateforme de paris
CREATE DATABASE IF NOT EXISTS betting;
Et basculez immédiatement :
clickhouse-client --database betting
Ou à l'intérieur du client :
USE betting;
Première table : schéma de pari d'un projet réel
Dans mon projet de production pour l'analyse des paris, la table ressemble à ceci :
CREATE TABLE betting.bets
(
user_id UInt64,
created_at DateTime64(3),
amount Decimal(18, 2),
odds Float64,
sport LowCardinality(String),
outcome Enum8('win' = 1, 'loss' = 2, 'void' = 3),
event_id UInt64,
bet_type String
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (created_at, user_id);
Pourquoi ainsi :
LowCardinalitypour le sport — football, basket, tennis. Ils se répètent des milliers de fois, compressés dans un dictionnaire.Enum8pour le résultat — seulement trois valeurs, prend 1 octet au lieu d'une chaîne.DateTime64(3)— les millisecondes comptent pour l'analyse des paris en direct.
Erreur courante de débutant : oublier de spécifier ENGINE = MergeTree(). Sans cela, ClickHouse crée une table avec le moteur TinyLog (test uniquement), qui ne peut pas être partitionnée et ne supporte pas la réplication. En production, insérer 10 millions de lignes dans une telle table la tuera.
Insertion de données de test
Enregistrement unique
INSERT INTO betting.bets (user_id, created_at, amount, odds, sport, outcome, event_id, bet_type)
VALUES (1001, now(), 50.00, 2.1, 'football', 'win', 50001, 'single');
Enregistrements multiples (insertion par lots)
INSERT INTO betting.bets VALUES
(1002, now() - INTERVAL 1 HOUR, 100.00, 1.8, 'basketball', 'loss', 50002, 'single'),
(1003, now() - INTERVAL 2 HOUR, 200.00, 3.0, 'tennis', 'win', 50003, 'express'),
(1001, now() - INTERVAL 30 MINUTE, 75.00, 2.5, 'football', 'void', 50001, 'single');
Génération de données de test avec numbers()
Pour les tests de charge, je génère souvent un million d'enregistrements à la volée :
INSERT INTO betting.bets
SELECT
number % 10000 AS user_id,
now() - INTERVAL (number % 86400) SECOND,
(number % 1000) / 10 + 10,
1.5 + (number % 200) / 100,
arrayElement(['football', 'basketball', 'tennis', 'hockey'], (number % 4) + 1),
CAST((number % 3) + 1 AS Enum8('win' = 1, 'loss' = 2, 'void' = 3)),
number,
'single'
FROM numbers(1000000);
Remarque importante : Cette insertion prendra 5 à 10 secondes sur un serveur décent. ClickHouse est optimisé pour ce genre d'opérations en masse, mais sur une VM faible, cela peut prendre une minute.
Requêtes SELECT de base dans le contexte des paris
WHERE — Filtrage
-- Paris d'un utilisateur spécifique dans la dernière heure
SELECT *
FROM betting.bets
WHERE user_id = 1001
AND created_at >= now() - INTERVAL 1 HOUR;
-- Paris gagnants avec une cote supérieure à 2.0
SELECT user_id, amount, odds, amount * odds AS payout
FROM betting.bets
WHERE outcome = 'win' AND odds > 2.0;
ORDER BY — Tri
-- Plus gros paris aujourd'hui
SELECT user_id, amount, created_at
FROM betting.bets
WHERE created_at >= today()
ORDER BY amount DESC
LIMIT 10;
-- 5 derniers paris d'un utilisateur
SELECT created_at, sport, amount, odds, outcome
FROM betting.bets
WHERE user_id = 1001
ORDER BY created_at DESC
LIMIT 5;
GROUP BY — Analyses
-- Gains par sport pour la semaine
SELECT
sport,
count() AS total_bets,
sum(amount) AS total_staked,
sumIf(amount * odds, outcome = 'win') AS total_payout,
round(total_payout / total_staked, 4) AS roi
FROM betting.bets
WHERE created_at >= today() - 7
GROUP BY sport
ORDER BY total_bets DESC;
Combinaison de conditions
-- Utilisateurs ayant placé plus de 10 paris en un jour
SELECT
user_id,
count() AS bets_count,
sum(amount) AS total_amount
FROM betting.bets
WHERE created_at >= today()
GROUP BY user_id
HAVING bets_count > 10
ORDER BY total_amount DESC;
Cas pratique : détection de schémas suspects
Voici une requête réelle de notre système de détection de fraude :
WITH hourly_bets AS (
SELECT
user_id,
toStartOfHour(created_at) AS hour,
count() AS bets_per_hour,
avg(amount) AS avg_bet
FROM betting.bets
WHERE created_at >= now() - INTERVAL 3 HOUR
GROUP BY user_id, hour
)
SELECT
user_id,
max(bets_per_hour) AS max_rate,
avg(avg_bet) AS typical_bet,
stddevPop(avg_bet) AS bet_variance
FROM hourly_bets
GROUP BY user_id
HAVING max_rate > 100 AND bet_variance < 0.5;
Cette requête trouve les bots : ceux qui parient plus de 100 fois par heure avec des montants presque identiques. Avec PostgreSQL sur 50 millions d'enregistrements, cela ne finirait jamais. ClickHouse renvoie la réponse en 0,6 seconde.
Exportation de données : quand vous devez les donner au métier
# Export vers CSV pour le service marketing
clickhouse-client --query "
SELECT user_id, sum(amount) AS total_bet, count() AS bet_count
FROM betting.bets
WHERE created_at >= '2024-01-01'
GROUP BY user_id
ORDER BY total_bet DESC
LIMIT 1000
" --format CSV > top_users.csv
# Export vers JSON pour l'API d'un autre service
curl "http://localhost:8123/?query=SELECT+user_id,sum(amount)+FROM+betting.bets+GROUP+BY+user_id+LIMIT+10&default_format=JSON" \
-o top_users.json
Erreurs courantes et leurs solutions
Erreur : Code: 102. DB::NetException: Connection refused
Cause : Mauvais hôte ou port, ou le serveur n'écoute pas les connexions externes.
Solution : Vérifiez netstat -tulpn | grep clickhouse. S'il n'y a pas de 0.0.0.0:9000, modifiez config.xml :
<listen_host>0.0.0.0</listen_host>
Erreur : Code: 81. DB::Exception: Database betting doesn't exist
Cause : Vous n'avez pas créé la base de données ou n'avez pas spécifié --database.
Solution : CREATE DATABASE IF NOT EXISTS betting; ou connectez-vous avec --database betting.
Erreur : Code: 62. DB::Exception: Syntax error: failed at position 1
Cause : Vous avez oublié le point-virgule en mode batch.
Solution : Mettez toujours ; à la fin de la requête lorsque vous utilisez --query.
Et ensuite ?
Maintenant vous savez comment vous connecter à ClickHouse de toutes les manières, créer des tables, insérer des données et exécuter des requêtes. Dans le prochain article, nous plongerons dans les analyses avancées : fonctions de fenêtre, tableaux, agrégations et vues matérialisées.
Tous les exemples testés sur ClickHouse 24.8. Si quelque chose ne fonctionne pas, vérifiez d'abord la version : SELECT version(); Cela m'a sauvé des centaines de fois.
← Précédente: ClickHouse dans Docker : comment j'ai arrêté de m'inquiéter et lancé l'analyse en 2 minutes
→ Suivante: ClickHouse : Référence complète des types de données pour l'analyse des paris (ce qui m'a coûté cher)
— Editorial Team
Aucun commentaire pour le moment.