← Blog
Observability17 min czytania

HyperDX — przeszukiwanie logów i traces na ClickHouse

HyperDX to interfejs do logów, metryk, traces i nagrań sesji położony na ClickHouse — coś w rodzaju Kibany dla tej bazy. Pokazujemy architekturę, schemat tabel, konfigurację produkcyjną i pułapki, które łatwo przeoczyć.

Rachunek za observability ma nieprzyjemną własność: rośnie liniowo z ilością danych, a ilość danych rośnie sama. Gdy faktura zaczyna kłuć, zespoły robią jedyną rzecz, jaką mogą zrobić szybko — obcinają sampling, skracają retencję, przestają zbierać metryki, których „i tak nikt nie oglądał". Trzy miesiące później ktoś debuguje incydent i okazuje się, że akurat tych danych nie ma.

HyperDX podchodzi do tego z innej strony: zamiast własnego magazynu danych z własnym cennikiem kładzie interfejs na ClickHouse. Twórcy opisują to zwięźle — wyobraź sobie Kibanę dla ClickHouse. Logi, metryki, traces, nagrania sesji i błędy trafiają do bazy, którą już umiesz utrzymywać i której koszt znasz, a HyperDX daje do nich wyszukiwanie, korelację i dashboardy.

Jedna rzecz do wyjaśnienia na wstępie

Jeśli szukasz dokumentacji HyperDX-a, nie znajdziesz jej pod adresem projektu. README kieruje na clickhouse.com/docs/use-cases/observability/clickstack i mówi wprost, że HyperDX jest dziś podstawowym komponentem ClickStacka — stosu observability firmy ClickHouse. To nie jest kosmetyczna zmiana szyldu. Widać ją w kodzie: plik .env w repozytorium trzyma obok siebie stare i nowe nazwy obrazów, od hyperdx/hyperdx-all-in-one po clickhouse/clickstack-all-in-one, a nagłówek licencji MIT nosi datę i nazwę pierwotnej firmy — DeploySentinel, Inc.

Praktyczny wniosek jest taki: przy stawianiu instancji sprawdź, na jaki obraz i jaki rejestr wskazuje twój plik compose, bo w obiegu są równolegle docker.hyperdx.io i docker.clickhouse.com. Wpisy na blogach z zeszłego roku mogą wskazywać na nazwy, które już się nie rozwijają.

Licencja to MIT — bez klauzul dodatkowych, bez modelu open core z okrojoną wersją społecznościową. Repozytorium: hyperdxio/hyperdx.

Stan projektu

Projekt jest wyjątkowo żywy. 9878 gwiazdek, 464 forki, start we wrześniu 2023. Ostatni commit w gałęzi main pochodzi z 9 września 2026 — dzień przed powstaniem tego wpisu. Wydania idą w rytmie monorepo z osobnymi tagami per paczka: @hyperdx/app@2.38.0, @hyperdx/api@2.38.0, @hyperdx/otel-collector@2.38.0 i @hyperdx/cli@0.6.3, wszystkie z 4 września 2026. Commity podpisuje kilka różnych osób i noszą identyfikatory zadań w formacie [HDX-XXXX], co jest sygnaturą pracy etatowej, nie hobby po godzinach.

Otwartych zgłoszeń jest 189. Przy tej skali i tempie to normalne, ale nie zakładaj, że twoje trafi na wierzch — jest Discord i są dyskusje na GitHubie, i tam odpowiedź przychodzi szybciej.

Architektura — cztery usługi, nie jedna

Nazwa „interfejs na ClickHouse" sugeruje jeden kontener. W praktyce pełny stos to cztery elementy i warto to wiedzieć przed planowaniem wdrożenia:

  • HyperDX UI — frontend w Next.js.
  • HyperDX API — backend w Node.js na Express, który buduje zapytania i trzyma logikę biznesową.
  • Kolektor OpenTelemetry — przyjmuje i przetwarza telemetrię.
  • ClickHouse — właściwy magazyn logów, metryk i traces.
  • MongoDB — metadane: użytkownicy, dashboardy, alerty i zapisane wyszukiwania.

