Aleksey Lizunov, kierownik działu centrum kompetencji kanałów zdalnych obsługi dyrekcji technologii informacyjnych MKB

Jako alternatywę dla stosu ELK (ElasticSearch, Logstash, Kibana) prowadzimy badania nad wykorzystaniem bazy danych ClickHouse jako magazynu danych dla logów.
W tym artykule chcielibyśmy opowiedzieć o naszym doświadczeniu z wykorzystaniem bazy danych ClickHouse oraz o wstępnych wynikach z pilotażowej eksploatacji. Warto od razu zauważyć, że wyniki są imponujące.

Następnie opiszemy szczegółowo, jak zorganizowany jest nasz system i z jakich komponentów się składa. Ale na początku chcielibyśmy nieco opowiedzieć o tej bazie danych w ogóle i dlaczego warto na nią zwrócić uwagę. Baza danych ClickHouse to wydajna analityczna baza danych kolumnowych od Yandex. Jest wykorzystywana w usługach Yandex, pierwotnie służyła jako główne magazyn danych dla Yandex.Metrica. System jest open-source, darmowy. Z perspektywy programisty, zawsze byłem ciekawy, jak to zostało zrealizowane, biorąc pod uwagę fantastycznie duże dane. A sam interfejs użytkownika Metriki jest bardzo elastyczny i działa szybko. Przy pierwszym zapoznaniu się z tą bazą danych wrażenie: „No wreszcie! Zrobione «dla ludzi»! Od procesu instalacji po wysyłanie zapytań.”
Ta baza danych ma bardzo niski próg wejścia. Nawet średnio wykwalifikowany programista może w kilka minut zainstalować tę bazę danych i zacząć korzystać. Wszystko działa bez zarzutu. Nawet osoby, które słabo znają się na Linuxie, dość szybko mogą poradzić sobie z instalacją i wykonywać podstawowe operacje. Jeśli wcześniej, na hasło Big Data, Hadoop, Google BigTable, HDFS, przeciętny programista miał wizje jakichś terabajtów, petabajtów, że konfiguracją i rozwojem tych systemów zajmują się jacyś superludzie, to dzięki pojawieniu się bazy danych ClickHouse otrzymaliśmy prostą, zrozumiałą, narzędzie, za pomocą którego można rozwiązywać wcześniej niedostępne problemy. Wystarczy zwykła maszyna i pięć minut na instalację. To jakby otrzymaliśmy bazę danych podobną do MySql, ale do przechowywania miliardów rekordów! Jakiś superarchiwizator z językiem SQL. Jakby ludzie otrzymali broń obcych.
O naszym systemie zbierania logów
Do zbierania informacji używane są pliki logów IIS aplikacji internetowych w standardowym formacie (obecnie zajmujemy się również parsowaniem logów aplikacji, ale głównym celem na etapie pilotażowym jest zbieranie logów IIS).
Nie udało nam się całkowicie zrezygnować z ekosystemu ELK z różnych powodów, więc nadal korzystamy z komponentów LogStash i Filebeat, które dobrze się sprawdzają i działają niezawodnie oraz przewidywalnie.
Ogólny schemat logowania przedstawiony jest na poniższym rysunku:

