perf(clickhouse): P1 — partition + skipping indexes sur ml_detected_anomalies, http_logs, agg_host_ip_ja4_1h
Problème : toutes les requêtes du dashboard WHERE detected_at >= now() - INTERVAL N
faisaient un full scan car ml_detected_anomalies avait ORDER BY (src_ip) sans
partition ni index temporel.
Changements :
- 06_ml_tables.sql :
* ml_detected_anomalies : PARTITION BY toYYYYMMDD(detected_at)
→ élagage de partitions journalières sur toutes les requêtes temporelles
* INDEX idx_detected_at (minmax) → skip des granules hors plage
* INDEX idx_threat_level set(8) → skip pour countIf(threat_level = ...)
* INDEX idx_bot_name bloom_filter → skip pour bot_name != ''
* ttl_only_drop_parts = 1 → TTL par suppression de partition entière
* ml_all_scores : même traitement (PARTITION BY + 2 indexes)
- 04_mv_http_logs.sql :
* http_logs : INDEX idx_src_ip bloom_filter(0.01)
→ les requêtes WHERE src_ip = X (analysis.py, variability.py) sautent
~90% des granules sans scanner toute la plage temporelle
* INDEX idx_ja4 bloom_filter(0.01) → idem pour filtres JA4
- 05_aggregation_tables.sql :
* agg_host_ip_ja4_1h : PROJECTION proj_by_ip ORDER BY (src_ip, window_start, ...)
→ investigation_summary.py et rotation.py (WHERE src_ip = X) utilisent
automatiquement la projection au lieu de scanner tous les window_start
- 10_perf_indexes.sql (nouveau) :
* Migration ALTER TABLE pour instances existantes
* ADD INDEX + MATERIALIZE INDEX pour les 4 tables
* ADD PROJECTION + MATERIALIZE PROJECTION pour agg_host_ip_ja4_1h
* Note : PARTITION BY sur table existante nécessite recréation (documenté)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@ -87,7 +87,13 @@ CREATE TABLE IF NOT EXISTS ja4_logs.http_logs
|
||||
-- Anubis enrichment columns
|
||||
`anubis_bot_name` LowCardinality(String) DEFAULT '',
|
||||
`anubis_bot_action` LowCardinality(String) DEFAULT '',
|
||||
`anubis_bot_category` LowCardinality(String) DEFAULT ''
|
||||
`anubis_bot_category` LowCardinality(String) DEFAULT '',
|
||||
|
||||
-- Index bloom_filter sur src_ip : les requêtes WHERE src_ip = X sautent
|
||||
-- les granules qui ne contiennent pas cette IP (~90% des granules en pratique).
|
||||
-- Taux de faux positifs 1% (0.01) : bon compromis taille / efficacité.
|
||||
INDEX idx_src_ip src_ip TYPE bloom_filter(0.01) GRANULARITY 4,
|
||||
INDEX idx_ja4 ja4 TYPE bloom_filter(0.01) GRANULARITY 4
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY log_date
|
||||
|
||||
@ -98,7 +98,16 @@ CREATE TABLE IF NOT EXISTS ja4_processing.agg_host_ip_ja4_1h
|
||||
count_correlated SimpleAggregateFunction(sum, UInt64),
|
||||
-- HTTP features
|
||||
count_no_accept_enc SimpleAggregateFunction(sum, UInt64),
|
||||
count_http_scheme SimpleAggregateFunction(sum, UInt64)
|
||||
count_http_scheme SimpleAggregateFunction(sum, UInt64),
|
||||
|
||||
-- Projection pour les requêtes d'investigation par IP :
|
||||
-- ORDER BY actuel (window_start, src_ip, ...) est optimal pour heatmap
|
||||
-- mais inefficace pour WHERE src_ip = X (IP pas en première position).
|
||||
-- Cette projection stocke les données triées par (src_ip, ...) et est
|
||||
-- utilisée automatiquement par ClickHouse pour les filtres sur src_ip.
|
||||
PROJECTION proj_by_ip (
|
||||
SELECT * ORDER BY (src_ip, window_start, ja4, host)
|
||||
)
|
||||
)
|
||||
ENGINE = AggregatingMergeTree()
|
||||
ORDER BY (window_start, src_ip, ja4, host);
|
||||
|
||||
@ -1,10 +1,25 @@
|
||||
-- =============================================================================
|
||||
-- 06_ml_tables.sql — ML detection results tables
|
||||
-- Source: bot_detector/deploy_views.sql sections 6-6b + deploy_schema.sql items 11-12
|
||||
--
|
||||
-- Optimisations de performance :
|
||||
-- - ml_detected_anomalies : PARTITION BY date → élagage de partitions sur
|
||||
-- les requêtes temporelles (WHERE detected_at >= now() - INTERVAL N DAY)
|
||||
-- - INDEX idx_detected_at (minmax) → skip des granules hors plage temporelle
|
||||
-- - INDEX idx_threat_level (set) → skip pour les filtres par niveau de menace
|
||||
-- - ml_all_scores : PARTITION BY date + INDEX identiques
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- ml_detected_anomalies — anomaly detections above threat threshold
|
||||
--
|
||||
-- Déduplication : ReplacingMergeTree(detected_at) sur ORDER BY (src_ip)
|
||||
-- → conserve la détection la plus récente par IP.
|
||||
-- PARTITION BY : élagage journalier (les requêtes 24h/7j ignorent les vieilles
|
||||
-- partitions sans lire aucune donnée).
|
||||
-- INDEX idx_detected_at : skip des granules 8192 lignes hors de la plage
|
||||
-- temporelle demandée (minmax = min/max par granule).
|
||||
-- INDEX idx_threat_level : skip pour countIf(threat_level = 'CRITICAL') etc.
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS ja4_processing.ml_detected_anomalies
|
||||
(
|
||||
@ -32,15 +47,30 @@ CREATE TABLE IF NOT EXISTS ja4_processing.ml_detected_anomalies
|
||||
-- Anubis enrichment (deploy_schema.sql item 11)
|
||||
anubis_bot_name LowCardinality(String) DEFAULT '',
|
||||
anubis_bot_action LowCardinality(String) DEFAULT '',
|
||||
anubis_bot_category LowCardinality(String) DEFAULT ''
|
||||
anubis_bot_category LowCardinality(String) DEFAULT '',
|
||||
|
||||
-- Index de saut : skip des granules hors plage temporelle
|
||||
INDEX idx_detected_at detected_at TYPE minmax GRANULARITY 4,
|
||||
-- Index de saut : skip pour les filtres sur threat_level (CRITICAL/HIGH/...)
|
||||
INDEX idx_threat_level threat_level TYPE set(8) GRANULARITY 4,
|
||||
-- Index de saut : skip pour les filtres bot_name != ''
|
||||
INDEX idx_bot_name bot_name TYPE bloom_filter() GRANULARITY 4
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(detected_at)
|
||||
PARTITION BY toYYYYMMDD(detected_at)
|
||||
ORDER BY (src_ip)
|
||||
TTL detected_at + INTERVAL 30 DAY;
|
||||
TTL detected_at + INTERVAL 30 DAY
|
||||
SETTINGS
|
||||
index_granularity = 8192,
|
||||
ttl_only_drop_parts = 1; -- supprime la partition entière à expiration (plus efficace)
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- ml_all_scores — all classifications (no threshold, for observability)
|
||||
--
|
||||
-- PARTITION BY date : TTL de 3 jours → les partitions expirées sont supprimées
|
||||
-- entièrement sans avoir à lire chaque granule (ttl_only_drop_parts).
|
||||
-- INDEX idx_detected_at : idem ml_detected_anomalies.
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS ja4_processing.ml_all_scores
|
||||
(
|
||||
@ -67,12 +97,18 @@ CREATE TABLE IF NOT EXISTS ja4_processing.ml_all_scores
|
||||
-- Anubis enrichment (deploy_schema.sql item 12)
|
||||
anubis_bot_name LowCardinality(String) DEFAULT '',
|
||||
anubis_bot_action LowCardinality(String) DEFAULT '',
|
||||
anubis_bot_category LowCardinality(String) DEFAULT ''
|
||||
anubis_bot_category LowCardinality(String) DEFAULT '',
|
||||
|
||||
INDEX idx_detected_at detected_at TYPE minmax GRANULARITY 4,
|
||||
INDEX idx_threat_level threat_level TYPE set(8) GRANULARITY 4
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(detected_at)
|
||||
PARTITION BY toYYYYMMDD(window_start)
|
||||
ORDER BY (window_start, src_ip, ja4, host, model_name)
|
||||
TTL window_start + INTERVAL 3 DAY
|
||||
SETTINGS index_granularity = 8192;
|
||||
SETTINGS
|
||||
index_granularity = 8192,
|
||||
ttl_only_drop_parts = 1;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
113
shared/clickhouse/10_perf_indexes.sql
Normal file
113
shared/clickhouse/10_perf_indexes.sql
Normal file
@ -0,0 +1,113 @@
|
||||
-- =============================================================================
|
||||
-- 10_perf_indexes.sql — Migrations de performance (indexes + vues matérialisées)
|
||||
--
|
||||
-- Ce fichier applique les optimisations de performance sur des tables existantes.
|
||||
-- Il est idempotent : les ADD INDEX / ADD PROJECTION échouent silencieusement
|
||||
-- si l'index existe déjà (IF NOT EXISTS non disponible pour les indexes, utiliser
|
||||
-- la commande IF NOT EXISTS en ClickHouse 24+, sinon ignorer les erreurs ALREADY_EXISTS).
|
||||
--
|
||||
-- Usage :
|
||||
-- Pour une installation fraîche, ces optimisations sont déjà incluses dans les
|
||||
-- fichiers 04/05/06_*.sql. Ce fichier sert aux migrations d'instances existantes.
|
||||
--
|
||||
-- clickhouse-client --multiquery < 10_perf_indexes.sql
|
||||
--
|
||||
-- Sections :
|
||||
-- 1. ml_detected_anomalies — indexes de saut temporels et sémantiques
|
||||
-- 2. ml_all_scores — index de saut temporel
|
||||
-- 3. http_logs — bloom filter sur src_ip et ja4
|
||||
-- 4. agg_host_ip_ja4_1h — projection per-IP pour les requêtes d'investigation
|
||||
-- =============================================================================
|
||||
|
||||
-- NOTE : PARTITION BY ne peut pas être ajouté via ALTER TABLE dans ClickHouse.
|
||||
-- Pour bénéficier du partitionnement journalier sur une table existante, il faut
|
||||
-- recréer la table et réinsérer les données :
|
||||
--
|
||||
-- RENAME TABLE ja4_processing.ml_detected_anomalies
|
||||
-- TO ja4_processing.ml_detected_anomalies_old;
|
||||
-- (recréer avec 06_ml_tables.sql)
|
||||
-- INSERT INTO ja4_processing.ml_detected_anomalies
|
||||
-- SELECT * FROM ja4_processing.ml_detected_anomalies_old;
|
||||
-- DROP TABLE ja4_processing.ml_detected_anomalies_old;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. ml_detected_anomalies — indexes de saut
|
||||
-- =============================================================================
|
||||
|
||||
-- Index minmax sur detected_at : permet au moteur de sauter les granules
|
||||
-- (blocs de 8192 lignes) dont le min/max de detected_at est en dehors
|
||||
-- de la plage temporelle demandée (ex: WHERE detected_at >= now() - INTERVAL 24 HOUR).
|
||||
ALTER TABLE ja4_processing.ml_detected_anomalies
|
||||
ADD INDEX IF NOT EXISTS idx_detected_at detected_at TYPE minmax GRANULARITY 4;
|
||||
|
||||
-- Index set(8) sur threat_level : stocke au maximum 8 valeurs distinctes
|
||||
-- par granule. Permet de sauter les granules qui ne contiennent pas la valeur
|
||||
-- filtrée (ex: WHERE threat_level = 'CRITICAL' skip les granules sans CRITICAL).
|
||||
ALTER TABLE ja4_processing.ml_detected_anomalies
|
||||
ADD INDEX IF NOT EXISTS idx_threat_level threat_level TYPE set(8) GRANULARITY 4;
|
||||
|
||||
-- Index bloom_filter sur bot_name : skip des granules pour bot_name != ''
|
||||
-- et les filtres par nom exact (ex: WHERE bot_name = 'Googlebot').
|
||||
ALTER TABLE ja4_processing.ml_detected_anomalies
|
||||
ADD INDEX IF NOT EXISTS idx_bot_name bot_name TYPE bloom_filter() GRANULARITY 4;
|
||||
|
||||
-- Matérialiser les indexes sur les données existantes (opération asynchrone).
|
||||
-- ClickHouse traite ça en tâche de fond ; la table reste accessible en lecture.
|
||||
ALTER TABLE ja4_processing.ml_detected_anomalies MATERIALIZE INDEX idx_detected_at;
|
||||
ALTER TABLE ja4_processing.ml_detected_anomalies MATERIALIZE INDEX idx_threat_level;
|
||||
ALTER TABLE ja4_processing.ml_detected_anomalies MATERIALIZE INDEX idx_bot_name;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. ml_all_scores — index de saut temporel
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE ja4_processing.ml_all_scores
|
||||
ADD INDEX IF NOT EXISTS idx_detected_at detected_at TYPE minmax GRANULARITY 4;
|
||||
ALTER TABLE ja4_processing.ml_all_scores
|
||||
ADD INDEX IF NOT EXISTS idx_threat_level threat_level TYPE set(8) GRANULARITY 4;
|
||||
|
||||
ALTER TABLE ja4_processing.ml_all_scores MATERIALIZE INDEX idx_detected_at;
|
||||
ALTER TABLE ja4_processing.ml_all_scores MATERIALIZE INDEX idx_threat_level;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. http_logs — bloom filter sur src_ip et ja4
|
||||
--
|
||||
-- http_logs est ORDER BY (time, src_ip, dst_ip, ja4).
|
||||
-- Les requêtes de type WHERE src_ip = X AND time >= ... ne peuvent pas utiliser
|
||||
-- l'index de tri efficacement (src_ip n'est pas en première position).
|
||||
-- Un bloom filter permet de sauter les granules qui ne contiennent pas l'IP
|
||||
-- recherchée, réduisant la quantité de données lues de ~90% en pratique.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE ja4_logs.http_logs
|
||||
ADD INDEX IF NOT EXISTS idx_src_ip src_ip TYPE bloom_filter(0.01) GRANULARITY 4;
|
||||
ALTER TABLE ja4_logs.http_logs
|
||||
ADD INDEX IF NOT EXISTS idx_ja4 ja4 TYPE bloom_filter(0.01) GRANULARITY 4;
|
||||
|
||||
ALTER TABLE ja4_logs.http_logs MATERIALIZE INDEX idx_src_ip;
|
||||
ALTER TABLE ja4_logs.http_logs MATERIALIZE INDEX idx_ja4;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 4. agg_host_ip_ja4_1h — projection per-IP
|
||||
--
|
||||
-- ORDER BY actuel : (window_start, src_ip, ja4, host)
|
||||
-- → efficace pour les requêtes heatmap (GROUP BY window_start)
|
||||
-- → inefficace pour les requêtes d'investigation (WHERE src_ip = X)
|
||||
--
|
||||
-- La projection stocke les données dans un ordre alternatif (src_ip en premier)
|
||||
-- et est utilisée automatiquement par ClickHouse quand src_ip est filtré.
|
||||
-- Coût : ~2× l'espace disque pour cette table (acceptable vu sa taille).
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE ja4_processing.agg_host_ip_ja4_1h
|
||||
ADD PROJECTION IF NOT EXISTS proj_by_ip (
|
||||
SELECT * ORDER BY (src_ip, window_start, ja4, host)
|
||||
);
|
||||
|
||||
-- Matérialiser la projection sur les données existantes.
|
||||
ALTER TABLE ja4_processing.agg_host_ip_ja4_1h
|
||||
MATERIALIZE PROJECTION proj_by_ip;
|
||||
Reference in New Issue
Block a user