Ta ostatnia pozycja zaskakuje najczęściej. HyperDX nie trzyma konfiguracji w ClickHouse — potrzebuje osobnej instancji MongoDB, i dokumentacja wdrożeniowa mówi to wprost także w wariancie „przynieś własny ClickHouse": będziesz potrzebować osobnej instancji MongoDB, by utrwalić użytkowników, dashboardy i wyszukiwania. Jeśli twoja organizacja ma politykę „nie wprowadzamy nowych silników baz danych", to jest właśnie ten moment, w którym trzeba ją skonfrontować z rzeczywistością.

Przepływ danych jest prosty: aplikacja wysyła telemetrię przez OpenTelemetry do kolektora, kolektor przetwarza ją i przekazuje do ClickHouse, użytkownik klika w UI, API odpytuje ClickHouse, a konfiguracja siedzi w MongoDB. Wielodostępność jest zbudowana wokół pojęcia zespołu — niemal każda encja należy do jakiegoś team i wszystkie zapytania są filtrowane po tym kontekście.

Backend to w rzeczywistości kilka osobnych aplikacji z własnym routingiem, uwierzytelnianiem i limitami: wewnętrzne API dla frontendu (sesje przez Passport.js), publiczne API v2 uwierzytelniane osobistym kluczem dostępu z limitem 100 żądań na minutę, serwer MCP z limitem 600 żądań na minutę oraz serwer OpAMP, który rozdaje konfiguracje nadzorowanym kolektorom OpenTelemetry.

Jest jeszcze piąty element, który nie jest usługą, ale potrafi zepsuć wdrożenie: zadania w tle. Ewaluacja alertów, provisioning dashboardów i zbieranie statystyk użycia działają poza cyklem żądanie–odpowiedź, sterowane cronem. W trybie deweloperskim uruchamia je yarn dev-task i alerty są sprawdzane co minutę, ale — cytując dokumentację architektury — na produkcji są uruchamiane zewnętrznie. Jeśli postawisz sam kontener aplikacji i będziesz się dziwić, dlaczego alerty nigdy nie strzelają, to jest pierwsze miejsce do sprawdzenia.

Uruchomienie

Najkrótsza droga to obraz all-in-one, który podnosi cały stos w jednym kontenerze:

docker run -p 8080:8080 -p 4317:4317 -p 4318:4318 \
  docker.hyperdx.io/hyperdx/hyperdx-all-in-one

Interfejs czeka pod http://localhost:8080. Autorzy podają wymagania minimalne do testów: 4 GB RAM i 2 rdzenie. Trzy porty w powyższym poleceniu to kolejno UI, odbiornik OTLP przez gRPC i odbiornik OTLP przez HTTP; README dodaje, że przy serwerze za zaporą trzeba przepuścić 8080, 8000 i 4318 — odpowiednio interfejs, API i kolektor.

Jeśli ClickHouse już masz, sensowniejszy jest wariant z własną bazą — wtedy potrzebujesz tylko kontenera HyperDX-a i MongoDB:

docker run -e MONGO_URI=mongodb://TWOJE_MONGO_URI -p 8080:8080 \
  docker.hyperdx.io/hyperdx/hyperdx:2-beta

Połączenie z ClickHouse konfiguruje się już z interfejsu. Jest też trzeci tryb, hyperdx-local, przeznaczony do stawiania obok lokalnego środowiska deweloperskiego — bez uwierzytelniania i bez utrwalania konfiguracji. To wariant do debugowania własnej aplikacji na laptopie, nie do niczego innego.

Zwróć uwagę na tag 2-beta w oficjalnych przykładach wdrożeniowych. Wersja druga wciąż jest oznaczana jako beta, mimo że numery paczek doszły do 2.38.0 — warto to wiedzieć, zanim ktoś zapyta „czy to jest stabilne".

Co trzeba ustawić, zanim to pojedzie na produkcję

Ta sekcja jest ważniejsza niż instalacja, bo składa się z rzeczy, których domyślny plik compose za ciebie nie zrobi.

