JOIN kötü değildir, ama alışkanlık haline gelmemeli
OLTP dünyasından gelen ekipler normalize modeli doğal görür. ClickHouse tarafında ise analitik sorgular çoğu zaman geniş event tabloları üzerinde çalışır. Her sorguda büyük JOIN yapmak okunan veri ve memory kullanımını artırabilir.
Önce sorgunun ne sorduğunu anlamak gerekir. Kullanıcı adını mı göstereceksiniz, yoksa kullanıcı segmentine göre milyarlarca eventi mi gruplayacaksınız? İki sorunun tasarımı aynı olmayabilir.
- Küçük ve sık okunan lookup verisi dictionary adayıdır.
- Sık değişmeyen boyut bilgisi event içine denormalize edilebilir.
- Ağır agregasyonlar materialized view veya rollup tabloya taşınabilir.
- Büyük JOINler query_log ile read_rows, memory_usage ve duration üzerinden izlenmelidir.
Dictionary lookup için sade bir araçtır
Dictionary, küçük veya orta boy lookup verisini sorgu sırasında hızlı okumak için kullanılır. Örneğin tenant_id -> plan, country_code -> region veya product_id -> category gibi eşleştirmelerde işe yarayabilir.
Dictionary verisinin güncellik ihtiyacı önemlidir. Sürekli değişen ve transaction tutarlılığı beklenen veri dictionary için uygun olmayabilir.
Dictionary kullanım fikri
CREATE DICTIONARY tenant_plan_dict
(
tenant_id String,
plan String
)
PRIMARY KEY tenant_id
SOURCE(CLICKHOUSE(TABLE 'tenant_plans'))
LAYOUT(HASHED())
LIFETIME(MIN 60 MAX 300);
SELECT
dictGet('tenant_plan_dict', 'plan', tenant_id) AS plan,
count()
FROM events
GROUP BY plan;Gerçek kaynak ve refresh ayarları altyapınıza göre değişir. Kritik nokta, lookup verisinin boyutu ve güncellik beklentisini doğru okumaktır.
Denormalizasyon bazen daha dürüst çözümdür
Event yazılırken o anki product_category veya tenant_plan bilgisini event içine koymak, bazı panolarda JOIN ihtiyacını tamamen kaldırır. Bunun bedeli, geçmiş verinin o andaki bilgiyi taşımasıdır.
Bu kötü değildir; hatta analitikte çoğu zaman istenen şey budur. “O gün kullanıcı hangi plandaydı?” sorusu ile “şu an hangi planda?” sorusu farklıdır.
Kararı query_log ile verin
Dictionary, JOIN veya denormalizasyon kararını tartışmayla değil ölçümle verin. Aynı pano sorgusunda read_rows, read_bytes, memory_usage ve query_duration_ms değerleri nasıl değişiyor bakın.
TürkDB tarafında kapasiteyi artırmak mümkün olsa bile, doğru modelleme kararı önce gelmelidir. Aksi halde pahalı kaynakla yanlış sorgu modelini ayakta tutarsınız.
JOIN maliyeti ölçümü
SELECT query_duration_ms, read_rows, formatReadableSize(read_bytes) AS read_size, formatReadableSize(memory_usage) AS memory, left(query, 200) AS query FROM system.query_log WHERE type = 'QueryFinish' AND event_time > now() - INTERVAL 1 HOUR AND positionCaseInsensitive(query, 'join') > 0 ORDER BY query_duration_ms DESC LIMIT 20;