Önce neden ayırdığınızı yazın
PostgreSQL’den ClickHouse’a geçmek “raporlar hızlı olsun” diye tek cümleyle verilmemesi gereken bir karardır. Sorun gerçekten analitik okuma yükü mü, yoksa eksik indeks, yanlış sorgu veya eski veri yaşam döngüsü mü? Önce bunu ayırın.
Ayrım doğruysa hedef şudur: uygulamanın transaction yükü PostgreSQL’de kalır, geniş zaman aralıklı okuma ve agregasyon ClickHouse’a gider. Böylece iki motor kendi güçlü olduğu işi yapar.
- OLTP: sipariş oluşturma, kullanıcı güncelleme, ödeme durumu gibi doğru ve güncel işlem verisi.
- OLAP: event sayımı, funnel, cohort, pano, uzun tarih aralığı ve yüksek hacimli agregasyon.
- Ayrım, uygulama doğruluğunu değil raporlama yükünü rahatlatmalıdır.
- ClickHouse kaynağı değil, okuma modelini hızlandırır; event kalitesi kötü ise sonuç yine kötü olur.
Event sözleşmesini baştan sade tutun
İyi event modeli her şeyi JSON içine atmak değildir. Tenant, event adı, zaman, kullanıcı, kaynak ve idempotency anahtarı gibi alanlar açık kolon olmalıdır. Bu alanlar hem sorgu performansını hem de duplicate temizliğini belirler.
Event payloadı elbette esnek olabilir; ama panoların sürekli filtrelediği alanları payload içinde saklamak ClickHouse’un gücünü azaltır.
ClickHouse event tablosu başlangıcı
CREATE TABLE analytics_events ( tenant_id String, event_id UUID, event_name LowCardinality(String), user_id String, occurred_at DateTime64(3), ingested_at DateTime64(3) DEFAULT now64(3), source LowCardinality(String), payload String ) ENGINE = MergeTree PARTITION BY toYYYYMM(occurred_at) ORDER BY (tenant_id, event_name, occurred_at, event_id);
Batch insert küçük fark gibi görünür, büyük fark yaratır
ClickHouse tek tek insert edildiğinde yorulabilir. Pipeline küçük eventleri biriktirip kontrollü batchler halinde yazmalıdır. Batch boyutu çok küçükse part sayısı artar; çok büyükse gecikme ve retry maliyeti artar.
Burada amaç sadece hız değildir. Retry olduğunda aynı event tekrar yazılmasın, yazıldıysa raporda çift sayılmasın. Bunun için event_id veya doğal idempotency anahtarı tasarlanmalıdır.
Basit batch insert fikri
const rows = events.map((event) => ({
tenant_id: event.tenantId,
event_id: event.id,
event_name: event.name,
user_id: event.userId,
occurred_at: event.occurredAt,
source: "app",
payload: JSON.stringify(event.payload),
}))
await clickhouse.insert({
table: "analytics_events",
values: rows,
format: "JSONEachRow",
})Pano sorgusu tablo tasarımını test eder
Event yazmak kolaydır; doğru okumak daha zordur. En sık kullanılan pano sorgularını erken yazın ve read_rows/read_bytes değerlerini ölçün. Eğer her sorgu tüm tenantları okuyorsa ORDER BY veya filtre modeliniz yanlıştır.
Tenant bazlı funnel sorgusu
SELECT
event_name,
countDistinct(user_id) AS users
FROM analytics_events
WHERE tenant_id = 'acme'
AND occurred_at >= now() - INTERVAL 30 DAY
AND event_name IN ('signup', 'ödeme akışı_started', 'payment_completed')
GROUP BY event_name
ORDER BY users DESC;TürkDB ile iki motoru aynı operasyon ritmine alın
Pipeline kurulduğunda artık iki motor vardır: PostgreSQL uygulama doğruluğunu, ClickHouse analitik hızını taşır. Bunların yedek, erişim, olay ve kapasite yönetimi ayrı ayrı unutulmamalıdır.
TürkDB burada pratik bir kolaylık hedefler: iki motorun bağlantı, yedek ve olay görünürlüğünü aynı arayüzden takip etmek. Bu hedef pilotta çalışırsa özellikle küçük ekiplerde “ClickHouse başka bir ekip bilir” boşluğunu azaltabilir.
OLTP ve OLAP cluster hazırlığı
turkdb cluster create --name app-postgres --engine postgresql --version 16 --region tr-ist-1 turkdb cluster create --name app-clickhouse --engine clickhouse --version 24 --region tr-ist-1 turkdb connect app-postgres --no-tunnel turkdb connect app-clickhouse --no-tunnel turkdb backup list app-postgres turkdb cluster events app-clickhouse --limit 20