Sekret sesji. Dokumentacja wdrożeniowa powtarza to przy każdym z wariantów: przed wyjściem na produkcję ustaw EXPRESS_SESSION_SECRET na losowy ciąg. Sprawdziłem plik docker-compose.yml z repozytorium — tej zmiennej po prostu w nim nie ma. Czyli jeśli sklonujesz repo, wykonasz docker compose up -d i wystawisz to na świat, pojedziesz bez jawnie ustawionego sekretu sesji. To nie jest błąd w compose, to podział pracy: compose służy do uruchomienia stosu, a produkcja jest na tobie. Trzeba tylko o tym wiedzieć.

Porty i zapora. Dokumentacja zawiera ostrzeżenie, które warto przeczytać dwa razy, bo dotyczy nie tylko HyperDX-a: wystawienie jakiegokolwiek portu w Docker Compose sprawi, że będzie publicznie dostępny, nawet przy zaporze opartej na iptables, takiej jak ufw. Sam plik compose trzyma porty MongoDB i ClickHouse zakomentowane, z komentarzem ostrzegającym, że ich odkomentowanie może otworzyć nieuprawniony dostęp. Nie odkomentowuj ich „na chwilę, do debugowania" na maszynie z publicznym adresem — ufw deny tego nie zatrzyma.

Webhooki alertów. Jest zmienna WEBHOOK_HOSTNAME_ALLOWLIST, przyjmująca listę nazw hostów albo dosłownych adresów IP, które mogą otrzymywać webhooki; wpisy domenowe obejmują też subdomeny, a wpisy IP są dopasowywane dokładnie. Domyślnie jest pusta. Warto ją wypełnić: alert z webhookiem to mechanizm, w którym użytkownik podaje adres, a serwer go odwiedza — czyli klasyczna powierzchnia SSRF, jeśli zostawi się ją otwartą.

Statystyki użycia. HyperDX zbiera anonimizowane dane o użyciu w instalacjach open source i w pliku compose ma to zapisane jako USAGE_STATS_ENABLED: ${USAGE_STATS_ENABLED:-true} — czyli włączone, dopóki nie ustawisz inaczej. Autorzy piszą o tym otwarcie w README i podają, jak zrezygnować: USAGE_STATS_ENABLED=false. Uczciwe podejście, ale jeśli twoja instancja stoi w środowisku, które nie ma prawa wysyłać niczego na zewnątrz, tę zmienną ustawiasz przed pierwszym startem, nie po audycie.

Wersje obrazów w compose. Warto na nie zerknąć krytycznie. ClickHouse jest przypięty do 26.5-alpine, a MongoDB do mongo:5.0.32-focal. Linia 5.0 Mongo ma już swoje lata i jej okres wsparcia u producenta się zakończył, więc jeśli obowiązują was wymogi typu „tylko wspierane wersje silników", to jest pozycja do podmiany na własną, nowszą instancję przez MONGO_URI — tym bardziej że w wariancie z własnym ClickHouse i tak dostarczasz Mongo samodzielnie.

Jak to wygląda w ClickHouse

Najciekawsza część tego projektu nie jest widoczna w interfejsie — to schemat tabel. Repozytorium trzyma go jawnie, w migracjach SQL w katalogu docker/otel-collector/schema/seed, i można go przeczytać, zanim cokolwiek uruchomisz. Tabela logów wygląda tak (fragment):

CREATE TABLE IF NOT EXISTS ${DATABASE}.otel_logs
(
  `Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
  `TraceId` String CODEC(ZSTD(1)),
  `SeverityText` LowCardinality(String) CODEC(ZSTD(1)),
  `ServiceName` LowCardinality(String) CODEC(ZSTD(1)),
  `Body` String CODEC(ZSTD(1)),
  `ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
  `LogAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.pod.name` LowCardinality(String)
      MATERIALIZED ResourceAttributes['k8s.pod.name'] CODEC(ZSTD(1)),
  INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE text(tokenizer = 'array'),
  INDEX idx_lower_body lower(Body) TYPE text(tokenizer = 'splitByNonAlpha')
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp)
TTL toDateTime(Timestamp) + ${LOGS_TTL}
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1;

W tym jednym fragmencie jest kilka decyzji wartych zrozumienia, bo każda z nich odpowiada za konkretną cechę działającego systemu.

