Powrót do strony głównej

ClickHouse client i HTTP API: połączenie i pierwsze zapytania

Przewodnik po wszystkich sposobach połączenia z ClickHouse: clickhouse-client z flagami, plik konfiguracyjny ~/.clickhouse-client/config.xml, HTTP API przez curl. Pokazano tryb interaktywny i batch, formaty odpowiedzi (JSON, CSV, Pretty, TSV). Na przykładzie platformy bukmacherskiej tworzona jest baza danych betting, tabela bets z typami LowCardinality i Enum8, wstawiane są dane testowe przez INSERT i generator numbers(), wykonywane są SELECT z WHERE, ORDER BY, GROUP BY.

ClickHouse client: jak się łączyć i pracować z konsolą i HTTP
Advertisement 728x90

ClickHouse client: jak zaprzyjaźniłem się z konsolą i HTTP API w projekcie gamblingowym

Moje pierwsze pukanie do zamkniętych drzwi

Pamiętam, jak po instalacji ClickHouse z radością wpisałem clickhouse-client i dostałem błąd: Code: 210. DB::NetException: Connection refused (localhost:9000). Okazało się, że serwer słucha tylko na 127.0.0.1, a ja próbowałem połączyć się z innej maszyny. Godzina googlowania, edycja config.xml, restart — i dopiero wtedy dowiedziałem się, że klient ma flagi --host i --port.

ClickHouse daje dwa sposoby komunikacji: natywny klient (dla ludzi i skryptów) oraz HTTP API (dla wszystkiego innego). Używam obu każdego dnia. Poniżej — wszystko, co naprawdę potrzebne, plus grabie, na które nadepnąłem.

Sposoby połączenia: od prostego do właściwego

Sposób 1. Naiwny (tylko dla localhost)

clickhouse-client

To działa tylko wtedy, gdy jesteś na tej samej maszynie co serwer i nie zmieniałeś portu 9000. W produkcji nikt tak nie robi.

Google AdInline article slot

Sposób 2. Profesjonalny: flagi dla zdalnego dostępu

clickhouse-client \
  --host analytics.prod.company.com \
  --port 9000 \
  --user analyst \
  --password 'StrongPass123' \
  --database betting

Co wyniosłem z produkcji: nigdy nie przekazuj hasła w wierszu poleceń, jeśli włączona jest historia bash. Zamiast tego użyj pliku konfiguracyjnego.

Sposób 3. Właściwy: plik konfiguracyjny

Tworzymy ~/.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>

Hasło przez zmienną środowiskową:

Google AdInline article slot
export CLICKHOUSE_PASSWORD="StrongPass123"
clickhouse-client

Dlaczego to bezpieczniejsze: hasło nie trafi do ps aux ani historii. W produkcji mieliśmy przypadek, gdy programista uruchomił clickhouse-client --password secret, a po godzinie wszyscy widzieli hasło w logach orkiestratora.

Sposób 4. Połączenie przez HTTP (alternatywa dla CI/CD)

Do automatyzacji często używam HTTP:

curl -u analyst:StrongPass123 \
  "http://clickhouse.prod.internal:8123/?query=SELECT+1"

Tryb interaktywny vs batch: kiedy czego używać

Tryb interaktywny (dla ludzi)

clickhouse-client

Zalety: autouzupełnianie (naciśnij Tab), historia poleceń, zapytania wieloliniowe. Wady: nie do skryptów.

Google AdInline article slot
:) SELECT user_id, sum(amount) FROM bets GROUP BY user_id LIMIT 5;

Mój lifehack: w trybie interaktywnym działają skróty \l (lista baz), \d (lista tabel), \c betting (zmiana bazy). Nie wszyscy wiedzą, ale oszczędza mnóstwo czasu.

Tryb batch (dla skryptów i cron)

# Jedno polecenie
clickhouse-client --query "SELECT count() FROM betting.bets"

# Z pliku
clickhouse-client --queries-file /path/to/analytics.sql

# Wieloliniowe przez 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

Na czym się sparzyłem: w trybie batch kończ zapytanie średnikiem. Bez niego polecenie się nie wykona, ale też nie pokaże błędu — po prostu zawisnie. Straciliśmy godzinę na debugowanie zadań cron.

HTTP API: curl — twój najlepszy przyjaciel

Interfejs HTTP działa na porcie 8123. Jest idealny dla mikrousług, dashboardów i skryptów w dowolnym języku.

Zapytania GET: prosto i szybko

# Najprostsze zapytanie
curl "http://localhost:8123/?query=SELECT+version()"

# Z autoryzacją
curl -u user:pass "http://localhost:8123/?query=SELECT+count()+FROM+betting.bets"

# Z parametrem database
curl "http://localhost:8123/?database=betting&query=SELECT+count()+FROM+bets"

Zapytania POST: dla dużych zapytań i wstawiania danych

# Długie zapytanie przez POST (bez limitu długości URL)
curl -X POST "http://localhost:8123/" \
  -d "SELECT user_id, sum(amount) FROM betting.bets GROUP BY user_id"

# Wstawianie danych przez POST
curl -X POST "http://localhost:8123/?query=INSERT+INTO+betting.bets+FORMAT+CSV" \
  --data-binary @bets_data.csv

Formaty odpowiedzi: wybierz pod zadanie

ClickHouse potrafi zwracać dane w wielu formatach. Przetestowałem wszystkie — oto co naprawdę jest potrzebne:

# Pretty — dla człowieka (czytelny, ale dużo znaków kontrolnych)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=Pretty"

