Cliente de ClickHouse: Cómo me hice amigo de la consola y la API HTTP en un proyecto de apuestas
Mi primer toque en una puerta cerrada
Recuerdo que después de instalar ClickHouse, escribí felizmente clickhouse-client y obtuve un error: Code: 210. DB::NetException: Connection refused (localhost:9000). Resulta que el servidor solo escuchaba en 127.0.0.1, y yo intentaba conectarme desde otra máquina. Una hora de Google, editando config.xml, reiniciando — y solo entonces aprendí que el cliente tiene las opciones --host y --port.
ClickHouse ofrece dos formas de comunicación: el cliente nativo (para humanos y scripts) y la API HTTP (para todo lo demás). Uso ambos a diario. A continuación, todo lo que realmente necesitas, más los errores que he cometido.
Métodos de conexión: De lo simple a lo correcto
Método 1. Ingenuo (solo localhost)
clickhouse-client
Esto solo funciona si estás en la misma máquina que el servidor y no has cambiado el puerto 9000. Nadie hace esto en producción.
Método 2. Profesional: Opciones para acceso remoto
clickhouse-client \
--host analytics.prod.company.com \
--port 9000 \
--user analyst \
--password 'StrongPass123' \
--database betting
Lo que aprendí en producción: nunca pases una contraseña en la línea de comandos si el historial de bash está habilitado. En su lugar, usa un archivo de configuración.
Método 3. Correcto: Archivo de configuración
Crea ~/.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>
Contraseña mediante variable de entorno:
export CLICKHOUSE_PASSWORD="StrongPass123"
clickhouse-client
Por qué es más seguro: la contraseña no aparecerá en ps aux ni en el historial. En producción, tuvimos un caso donde un desarrollador ejecutó clickhouse-client --password secret, y en una hora todos podían ver la contraseña en los logs del orquestador.
Método 4. Conexión HTTP (Alternativa para CI/CD)
Para automatización, a menudo uso HTTP:
curl -u analyst:StrongPass123 \
"http://clickhouse.prod.internal:8123/?query=SELECT+1"
Modo interactivo vs. por lotes: Cuándo usar cada uno
Modo interactivo (para humanos)
clickhouse-client
Pros: autocompletado (presiona Tab), historial de comandos, consultas multilínea. Contras: no para scripts.
:) SELECT user_id, sum(amount) FROM bets GROUP BY user_id LIMIT 5;
Mi truco de vida: en modo interactivo, funcionan atajos como \l (listar bases de datos), \d (listar tablas), \c betting (cambiar de base de datos). No todos lo saben, pero ahorra mucho tiempo.
Modo por lotes (para scripts y cron)
# Comando único
clickhouse-client --query "SELECT count() FROM betting.bets"
# Desde archivo
clickhouse-client --queries-file /path/to/analytics.sql
# Multilínea mediante 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
Lo que me quemó: en modo por lotes, siempre termina tu consulta con un punto y coma. Sin él, el comando no se ejecuta, pero tampoco muestra un error — simplemente se cuelga. Perdimos una hora depurando trabajos de cron.
API HTTP: curl es tu mejor amigo
La interfaz HTTP funciona en el puerto 8123. Es perfecta para microservicios, paneles y scripts en cualquier lenguaje.
Solicitudes GET: Simples y rápidas
# Consulta más simple
curl "http://localhost:8123/?query=SELECT+version()"
# Con autenticación
curl -u user:pass "http://localhost:8123/?query=SELECT+count()+FROM+betting.bets"
# Con parámetro de base de datos
curl "http://localhost:8123/?database=betting&query=SELECT+count()+FROM+bets"
Solicitudes POST: Para consultas grandes e inserción de datos
# Consulta larga mediante POST (sin límite de longitud de URL)
curl -X POST "http://localhost:8123/" \
-d "SELECT user_id, sum(amount) FROM betting.bets GROUP BY user_id"
# Insertar datos mediante POST
curl -X POST "http://localhost:8123/?query=INSERT+INTO+betting.bets+FORMAT+CSV" \
--data-binary @bets_data.csv
Formatos de respuesta: Elige según tu tarea
ClickHouse puede devolver datos en muchos formatos. Los he probado todos — aquí están los que realmente necesitas:
# Pretty — para humanos (legible pero muchos caracteres de formato)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=Pretty"
# JSON — para APIs (se parsea en todas partes)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=JSON"
# JSONEachRow — para procesamiento línea por línea (eficiente en memoria)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=JSONEachRow"
# CSV — para exportar a Excel/Google Sheets
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=CSV"
# TabSeparated — para tuberías a otras herramientas (grep, awk)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=TSV"
Ejemplo real: Enviamos agregados a un bot de Telegram. Usamos JSONEachRow, lo parseamos en Python con un solo response.json(), y lo formateamos en un mensaje.
Creando una base de datos para una plataforma de apuestas
CREATE DATABASE IF NOT EXISTS betting;
Y cambia a ella inmediatamente:
clickhouse-client --database betting
O dentro del cliente:
USE betting;
Primera tabla: Esquema de apuestas de un proyecto real
En mi proyecto de producción para análisis de apuestas, la tabla se ve así:
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);
Por qué así:
LowCardinalitypara deporte — fútbol, baloncesto, tenis. Se repiten miles de veces, comprimidos en un diccionario.Enum8para resultado — solo tres valores, ocupa 1 byte en lugar de una cadena.DateTime64(3)— los milisegundos importan para el análisis de apuestas en vivo.
Error común de principiantes: olvidar especificar ENGINE = MergeTree(). Sin él, ClickHouse crea una tabla con el motor TinyLog (solo pruebas), que no se puede particionar y no soporta replicación. En producción, insertar 10 millones de filas en esa tabla la matará.
Insertando datos de prueba
Registro único
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');
Múltiples registros (inserción por lotes)
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');
Generando datos de prueba con numbers()
Para pruebas de carga, a menudo genero un millón de registros sobre la marcha:
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);
Nota importante: Esta inserción tomará de 5 a 10 segundos en un servidor decente. ClickHouse está optimizado para estas operaciones masivas, pero en una VM débil podría tomar un minuto.
Consultas SELECT básicas en el contexto de apuestas
WHERE — Filtrado
-- Apuestas de un usuario específico en la última hora
SELECT *
FROM betting.bets
WHERE user_id = 1001
AND created_at >= now() - INTERVAL 1 HOUR;
-- Apuestas ganadoras con cuotas mayores a 2.0
SELECT user_id, amount, odds, amount * odds AS payout
FROM betting.bets
WHERE outcome = 'win' AND odds > 2.0;
ORDER BY — Ordenación
-- Apuestas más grandes hoy
SELECT user_id, amount, created_at
FROM betting.bets
WHERE created_at >= today()
ORDER BY amount DESC
LIMIT 10;
-- Últimas 5 apuestas de un usuario
SELECT created_at, sport, amount, odds, outcome
FROM betting.bets
WHERE user_id = 1001
ORDER BY created_at DESC
LIMIT 5;
GROUP BY — Analítica
-- Pagos por deporte en la semana
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;
Combinando condiciones
-- Usuarios que hicieron más de 10 apuestas en un día
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;
Caso práctico: Encontrando patrones sospechosos
Aquí hay una consulta real de nuestro sistema de detección 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;
Esta consulta encuentra bots: aquellos que apuestan más de 100 veces por hora con cantidades casi idénticas. En PostgreSQL con 50 millones de registros, nunca terminaría. ClickHouse devuelve la respuesta en 0.6 segundos.
Exportando datos: Cuando necesitas dárselos al negocio
# Exportar a CSV para el departamento de 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
# Exportar a JSON para la API de otro servicio
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
Errores comunes y sus soluciones
Error: Code: 102. DB::NetException: Connection refused
Causa: Host o puerto incorrectos, o el servidor no está escuchando conexiones externas.
Solución: Verifica netstat -tulpn | grep clickhouse. Si no hay 0.0.0.0:9000, edita config.xml:
<listen_host>0.0.0.0</listen_host>
Error: Code: 81. DB::Exception: Database betting doesn't exist
Causa: No creaste la base de datos o no especificaste --database.
Solución: CREATE DATABASE IF NOT EXISTS betting; o conéctate con --database betting.
Error: Code: 62. DB::Exception: Syntax error: failed at position 1
Causa: Olvidaste el punto y coma en modo por lotes.
Solución: Siempre pon ; al final de la consulta cuando uses --query.
¿Qué sigue?
Ahora sabes cómo conectarte a ClickHouse de cualquier forma, crear tablas, insertar datos y ejecutar consultas. En el próximo artículo, nos sumergiremos en analítica avanzada: funciones de ventana, arrays, agregaciones y vistas materializadas.
Todos los ejemplos probados en ClickHouse 24.8. Si algo no funciona, primero verifica la versión: SELECT version(); Esto me ha salvado cientos de veces.
← Anterior: ClickHouse en Docker: Cómo dejé de preocuparme y lancé análisis en 2 minutos
→ Siguiente: ClickHouse: Referencia completa de tipos de datos para análisis de apuestas (lo que me costó caro)
— Editorial Team
Aún no hay comentarios.