Klucz sortowania to (toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp). Zaokrąglenie czasu do pięciu minut na pierwszej pozycji sprawia, że wszystkie logi z jednej usługi w danym okienku leżą obok siebie na dysku. To dlatego zapytanie „pokaż mi błędy usługi X z ostatniej godziny" nie musi czytać całej doby.

Kolumny materializowane z przedrostkiem __hdx_materialized_ wyciągają najczęściej używane atrybuty Kubernetesa (nazwa poda, kontenera, namespace'u, deploymentu, klastra, węzła) z mapy ResourceAttributes do osobnych kolumn typu LowCardinality(String). Filtrowanie po nazwie namespace'u przestaje wtedy być odczytem mapy, a staje się odczytem jednej kolumny ze słownikiem.

Indeksy tekstowe z tokenizerami to najświeższa część. Klucze i pary atrybutów są indeksowane tokenizerem array, a treść logu — po zamianie na małe litery — tokenizerem splitByNonAlpha. To właśnie sprawia, że pełnotekstowe szukanie po treści logu nie degeneruje się do pełnego skanu.

TTL z ttl_only_drop_parts = 1 oznacza, że wygasanie danych usuwa całe partycje, a nie kasuje wiersze pojedynczo. W połączeniu z partycjonowaniem po dniu daje to retencję, która jest tania — usunięcie dnia to usunięcie plików, nie przepisanie tabeli.

Obok tabel surowych są jeszcze rollupy. Tabela otel_logs_kv_rollup_15m na silniku SummingMergeTree zbiera w piętnastominutowych okienkach liczniki wystąpień par klucz–wartość, zasilana widokiem materializowanym, który robi UNION ALL po kolumnach natywnych i po mapach atrybutów. To odpowiedź na pytanie, jak interfejs potrafi natychmiast pokazać, że w danym przedziale masz 12 różnych wartości ServiceName i ile razy każda wystąpiła — nie liczy tego z surowych danych, tylko czyta gotowe sumy.

Warto przy tym pamiętać o obietnicy z README: HyperDX jest agnostyczny wobec schematu i działa na istniejącym schemacie ClickHouse. Powyższe DDL to schemat zasiewany przez własny kolektor projektu, a nie warunek działania. W repozytorium leżą też warianty z sufiksem _compat, a plik compose ustawia HYPERDX_OTEL_EXPORTER_CREATE_LEGACY_SCHEMA: 'true' z komentarzem TODO: use new schema — czyli domyślnie dostajesz jeszcze starszy wariant schematu. Jeśli zaczynasz od zera, to jest miejsce, w które warto zajrzeć przed pierwszym zasiewem, a nie po zebraniu terabajta.

Szukanie i praca z danymi

Składnia zapytań jest dwupoziomowa. Na wierzchu jest szukanie pełnotekstowe i proste filtry po właściwościach w stylu level:err — bez SQL-a. Pod spodem SQL jest dostępny, gdy jest potrzebny. To rozsądny kompromis: dyżurny o trzeciej nad ranem nie powinien musieć pisać zapytania z JOIN-em, ale osoba budująca dashboard o wysokiej kardynalności powinna móc.

Z rzeczy, które w praktyce robią różnicę: korelacja logów, metryk, traces i nagrań sesji w jednym miejscu (żeby nie skakać między czterema narzędziami przy jednym incydencie), live tail dla logów i traces, oraz analiza anomalii przez różnice zdarzeń. Do tego alerty konfigurowane z interfejsu i monitorowanie wydajności od żądań HTTP do zapytań do bazy.

Instrumentacja idzie przez OpenTelemetry, więc lista wspieranych języków to po prostu lista OTel-a: JavaScript, Python, Java, Go, Ruby, PHP, .NET, Elixir, Rust i Kubernetes. Są też własne SDK dla przeglądarki, Node.js i Pythona. Po uruchomieniu instancji wskazujesz swojemu SDK adres kolektora — domyślnie http://localhost:4318 — i to wszystko. Dla projektu w PHP oznacza to, że wystarczy standardowe rozszerzenie OpenTelemetry; nie potrzebujesz nic specyficznego dla HyperDX-a.

Terminal i agenci

Jest osobne narzędzie CLI, @hyperdx/cli, i jest lepsze, niż sugeruje nazwa. Wymaga Node.js w wersji co najmniej 22.16, instaluje binarkę hdx i — co istotne — uruchamia dokładnie ten sam potok zapytań co aplikacja webowa, więc wyniki w terminalu zgadzają się z tym, co widać w przeglądarce.

npm install -g @hyperdx/cli
hdx auth login
hdx tui

# wykres liczby błędów per usługa z ostatnich trzech godzin
hdx chart -s Logs --where 'SeverityText:error' --group-by ServiceName --since 3h

# surowy SQL do ClickHouse przez proxy HyperDX-a
hdx query --connection-id "$CONN" --sql "SELECT count() FROM default.otel_logs"

Interaktywne TUI ma skróty w stylu vima, wodospad spanów z powiązanymi logami i odnośniki otwierające widok w przeglądarce. Wykresy renderują się jako ANSI — liniowe, słupkowe, kołowe, tabele. Jest też funkcja, która wyróżnia to narzędzie na tle innych klientów terminalowych: klastrowanie wyników w wzorce logów algorytmem Drain, czyli automatyczne sprowadzenie dziesiątek tysięcy linii do garści szablonów. Przy przeglądaniu skutków awarii to często jedyny sposób, żeby zobaczyć, co się właściwie stało, bez czytania wszystkiego.

Osobno zadbano o współpracę z agentami: --json na komendach odkrywających, strumieniowanie NDJSON, udokumentowane kody wyjścia i automatyczne usuwanie kolorów przy przekierowaniu wyjścia do potoku.

Serwer MCP działa jako bezstanowy endpoint Streamable HTTP pod ścieżką /api/mcp, uwierzytelniany osobistym kluczem dostępu jako token Bearer. Narzędzia obejmują odpytywanie danych, alerty, dashboardy i zapisane wyszukiwania. Podłączenie w Claude Code to jedna komenda:

claude mcp add --transport http clickstack https://twoj-hyperdx/api/mcp \
  --header "Authorization: Bearer TWOJ_KLUCZ"

Dokumentacja podaje też konfiguracje dla Codex CLI, OpenCode i Cursora, a klucz znajdziesz w interfejsie pod Team Settings → API keys → Personal API access key. Jedno zastrzeżenie jest wpisane w dokumentację wprost: serwer MCP nie działa w HyperDX v1 — tylko w samodzielnie hostowanej wersji drugiej.

Ciekawostka o współpracy, która mówi coś o dzisiejszym open source

Ten projekt zmienił zasady przyjmowania kodu i uzasadnienie jest na tyle świeże, że warto je odnotować. Zgłoszenia błędów i dyskusje są otwarte dla wszystkich, ale pull requesty nie: zanim pierwszy PR trafi do kolejki przeglądu, ktoś z opiekunów musi za autora poręczyć. Otwarty PR bez poręczenia nie zostaje zamknięty — bot dodaje etykietę needs-vouch i komentarz odsyłający do instrukcji. Poręczenia zdobywa się, otwierając zgłoszenie z przedstawieniem się i opisem tego, co chce się zrobić.

Uzasadnienie z pliku CONTRIBUTING.md jest bez owijania: narzędzia AI sprawiły, że otwarcie wiarygodnie wyglądającego PR-a bez żadnego zrozumienia tematu jest tanie, a przeglądanie takich zgłoszeń wypycha z kolejki wkład, na który opiekunowie chcą poświęcać czas. Do tego doszedł limit równoległych zgłoszeń: osoba z zewnątrz może mieć otwarty jeden PR, a po poręczeniu trzy. Mechanizm to narzędzie Vouch, a lista poręczonych leży w repozytorium jako .github/VOUCHED.td.

Nie oceniam tu, czy to dobry kierunek dla open source. Odnotowuję, bo jeśli planujesz kontrybuować — na przykład dosłać wsparcie dla schematu, którego potrzebujesz — to kolejność działań jest inna niż zwykle: najpierw zgłoszenie z przedstawieniem się, potem kod.

Pułapki w jednym miejscu

  • Potrzebujesz MongoDB obok ClickHouse. Metadane, dashboardy i alerty nie leżą w ClickHouse. W wariancie z własną bazą Mongo dostarczasz samodzielnie.
  • EXPRESS_SESSION_SECRET nie występuje w dostarczonym pliku compose, a dokumentacja wymaga ustawienia go przed produkcją. Ustaw jawnie.
  • Zadania w tle na produkcji uruchamiane są zewnętrznie. Bez tego alerty nie będą ewaluowane, a dashboardy provisionowane.
  • Wystawienie portu w Docker Compose omija ufw. Ostrzeżenie jest w dokumentacji wdrożeniowej i dotyczy szczególnie zakomentowanych portów Mongo i ClickHouse.
  • WEBHOOK_HOSTNAME_ALLOWLIST jest domyślnie pusta. Webhooki alertów to powierzchnia SSRF — wypełnij listę.
  • USAGE_STATS_ENABLED ma domyślnie true. W środowisku odciętym od internetu ustaw false przed pierwszym startem.
  • Domyślnie zasiewany jest starszy schemat — HYPERDX_OTEL_EXPORTER_CREATE_LEGACY_SCHEMA: 'true', z komentarzem TODO w repozytorium. Zdecyduj przed zebraniem danych, nie po.
  • Oficjalne przykłady wdrożeniowe używają tagu 2-beta, mimo numerów paczek rzędu 2.38.0.
  • Nazwy obrazów są w trakcie migracji z hyperdx/* na clickhouse/clickstack-*. Sprawdź, co ciągnie twój compose.
  • MongoDB w compose to linia 5.0, bez aktualnego wsparcia producenta. Przy wymogach zgodności podstaw własną instancję.
  • Serwer MCP nie działa w HyperDX v1 — tylko w samodzielnie hostowanej wersji 2.
  • Pierwszy pull request wymaga poręczenia. Zacznij od zgłoszenia, nie od kodu.

Podsumowanie

HyperDX rozwiązuje problem, który w wielu zespołach jest realny i kosztowny: observability jest tak droga, że przestaje się zbierać dane, a bez danych nie ma z czego debugować. Kładąc interfejs na ClickHouse, przenosi koszt do miejsca, które można policzyć i zoptymalizować własnymi narzędziami — a jeśli ClickHouse już utrzymujesz, koszt krańcowy jest niewielki.

  • Dane w bazie, którą kontrolujesz — bez opłat za wolumen telemetrii i bez cudzego magazynu, na dowolnym klastrze ClickHouse.
  • Cztery rodzaje sygnałów w jednym widoku — logi, metryki, traces i nagrania sesji, korelowane bez przeskakiwania między narzędziami.
  • Schemat otwarty do przeczytania i przemyślenia — migracje SQL leżą w repozytorium, z materializowanymi kolumnami Kubernetesa, indeksami tekstowymi i rollupami na SummingMergeTree.
  • OpenTelemetry bez pośredników — instrumentujesz standardem, nie biblioteką jednego dostawcy, więc migracja w drugą stronę też jest możliwa.
  • Poważny klient terminalowy — TUI ze skrótami vima, wodospady spanów, wykresy ANSI i klastrowanie logów algorytmem Drain.
  • Realne tempo rozwoju — wydania co kilka tygodni, commity codziennie, praca prowadzona zawodowo.

HyperDX jest wydany na licencji MIT, z prawami autorskimi po stronie DeploySentinel, Inc. To licencja bez klauzul dodatkowych — możesz go używać komercyjnie, modyfikować i wdrażać u klientów bez obowiązku udostępniania zmian. Warto natomiast rozumieć kontekst: projekt jest dziś podstawowym komponentem ClickStacka, stosu observability firmy ClickHouse, a jego dokumentacja mieszka w dokumentacji tej firmy. Kod pozostaje w pełni otwarty i jeden podmiot nie może tego wycofać dla wydanych już wersji — ale kierunek rozwoju wyznacza dziś ta firma, i przy wyborze narzędzia na kilka lat to jest informacja, którą warto mieć świadomie, a nie odkryć później.