# JSON — dla API (parsowalny wszędzie)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=JSON"

# JSONEachRow — do przetwarzania linia po linii (oszczędny pamięciowo)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=JSONEachRow"

# CSV — do eksportu do Excel/Google Sheets
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=CSV"

# TabSeparated — do przekazywania do innych narzędzi (grep, awk)
curl "http://localhost:8123/?query=SELECT+user_id,amount+FROM+bets+LIMIT+3&default_format=TSV"

Przykład z życia: Wysyłamy agregaty do bota telegramowego. Używamy JSONEachRow, parsujemy w Pythonie jedną linią response.json() i formatujemy w wiadomość.

Tworzymy bazę danych dla platformy bukmacherskiej

CREATE DATABASE IF NOT EXISTS betting;

I od razu przełączamy się na nią:

clickhouse-client --database betting

Lub już wewnątrz klienta:

USE betting;

Pierwsza tabela: schemat zakładów z rzeczywistego projektu

W moim projekcie produkcyjnym do analityki zakładów tabela wygląda tak:

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);

Dlaczego tak:

  • LowCardinality dla sport — piłka nożna, koszykówka, tenis. Powtarzają się tysiące razy, kompresują się do słownika.
  • Enum8 dla outcome — tylko trzy wartości, zajmuje 1 bajt zamiast stringa.
  • DateTime64(3) — milisekundy są ważne dla analityki zakładów na żywo.

Częsty błąd początkujących: zapominają podać ENGINE = MergeTree(). Bez tego ClickHouse utworzy tabelę z silnikiem TinyLog (tylko do testów), której nie można partycjonować i która nie wspiera replikacji. W produkcji przy wstawianiu 10 mln wierszy taka tabela umrze.

Wstawiamy dane testowe

Jeden rekord

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');

Wiele rekordów (wstawianie wsadowe)

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');

Generowanie danych testowych przez numbers()

Do testów obciążeniowych często generuję milion rekordów na bieżąco:

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);

Ważna uwaga: to wstawianie zajmie 5-10 sekund na normalnym serwerze. ClickHouse jest zoptymalizowany do takich operacji wsadowych, ale na słabej wirtualce może ładować się minutę.

Podstawowe zapytania SELECT w kontekście zakładów

WHERE — filtrowanie

-- Zakłady konkretnego użytkownika z ostatniej godziny
SELECT *
FROM betting.bets
WHERE user_id = 1001 
  AND created_at >= now() - INTERVAL 1 HOUR;

-- Wygrane zakłady z kursem większym niż 2.0
SELECT user_id, amount, odds, amount * odds AS payout
FROM betting.bets
WHERE outcome = 'win' AND odds > 2.0;

ORDER BY — sortowanie

-- Największe zakłady dzisiaj
SELECT user_id, amount, created_at
FROM betting.bets
WHERE created_at >= today()
ORDER BY amount DESC
LIMIT 10;

-- Ostatnie 5 zakładów użytkownika
SELECT created_at, sport, amount, odds, outcome
FROM betting.bets
WHERE user_id = 1001
ORDER BY created_at DESC
LIMIT 5;

GROUP BY — analityka

-- Wypłaty według dyscyplin sportowych w ciągu tygodnia
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;

Kombinacje warunków

-- Użytkownicy, którzy postawili więcej niż 10 zakładów dzisiaj
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;

Praktyczny przypadek: znajdowanie podejrzanych wzorców

Oto prawdziwe zapytanie z naszego systemu detekcji fraudów:

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;

To zapytanie znajduje boty: tych, którzy stawiają częściej niż 100 razy na godzinę i z prawie identyczną kwotą. W PostgreSQL przy 50 mln rekordów nigdy by się nie wykonało. ClickHouse zwraca odpowiedź w 0,6 sekundy.

Eksport danych: gdy trzeba oddać biznesowi

# Eksport do CSV dla działu marketingu
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

# Eksport do JSON dla API innego serwisu
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

Typowe błędy i ich rozwiązania

Błąd: Code: 102. DB::NetException: Connection refused

Przyczyna: Zły host lub port, albo serwer nie słucha połączeń zewnętrznych.

Rozwiązanie: Sprawdź netstat -tulpn | grep clickhouse. Jeśli nie ma 0.0.0.0:9000, edytuj config.xml:

<listen_host>0.0.0.0</listen_host>

Błąd: Code: 81. DB::Exception: Database betting doesn't exist

Przyczyna: Nie utworzyłeś bazy lub nie podałeś --database.

Rozwiązanie: CREATE DATABASE IF NOT EXISTS betting; lub połącz się z flagą --database betting.

Błąd: Code: 62. DB::Exception: Syntax error: failed at position 1

Przyczyna: Zapomniałeś średnika w trybie batch.

Rozwiązanie: Zawsze stawiaj ; na końcu zapytania, jeśli używasz --query.

Co dalej?

Teraz umiesz łączyć się z ClickHouse na każdy sposób, tworzyć tabele, wstawiać dane i robić zapytania. W następnym artykule omówimy zaawansowaną analitykę: funkcje okienne, tablice, agregacje i widoki zmaterializowane.

Wszystkie przykłady przetestowane na ClickHouse 24.8. Jeśli coś nie działa — najpierw sprawdź wersję: SELECT version(); To ratowało mnie setki razy.


Poprzedni:
Następny: ClickHouse: pełny przewodnik po typach danych dla analityki zakładów (na czym się sparzyłem)

— Editorial Team

Advertisement 728x90

Czytaj dalej