Cechą charakterystyczną zapisywania danych w bazie danych ClickHouse jest rzadkie (co sekundę) wstawianie rekordów dużymi paczkami. To, jak się wydaje, jest najbardziej "problematyczną" częścią, z którą można się spotkać przy pierwszym doświadczeniu z bazą danych ClickHouse: schemat jest nieco bardziej skomplikowany.
Zdecydowanie pomógł tutaj plugin do LogStash, który bezpośrednio wstawia dane do ClickHouse. Ten komponent jest uruchamiany na tym samym serwerze, co sama baza danych. Ogólnie rzecz biorąc, nie jest to zalecane, ale z praktycznego punktu widzenia, aby nie rozmnażać oddzielnych serwerów, pozostaje uruchomiony na tym samym serwerze. Nie zauważyliśmy żadnych awarii ani konfliktów zasobów z bazą danych. Należy również zauważyć, że plugin ma mechanizm ponawiania w przypadku błędów. W przypadku błędów plugin zapisuje na dysku pakiet danych, które nie mogły zostać wstawione (format pliku jest wygodny: po poprawkach można łatwo wstawić poprawiony pakiet za pomocą clickhouse-client).
Pełna lista oprogramowania używanego w schemacie przedstawiona jest w tabeli:
Lista używanego oprogramowania
Nazwa
Opis
Link do dystrybucji
NGINX
Reverse-proxy do ograniczenia dostępu przez porty i organizacji autoryzacji
Obecnie nie jest wykorzystywane w schemacie
FileBeat
Przesyłanie plików logów.
(dystrybucja dla Windows 64bit).
LogStash
Kolektor logów.
Używany do zbierania logów z FileBeat, a także do zbierania logów z kolejki RabbitMQ (dla serwerów, które znajdują się w DMZ).
Logstash-output-clickhouse
Plugin Logstash do przesyłania logów do bazy danych ClickHouse w paczkach
/usr/share/logstash/bin/logstash-plugin install logstash-output-clickhouse
/usr/share/logstash/bin/logstash-plugin install logstash-filter-prune
/usr/share/logstash/bin/logstash-plugin install logstash-filter-multiline
ClickHouse
Magazyn logów
Uwaga. Od sierpnia 2018 w repozytorium Yandexu pojawiły się „normalne” pakiety rpm dla RHEL, więc można spróbować je wykorzystać. W momencie instalacji używaliśmy pakietów zbudowanych przez Altinity.
Grafana
Wizualizacja logów. Konfiguracja dashboardów
Redhat & Centos (64 Bit) – najnowsza wersja
ClickHouse datasource for Grafana 4.6+
Plugin do Grafana z źródłem danych ClickHouse
LogStash
Router logów z FileBeat do kolejki RabbitMQ.
Uwaga. Niestety, FileBeat nie ma bezpośredniego wyjścia do RabbitMQ, więc potrzebny jest pośredni element w postaci Logstash.
RabbitMQ
Kolejka wiadomości. To bufor rekordów logów w DMZ.
Erlang Runtime (Wymagane dla RabbitMQ)
Środowisko wykonawcze Erlang. Wymagane do pracy RabbitMQ.
Konfiguracja serwera z bazą danych ClickHouse przedstawiona jest w poniższej tabeli:
Nazwa
Wartość
Uwaga
Konfiguracja
HDD: 40GB
RAM: 8GB
Procesor: Core 2 2Ghz
Należy zwrócić uwagę na wskazówki dotyczące eksploatacji bazy danych ClickHouse ()
Oprogramowanie systemowe
OS: Red Hat Enterprise Linux Server (Maipo)
JRE (Java 8)
Jak widać, to zwykła stacja robocza.
Struktura tabeli do przechowywania logów wygląda następująco:
log_web.sql
CREATE TABLE log_web (
logdate Date,
logdatetime DateTime CODEC(Delta, LZ4HC),
fld_log_file_name LowCardinality( String ),
fld_server_name LowCardinality( String ),
fld_app_name LowCardinality( String ),
fld_app_module LowCardinality( String ),
fld_website_name LowCardinality( String ),
serverIP LowCardinality( String ),
method LowCardinality( String ),
uriStem String,
uriQuery String,
port UInt32,
username LowCardinality( String ),
clientIP String,
clientRealIP String,
userAgent String,
referer String,
response String,
subresponse String,
win32response String,
timetaken UInt64
, uriQuery__utm_medium String
, uriQuery__utm_source String
, uriQuery__utm_campaign String
, uriQuery__utm_term String
, uriQuery__utm_content String
, uriQuery__yclid String
, uriQuery__region String
) Engine = MergeTree()
PARTITION BY toYYYYMM(logdate)
ORDER BY (fld_app_name, fld_app_module, logdatetime)
SETTINGS index_granularity = 8192;Używamy wartości domyślnych dla partycjonowania (na miesiące) oraz granularności indeksu. Wszystkie pola w zasadzie odpowiadają zapisom logu IIS dla rejestracji żądań http. Osobno zaznaczymy, że są oddzielne pola do przechowywania etykiet utm (są parsowane na etapie wstawiania do tabeli z pola zapytania).
W tabeli dodano również kilka pól systemowych do przechowywania informacji o systemach, komponentach, serwerach. Opis tych pól znajduje się poniżej w tabeli. W jednej tabeli przechowujemy logi dla kilku systemów.
Nazwa
Opis
Przykład
fld_app_name
Nazwa aplikacji/systemu
Dopuszczalne wartości:
- site1.domain.com Zewnętrzny serwis 1
- site2.domain.com Zewnętrzny serwis 2
- internal-site1.domain.local Wewnętrzny serwis 1
site1.domain.com
fld_app_module
Moduł systemu
Dopuszczalne wartości:
- web — Witryna internetowa
- svc — Usługa sieciowa witryny
- intgr — Usługa sieciowa integracji
- bo — Panel administracyjny (BackOffice)
web
fld_website_name
Nazwa witryny w IIS
Na jednym serwerze może być wdrożonych kilka systemów lub nawet kilka egzemplarzy jednego modułu systemu.
web-main
fld_server_name
Nazwa serwera
web1.domain.com
fld_log_file_name
Ścieżka do pliku logu na serwerze
C:\inetpub\logs\LogFiles
W3SVC1u_ex190711.log
To umożliwia efektywne tworzenie wykresów w Grafana. Na przykład, przeglądanie zapytań z frontend konkretnego systemu. To przypomina licznik odwiedzin strony w Yandex.Metrica.
Oto statystyki dotyczące wykorzystania bazy danych w ciągu ostatnich dwóch miesięcy.
Liczba rekordów z podziałem na systemy i ich komponenty
SELECT
fld_app_name,
fld_app_module,
count(fld_app_name) AS rows_count
FROM log_web
GROUP BY
fld_app_name,
fld_app_module
WITH TOTALS
ORDER BY
fld_app_name ASC,
rows_count DESC
┌─fld_app_name─────┬─fld_app_module─┬─rows_count─┐
│ site1.domain.ru │ web │ 131441 │
│ site2.domain.ru │ web │ 1751081 │
│ site3.domain.ru │ web │ 106887543 │
│ site3.domain.ru │ svc │ 44908603 │
│ site3.domain.ru │ intgr │ 9813911 │
│ site4.domain.ru │ web │ 772095 │
│ site5.domain.ru │ web │ 17037221 │
│ site5.domain.ru │ intgr │ 838559 │
│ site5.domain.ru │ bo │ 7404 │
│ site6.domain.ru │ web │ 595877 │
│ site7.domain.ru │ web │ 27778858 │
└──────────────────┴────────────────┴────────────┘
Totals:
┌─fld_app_name─┬─fld_app_module─┬─rows_count─┐
│ │ │ 210522593 │
└──────────────┴────────────────┴────────────┘
11 rows in set. Elapsed: 4.874 sec. Processed 210.52 million rows, 421.67 MB (43.19 million rows/s., 86.51 MB/s.)Rozmiar danych na dysku
SELECT
formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
formatReadableSize(sum(data_compressed_bytes)) AS compressed,
sum(rows) AS total_rows
FROM system.parts
WHERE table = 'log_web'
┌─uncompressed─┬─compressed─┬─total_rows─┐
│ 54.50 GiB │ 4.86 GiB │ 211427094 │
└──────────────┴────────────┴────────────┘
1 rows in set. Elapsed: 0.035 sec.Stopień kompresji danych w kolumnach
WYBIERZ
nazwa,
formatReadableSize(data_uncompressed_bytes) AS niekompresowane,
formatReadableSize(data_compressed_bytes) AS skompresowane,
data_uncompressed_bytes / data_compressed_bytes AS współczynnik_kompresji
Z system.columns
GDZIE tabela = 'log_web'
┌─nazwa─────────────────────┬─niekompresowane─┬─skompresowane─┬─────współczynnik_kompresji─┐
│ logdata │ 401.53 MiB │ 1.80 MiB │ 223.16665968777315 │
│ logdatetime │ 803.06 MiB │ 35.91 MiB │ 22.363966401202305 │
│ fld_log_file_name │ 220.66 MiB │ 2.60 MiB │ 84.99905736932571 │
│ fld_server_name │ 201.54 MiB │ 50.63 MiB │ 3.980924816977078 │
│ fld_app_name │ 201.17 MiB │ 969.17 KiB │ 212.55518183686877 │
│ fld_app_module │ 201.17 MiB │ 968.60 KiB │ 212.67805817411906 │
│ fld_website_name │ 201.54 MiB │ 1.24 MiB │ 162.7204926761546 │
│ serverIP │ 201.54 MiB │ 50.25 MiB │ 4.010824061219731 │
│ metoda │ 201.53 MiB │ 43.64 MiB │ 4.617721053304486 │
│ uriStem │ 5.13 GiB │ 832.51 MiB │ 6.311522291936919 │
│ uriQuery │ 2.58 GiB │ 501.06 MiB │ 5.269731450124478 │
│ port │ 803.06 MiB │ 3.98 MiB │ 201.91673864241824 │
│ username │ 318.08 MiB │ 26.93 MiB │ 11.812513794583598 │
│ clientIP │ 2.35 GiB │ 82.59 MiB │ 29.132328640073343 │
│ clientRealIP │ 2.49 GiB │ 465.05 MiB │ 5.478382297052563 │
│ userAgent │ 18.34 GiB │ 764.08 MiB │ 24.57905114484208 │
│ referer │ 14.71 GiB │ 1.37 GiB │ 10.736792723669906 │
│ odpowiedź │ 803.06 MiB │ 83.81 MiB │ 9.582334090987247 │
│ subodpowiedź │ 399.87 MiB │ 1.83 MiB │ 218.4831068635027 │
│ win32odpowiedź │ 407.86 MiB │ 7.41 MiB │ 55.050315514606815 │
│ czas_reakcji │ 1.57 GiB │ 402.06 MiB │ 3.9947395692010637 │
│ uriQuery__utm_medium │ 208.17 MiB │ 12.29 MiB │ 16.936148912472955 │
│ uriQuery__utm_source │ 215.18 MiB │ 13.00 MiB │ 16.548367623199912 │
│ uriQuery__utm_campaign │ 381.46 MiB │ 37.94 MiB │ 10.055156353418509 │
│ uriQuery__utm_term │ 231.82 MiB │ 10.78 MiB │ 21.502540454070672 │
│ uriQuery__utm_content │ 441.34 MiB │ 87.60 MiB │ 5.038260760449327 │
│ uriQuery__yclid │ 216.88 MiB │ 16.58 MiB │ 13.07721335008116 │
│ uriQuery__region │ 204.35 MiB │ 9.49 MiB │ 21.52661903446796 │
└──────────────────────────┴────────────────┴──────────────┴────────────────────┘
28 wierszy w zbiorze. Czas trwania: 0.005 sekundy.Opis używanych komponentów
FileBeat. Przesyłanie plików dzienników
Ten komponent monitoruje zmiany w plikach dzienników na dysku i przesyła informacje do LogStash. Instalowany jest na wszystkich serwerach, na których zapisują się pliki dzienników (zwykle IIS). Działa w trybie tail (tj. przesyła tylko dodane wpisy do pliku). Można go także osobno skonfigurować do przesyłania całych plików. To wygodne, gdy trzeba załadować dane z poprzednich miesięcy. Wystarczy umieścić plik z dziennikiem w folderze, a on sam go całkowicie odczyta.
Po zatrzymaniu usługi dane przestają być przesyłane do magazynu.
Przykład konfiguracji wygląda następująco:
filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- C:/inetpub/logs/LogFiles/W3SVC1/*.log
exclude_files: ['.gz$','.zip$']
tail_files: true
ignore_older: 24h
fields:
fld_server_name: "site1.domain.pl"
fld_app_name: "site1.domain.pl"
fld_app_module: "web"
fld_website_name: "web-main"
- type: log
enabled: true
paths:
- C:/inetpub/logs/LogFiles/__Import/access_log-*
exclude_files: ['.gz$','.zip$']
tail_files: false
fields:
fld_server_name: "site2.domain.pl"
fld_app_name: "site2.domain.pl"
fld_app_module: "web"
fld_website_name: "web-main"
fld_logformat: "logformat__apache"
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
reload.period: 2s
output.logstash:
hosts: ["log.domain.com:5044"]
ssl.enabled: true
ssl.certificate_authorities: ["C:/filebeat/certs/ca.pem", "C:/filebeat/certs/ca-issuing.pem"]
ssl.certificate: "C:/filebeat/certs/site1.domain.pl.cer"
ssl.key: "C:/filebeat/certs/site1.domain.pl.key"
#================================ Processors =====================================
processors:
- add_host_metadata: ~
- add_cloud_metadata: ~LogStash. Zbieracz logów
Ten komponent służy do odbierania wpisów logów z FileBeat (lub przez kolejkę RabbitMQ), parsowania i zbiorczego wstawiania do bazy danych ClickHouse.
Do wstawiania do ClickHouse używany jest wtyczka Logstash-output-clickhouse. Wtyczka Logstash ma mechanizm powtarzania zapytań, ale przy standardowym zatrzymaniu, lepiej jest zatrzymać samą usługę. Podczas zatrzymania będą gromadzić się wiadomości w kolejce RabbitMQ, dlatego jeśli zatrzymanie trwa długo, lepiej jest zatrzymać Filebeat'y na serwerach. W schemacie, w którym nie używa się RabbitMQ (w lokalnej sieci Filebeat bezpośrednio wysyła logi do Logstash), Filebeat'y działają w pełni akceptowalnie i bezpiecznie, dlatego dla nich niedostępność outputu przechodzi bez skutków.
Przykład konfiguracji wygląda następująco:
log_web__filebeat_clickhouse.conf
input {
beats {
port => 5044
type => 'iis'
ssl => true
ssl_certificate_authorities => ["/etc/logstash/certs/ca.cer", "/etc/logstash/certs/ca-issuing.cer"]
ssl_certificate => "/etc/logstash/certs/server.cer"
ssl_key => "/etc/logstash/certs/server-pkcs8.key"
ssl_verify_mode => "peer"
add_field => {
"fld_server_name" => "%{[fields][fld_server_name]}"
"fld_app_name" => "%{[fields][fld_app_name]}"
"fld_app_module" => "%{[fields][fld_app_module]}"
"fld_website_name" => "%{[fields][fld_website_name]}"
"fld_log_file_name" => "%{source}"
"fld_logformat" => "%{[fields][fld_logformat]}"
}
}
rabbitmq {
host => "queue.domain.com"
port => 5671
user => "q-reader"
password => "password"
queue => "web_log"
heartbeat => 30
durable => true
ssl => true
#ssl_certificate_path => "/etc/logstash/certs/server.p12"
#ssl_certificate_password => "password"
add_field => {
"fld_server_name" => "%{[fields][fld_server_name]}"
"fld_app_name" => "%{[fields][fld_app_name]}"
"fld_app_module" => "%{[fields][fld_app_module]}"
"fld_website_name" => "%{[fields][fld_website_name]}"
"fld_log_file_name" => "%{source}"
"fld_logformat" => "%{[fields][fld_logformat]}"
}
}
}
filter {
if [message] =~ "^#" {
drop {}
}
if [fld_logformat] == "logformat__iis_with_xrealip" {
grok {
match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken} %{NOTSPACE:xrealIP} %{NOTSPACE:xforwarderfor}"]
}
} else {
grok {
match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken}"]
}
}
date {
match => [ "log_timestamp", "YYYY-MM-dd HH:mm:ss" ]
timezone => "Etc/UTC"
remove_field => [ "log_timestamp", "@timestamp" ]
target => [ "log_timestamp2" ]
}
ruby {
code => "tstamp = event.get('log_timestamp2').to_i
event.set('logdatetime', Time.at(tstamp).strftime('%Y-%m-%d %H:%M:%S'))
event.set('logdate', Time.at(tstamp).strftime('%Y-%m-%d'))"
}
if [bytesSent] {
ruby {
code => "event['kilobytesSent'] = event['bytesSent'].to_i / 1024.0"
}
}
if [bytesReceived] {
ruby {
code => "event['kilobytesReceived'] = event['bytesReceived'].to_i / 1024.0"
}
}
ruby {
code => "event.set('clientRealIP', event.get('clientIP'))"
}
if [xrealIP] {
ruby {
code => "event.set('clientRealIP', event.get('xrealIP'))"
}
}
if [xforwarderfor] {
ruby {
code => "event.set('clientRealIP', event.get('xforwarderfor'))"
}
}
mutate {
convert => ["bytesSent", "integer"]
convert => ["bytesReceived", "integer"]
convert => ["timetaken", "integer"]
convert => ["port", "integer"]
add_field => {
"clientHostname" => "%{clientIP}"
}
}
useragent {
source => "useragent"
prefix => "browser"
}
kv {
source => "uriQuery"
prefix => "uriQuery__"
allow_duplicate_values => false
field_split => "&"
include_keys => [ "utm_medium", "utm_source", "utm_campaign", "utm_term", "utm_content", "yclid", "region" ]
}
mutate {
join => { "uriQuery__utm_source" => "," }
join => { "uriQuery__utm_medium" => "," }
join => { "uriQuery__utm_campaign" => "," }
join => { "uriQuery__utm_term" => "," }
join => { "uriQuery__utm_content" => "," }
join => { "uriQuery__yclid" => "," }
join => { "uriQuery__region" => "," }
}
}
output {
#stdout {codec => rubydebug}
clickhouse {
headers => ["Authorization", "Basic abcdsfks..."]
http_hosts => ["http://127.0.0.1:8123"]
save_dir => "/etc/logstash/tmp"
table => "log_web"
request_tolerance => 1
flush_size => 10000
idle_flush_time => 1
mutations => {
"fld_log_file_name" => "fld_log_file_name"
"fld_server_name" => "fld_server_name"
"fld_app_name" => "fld_app_name"
"fld_app_module" => "fld_app_module"
"fld_website_name" => "fld_website_name"
"logdatetime" => "logdatetime"
"logdate" => "logdate"
"serverIP" => "serverIP"
"method" => "method"
"uriStem" => "uriStem"
"uriQuery" => "uriQuery"
"port" => "port"
"username" => "username"
"clientIP" => "clientIP"
"clientRealIP" => "clientRealIP"
"userAgent" => "userAgent"
"referer" => "referer"
"response" => "response"
"subresponse" => "subresponse"
"win32response" => "win32response"
"timetaken" => "timetaken"
"uriQuery__utm_medium" => "uriQuery__utm_medium"
"uriQuery__utm_source" => "uriQuery__utm_source"
"uriQuery__utm_campaign" => "uriQuery__utm_campaign"
"uriQuery__utm_term" => "uriQuery__utm_term"
"uriQuery__utm_content" => "uriQuery__utm_content"
"uriQuery__yclid" => "uriQuery__yclid"
"uriQuery__region" => "uriQuery__region"
}
}
}pipelines.yml
# This file is where you define your pipelines. You can define multiple.
# For more information on multiple pipelines, see the documentation:
# https://www.elastic.co/guide/en/logstash/current/multiple-pipelines.html
- pipeline.id: log_web__filebeat_clickhouse
path.config: "/etc/logstash/log_web__filebeat_clickhouse.conf"ClickHouse. Przechowalnia logów
Logi z wszystkich systemów są zapisywane w jednej tabeli (patrz początek artykułu). Jest ona przeznaczona do przechowywania informacji o zapytaniach: wszystkie parametry są podobne dla różnych formatów, np. logi IIS, logi apache i nginx. Dla logów aplikacji, w których rejestrowane są np. błędy, komunikaty informacyjne, ostrzeżenia, przewidziana będzie osobna tabela z odpowiednią strukturą (aktualnie na etapie projektowania).
Przy projektowaniu tabeli bardzo ważne jest określenie klucza głównego (na podstawie którego będą sortowane dane przy przechowywaniu). Ma to wpływ na stopień kompresji danych i szybkość zapytań. W naszym przykładzie kluczem jest
ORDER BY (fld_app_name, fld_app_module, logdatetime)
Tzn. po nazwie systemu, nazwie komponentu systemu i dacie zdarzenia. Początkowo data zdarzenia była na pierwszym miejscu. Po przeniesieniu jej na ostatnie miejsce zapytania zaczęły działać około dwa razy szybciej. Zmiana klucza głównego wymaga ponownego stworzenia tabeli i ponownego załadowania danych, aby ClickHouse przearanżował dane na dysku. Jest to ciężka operacja, dlatego warto z wyprzedzeniem przemyśleć, co powinno wchodzić w klucz sortowania.
Należy również zaznaczyć, że w ostatnich wersjach pojawił się typ danych LowCardinality. Jego zastosowanie znacznie zmniejsza rozmiar skompresowanych danych dla tych pól, które mają niską kardynalność (mało wariantów).
Obecnie używana jest wersja 19.6 i planujemy spróbować zaktualizować ją do najnowszej. W nich pojawiły się takie wspaniałe funkcje jak Adaptive Granularity, Skipping indices i kodek DoubleDelta.
Domyślnie podczas instalacji w konfiguracji ustawiony jest poziom logowania trace. Logi są rotowane i archiwizowane, ale jednocześnie rozszerzają się do gigabajta. Jeśli nie ma takiej potrzeby, można ustawić poziom warning, wtedy rozmiar logu znacznie się zmniejsza. Ustawienia logowania określane są w pliku config.xml:
warningKilka przydatnych komend
Ponieważ oryginalne pakiety instalacyjne są kompilowane w systemie Debian, dla innych wersji Linux należy używać pakietów skompilowanych przez firmę Altinity.
Oto link do instrukcji z odnośnikami do ich repozytoriów: https://www.altinity.com/blog/2017/12/18/logstash-with-clickhouse
sudo yum search clickhouse-server
sudo yum install clickhouse-server.noarch
1. sprawdzenie statusu
sudo systemctl status clickhouse-server
2. zatrzymanie serwera
sudo systemctl stop clickhouse-server
3. uruchomienie serwera
sudo systemctl start clickhouse-server
Uruchamianie w trybie wielowierszowym (wykonanie po znaku ";")
clickhouse-client --multiline
clickhouse-client --multiline --host 127.0.0.1 --password pa55w0rd
clickhouse-client --multiline --host 127.0.0.1 --port 9440 --secure --user default --password pa55w0rd
Plugin ClickHouse dla LogStash zapisuje całą paczkę do pliku /tmp/log_web_failed.json w przypadku błędu w jednym wierszu.
Można ręcznie poprawić ten plik i spróbować załadować go do Bazy Danych ręcznie:
clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /tmp/log_web_failed__fixed.json
sudo mv /etc/logstash/tmp/log_web_failed.json /etc/logstash/tmp/log_web_failed__fixed.json
sudo chown user_dev /etc/logstash/tmp/log_web_failed__fixed.json
sudo clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /etc/logstash/tmp/log_web_failed__fixed.json
sudo mv /etc/logstash/tmp/log_web_failed__fixed.json /etc/logstash/tmp/log_web_failed__fixed_.json
wyjście z wiersza poleceń
quit;
## Konfiguracja TLS
https://www.altinity.com/blog/2019/3/5/clickhouse-networking-part-2
openssl s_client -connect log.domain.com:9440 < /dev/nullLogStash. Router logów od FileBeat do kolejki RabbitMQ
Ten komponent służy do routingu logów pochodzących od FileBeat do kolejki RabbitMQ. Są tutaj dwa aspekty:
- Niestety, FileBeat nie ma pluginu output do zapisywania bezpośrednio w RabbitMQ. Taka funkcjonalność, jak wskazuje temat na ich GitHubie, nie jest planowana do realizacji. Jest plugin dla Kafki, ale z pewnych powodów nie możemy go u siebie użyć.
- Są wymagania dotyczące zbierania logów w DMZ. Zgodnie z nimi, logi najpierw muszą trafić do kolejki, a następnie LogStash zewnętrznie odczytuje zapisy z kolejki.
Dlatego w przypadku umiejscowienia serwerów w DMZ konieczne jest zastosowanie nieco bardziej skomplikowanej schemy. Przykład konfiguracji wygląda następująco:
iis_w3c_logs__filebeat_rabbitmq.conf
input {
beats {
port => 5044
type => 'iis'
ssl => true
ssl_certificate_authorities => ["/etc/pki/tls/certs/app/ca.pem", "/etc/pki/tls/certs/app/ca-issuing.pem"]
ssl_certificate => "/etc/pki/tls/certs/app/queue.domain.com.cer"
ssl_key => "/etc/pki/tls/certs/app/queue.domain.com-pkcs8.key"
ssl_verify_mode => "peer"
}
}
output {
#stdout {codec => rubydebug}
rabbitmq {
host => "127.0.0.1"
port => 5672
exchange => "monitor.direct"
exchange_type => "direct"
key => "%{[fields][fld_app_name]}"
user => "q-writer"
password => "password"
ssl => false
}
}RabbitMQ. Kolejka wiadomości
Ten komponent jest używany do buforowania wpisów logów w DMZ. Zapis jest realizowany za pośrednictwem połączenia Filebeat → LogStash. Odczyt odbywa się z zewnątrz DMZ poprzez LogStash. W eksploatacji za pomocą RabbitMQ obsługiwanych jest około 4 tysięcy wiadomości na sekundę.
Routing wiadomości jest skonfigurowany na podstawie nazwy systemu, tzn. na podstawie danych konfiguracyjnych FileBeat. Wszystkie wiadomości trafiają do jednej kolejki. Jeśli z jakiegoś powodu usługa kolejek zostanie zatrzymana, nie spowoduje to utraty wiadomości: FileBeat’y będą otrzymywać błędy połączenia i tymczasowo wstrzymają wysyłanie. LogStash, który odczytuje z kolejki, również będzie otrzymywał błędy sieciowe i czekał na przywrócenie połączenia. W tym czasie dane oczywiście przestaną być zapisywane w bazie danych.
Następujące instrukcje są używane do tworzenia i konfigurowania kolejek:
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare exchange --vhost=/ name=monitor.direct type=direct sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare queue --vhost=/ name=web_log durable=true
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site1.domain.ru"
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site2.domain.ru"Grafana. Dashboardy
Ten komponent jest używany do wizualizacji danych monitoringowych. W tym celu należy zainstalować wtyczkę ClickHouse datasource for Grafana 4.6+. Musieliśmy ją nieco zmodyfikować, aby zwiększyć efektywność przetwarzania filtrów SQL na dashboardzie.
Na przykład, używamy zmiennych, a jeśli nie są one zdefiniowane w polu filtra, chcielibyśmy, aby nie generowało to warunku w WHERE w postaci (uriStem = » AND uriStem != »). W takim przypadku ClickHouse będzie odczytywał kolumnę uriStem. Ogólnie rzecz biorąc, wypróbowaliśmy różne opcje i ostatecznie poprawiliśmy wtyczkę (makro $valueIfEmpty), aby w przypadku pustej wartości zwracała 1, bez wspominania samej kolumny.
I teraz można używać takiego zapytania do wykresu.
$columns(response, count(*) c) from $table where $adhoc
and $valueIfEmpty($fld_app_name, 1, fld_app_name = '$fld_app_name')
and $valueIfEmpty($fld_app_module, 1, fld_app_module = '$fld_app_module') and $valueIfEmpty($fld_server_name, 1, fld_server_name = '$fld_server_name') and $valueIfEmpty($uriStem, 1, uriStem like '%$uriStem%')
and $valueIfEmpty($clientRealIP, 1, clientRealIP = '$clientRealIP')który przekształca się w takie SQL (zauważ, że puste pola uriStem przekształciły się po prostu w 1)
SELECT
t,
groupArray((response, c)) AS groupArr
FROM (
SELECT
(intDiv(toUInt32(logdatetime), 60) * 60) * 1000 AS t, response,
count(*) AS c FROM default.log_web
WHERE (logdate >= toDate(1565061982)) AND (logdatetime >= toDateTime(1565061982)) AND 1 AND (fld_app_name = 'site1.domain.ru') AND (fld_app_module = 'web') AND 1 AND 1 AND 1
GROUP BY
t, response
ORDER BY
t ASC,
response ASC
)
GROUP BY t ORDER BY t ASCPodsumowanie
Pojawienie się bazy danych ClickHouse stało się znaczącym wydarzeniem na rynku. Trudno było sobie wyobrazić, że całkowicie za darmo w mgnieniu oka zostaliśmy wyposażeni w potężne i praktyczne narzędzie do pracy z danymi big data. Oczywiście, w miarę wzrostu potrzeb (na przykład sharding i replikacja na kilka serwerów) schemat będzie się kompliktował. Ale po pierwszych wrażeniach, praca z tą bazą danych jest bardzo przyjemna. Widać, że produkt został stworzony „dla ludzi”.
W porównaniu do ElasticSearch, koszty przechowywania i przetwarzania logów, według wstępnych ocen, zmniejszają się od pięciu do dziesięciu razy. Innymi słowy, jeśli do aktualnej ilości danych musielibyśmy konfigurować klaster z kilku maszyn, to przy użyciu ClickHouse wystarczy nam jedna mało wydajna maszyna. Tak, oczywiście, w ElasticSearch istnieją również mechanizmy kompresji danych na dysku i inne funkcje, które pozwalają znacząco zmniejszyć zużycie zasobów, ale w porównaniu do ClickHouse będzie to wymagało większych wydatków.
Bez żadnych specjalnych optymalizacji z naszej strony, przy domyślnych ustawieniach, ładowanie danych i zapytania z bazy danych działają z oszałamiającą prędkością. Na razie mamy niewiele danych (około 200 mln rekordów), ale sam serwer jest słaby. To narzędzie możemy w przyszłości wykorzystać również do innych celów, niezwiązanych z przechowywaniem logów. Na przykład, do analityki end-to-end, w zakresie bezpieczeństwa, uczenia maszynowego.
Na koniec trochę o minusach i plusach.
Minusy
- Ładowanie rekordów dużymi partiami. Z jednej strony to funkcjonalność, ale jednak trzeba korzystać z dodatkowych komponentów do buforowania rekordów. To zadanie nie zawsze jest proste, ale mimo wszystko jest do rozwiązania. Chciałbym uprościć tę schemat.
- Niektóre egzotyczne funkcje lub nowe cechy często psują się w nowych wersjach. Wywołuje to obawy, zmniejszając chęć przejścia na nową wersję. Na przykład, silnik tabel Kafka – bardzo przydatna funkcja, która pozwala na bezpośrednie odczytywanie zdarzeń z Kafki, bez konieczności wdrażania konsumentów. Jednak na podstawie liczby zgłoszeń na GitHubie, obecnie unikamy używania tego silnika w produkcji. Niemniej jednak, jeśli nie robić gwałtownych ruchów i korzystać z podstawowej funkcjonalności, działa on stabilnie.
Zalety
- Nie spowalnia.
- Niski próg wejścia.
- Open-source.
- Darmowy.
- Dobrze skalowalny (shardowanie/replikacja 'out of the box')
- Figuruje w rejestrze rosyjskiego oprogramowania rekomendowanego przez Ministerstwo Komunikacji.
- Obecność oficjalnego wsparcia od Yandex.
Źródło: habr.com
