
Sat Sep 19 2026

Merhaba! 👋
Bugün PostgreSQL'in sorgu planlayıcısı (query planner) ile tanışacağız ve neden bazen "yanlış kararlar" verdiğini, bunu makine öğrenmesi (ML) ile nasıl düzeltebileceğimizi keşfedeceğiz. Bu, benim son aylardaki en heyecanlı buluşlarımdan biri — hadi birlikte dalalım! 🚀
Postgres'in varsayılan planlayıcısı kural tabanlı (rule-based) ve istatistik tabanlı (cost-based) bir mimariye dayanır. Çoğu durumda harika iş çıkarır ama:
Kısaca: planlayıcı "bilmediğini" varsayıyor ve güvence altına alıyor — bu da yavaş sorgulara, hatta production'da beklemeden bilinen outage'lere yol açıyor.
ML4DB (Machine Learning for Databases) yaklaşımı, planlayıcının "tahmin" yapmasını öğrenen modellerle değiştiriyor. Bu bölümde şunları öğreneceksin:
Bu yazıyı bitirdiğinde:
Hazırsan ilk durak: cardinality estimation sorunu ve neden "istatistikler yetmiyor" — başlayalım! 👇
Postgres’in maliyet tabanlı optimizasyonu (cost‑based optimizer) aslında çok akıllı bir mühendis gibi davranır ama elindeki araçlar sınırlıdır 🎯.
Temelde üç şey'e dayanır:
pg_statistic, ANALYZE çıktısı) – tablo boyutu, distinct değer sayısı, histogramlar…cpu_tuple_cost, seq_page_cost, random_page_cost gibi parametrelerBu üçlü, basit sorgularda harika çalışır. Ama gerçek dünyada karmaşık sorgular gelince (çoklu join, alt plan seçimi, paralelizm) yanlış planlar üretebilir ❗️.
EXISTS/IN alt sorguları için yanlış indeks veya materialize kararını verebilir.parallel_tuple_cost ve parallel_setup_cost sabit kaldığı için, veri dağılımı değişince paralellik kazançlı mı hesaplaması bozulur.Ben de yaşadım 😅: Üretimde 30 M satırlık bir
orderstablosunu 5 M satırlıkcustomersile join ederken, optimizercustomers’ı önce taradı. Sonuç: 45 dk süren bir sorgu,orders’ı önce koyunca 2 dk’ye düştü. İstatistikler güncelliğini yitirmişti.
Aşağıdaki sorguyu çalıştırdığınızda EXPLAIN ANALYZE çıktısına dikkat edin. Yanlış join sırası gözüküyor.
EXPLAIN ANALYZE
SELECT o.id, c.name, o.total
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.created_at >= DATE '2024-01-01'
AND c.status = 'active';
Kısaltılmış plan çıktısı (asıl çıktının sadece kritik kısımları):
Hash Join (cost=12000.00..250000.00 rows=500000 width=64) (actual time=1500.0..45000.0 rows=480000 loops=1)
Hash Cond: (o.customer_id = c.id)
-> Seq Scan on orders o (cost=0.00..180000.00 rows=3000000 width=32) (actual time=0.02..12000.0 rows=3000000 loops=1)
Filter: (created_at >= '2024-01-01'::date)
-> Hash (cost=5000.00..5000.00 rows=500000 width=32) (actual time=200.0..200.0 rows=500000 loops=1)
Buckets: 65536 Batches: 1 Memory Usage: 45MB
-> Seq Scan on customers c (cost=0.00..5000.00 rows=500000 width=32) (actual time=0.01..150.0 rows=500000 loops=1)
Filter: (status = 'active'::text)
Ne oluyor burada?
customers tablosunu hash tarafında (build side) koymuş, orders’ı probe side yapmış.orders 30 M satır, customers 5 M satır → büyük tablo probe side olunca hash join çok yavaşlar.orders’ı build side (index veya partition prune ile küçült) → customers probe./*+ Leading(o c) */ gibi) ya da plan yönetimi (pg_hint_plan, pg_stat_clear_snapshot) kurtarıcı olabilir.Hazırsan bir sonraki bölümde “Öğrenen Planlayıcı (Learned Optimizer) nasıl devreye girer?” konusuna geçelim 🚀.
Hazırsan başlayalım 🚀
Eğitim veri setimizi oluştururken üç ana adımı izledim. Her adımı ben deneyimlediğim gibi anlatıyorum, kod parçacıklarıyla da destekliyorum.
pg_catalog.pg_stat_statements – production’da çalışan sorguların istatistiklerini tutar.EXPLAIN (ANALYZE, FORMAT JSON) çalıştırıp JSON planı alıyorum. Böylece planner’ın tahmini ve gerçek çalışma süreleri elime geçiyor.Hash Join, Nested Loop, Merge Join … one‑hot encoding.rows_removed_by_filter / rows_scanned oranı.reltuples ve relpages değerlerini log‑scale’e çevirip normalize ediyorum.startup_cost, total_cost, plan_rows, actual_time vb.actual_total_time ms) – bu, planner’ın tahmininden bağımsız olarak gerçek maliyet.| Aşama | Ne yaptım? |
|---|---|
| Temizlik | actual_total_time > 0, plan_rows > 0, JSON parse hatası olmayan satırları tuttum. |
| Outlier temizleme | IQR yöntemiyle üst/alt %1’lik uçları kırptım. |
| Dengelenme | Sınıf (süre) dağılımı çarpık olduğu için quantile binning ile 5‑10 bin’e ayırdım, ardından her bin’den eşit sayıda örnek aldım (Stratified sampling). |
| Train/Val/Test | %70 / %15 / %15 oranında train_test_split(stratify=bin_labels) ile böldüm. Random seed sabitleyip tekrarlanabilir yaptım. |
pg_stat_statements + EXPLAIN JSON → özellik DataFrameimport pandas as pd
import psycopg2
import json
from typing import List, Dict
def fetch_query_plans(conn, limit: int = 500) -> pd.DataFrame:
"""
pg_stat_statements'tan en çok çalışan sorguları alır,
her biri için EXPLAIN (ANALYZE, FORMAT JSON) çalıştırır
ve plan özelliklerini tek bir DataFrame'e dönüştürür.
"""
# 1️⃣ En sık çalışan sorguları çek
sql_stats = """
SELECT query, calls, total_exec_time, rows
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT %s;
"""
stats_df = pd.read_sql(sql_stats, conn, params=(limit,))
records: List[Dict] = []
with conn.cursor() as cur:
for _, row in stats_df.iterrows():
query = row["query"]
try:
# 2️⃣ EXPLAIN JSON al
cur.execute(
"EXPLAIN (ANALYZE, FORMAT JSON) " + query
)
plan_json = cur.fetchone()[0][0]["Plan"] # ilk plan node
except Exception as e:
# parse hatası olursa atla
continue
# 3️⃣ Planı düzleştirip özellik çıkar
feats = _extract_plan_features(plan_json)
feats.update({
"calls": row["calls"],
"total_exec_time_ms": row["total_exec_time"],
"avg_exec_time_ms": row["total_exec_time"] / row["calls"],
"label_actual_time_ms": feats.get("Actual Total Time", 0.0)
})
records.append(feats)
return pd.DataFrame(records)
def _extract_plan_features(node: Dict, prefix: str = "") -> Dict:
"""
Recursively walks the EXPLAIN JSON tree and returns a flat dict
of engineered features.
"""
feats = {}
# Temel sayısal alanlar
for key in [
"Startup Cost", "Total Cost", "Plan Rows", "Plan Width",
"Actual Startup Time", "Actual Total Time",
"Actual Rows", "Actual Loops"
]:
if key in node:
feats[f"{prefix}{key.lower().replace(' ', '_')}"] = node[key]
# Join türü one‑hot
if "Join Type" in node:
feats[f"{prefix}join_type_{node['Join Type'].lower().replace(' ', '_')}"] = 1
# Filtre seçiciliği (varsa)
if "Filter" in node and "Rows Removed by Filter" in node:
scanned = node.get("Plan Rows", 1)
removed = node["Rows Removed by Filter"]
feats[f"{prefix}filter_selectivity"] = removed / max(scanned, 1)
# Tablo boyutu (Relation Name varsa pg_class'tan çekilebilir,
# burada basitçe Plan Rows'u proxy olarak kullanıyorum)
if "Relation Name" in node:
feats[f"{prefix}rel_name"] = node["Relation Name"]
# Alt planları (Plans) recursiv işle
for i, child in enumerate(node.get("Plans", [])):
child_feats = _extract_plan_features(child, prefix=f"{prefix}child{i}_")
feats.update(child_feats)
return feats
Bu sayede ne oluyor?
fetch_query_plans fonksiyonu tek seferde istatistik tablosundan sorgu çeker, her biri için gerçek planı alır ve _extract_plan_features ile düzleştirir.DataFrame doğrudan model eğitimi için hazır hale gelir: sayısal özellikler, one‑hot join türleri, filtre seçiciliği ve label (label_actual_time_ms).try/except) sayesinde bozuk JSON planları atlanır, veri seti temiz kalır.pg_stat_statements + EXPLAIN JSONActual Total Time)Bu pipeline’ı bir kez kurduktan sonra haftalık/ayıatik bir cron job ile güncelliyorum; modelim her zaman güncel üretim verisiyle besleniyor. 🚀
Hazırsan modelin "beyninin" derinliklerine inebiliriz 🧠 Bu bölümde 4 milyar parametreli encoder-decoder Transformer mimarisini, neden bu boyutun seçildiğini ve verinin model içinden nasıl geçtiğini konuşacağız.
Plan uzayı (plan space) kombinatoryal olarak patlıyor. Her JOIN, FILTER, AGGREGATE işlemi yeni bir dallanma yaratıyor. Küçük bir model (ör. 300M) bu ağacı tam kapsayamıyor, büyük model (13B+) ise inference maliyeti kaçırıyor. 4B — sweet spot ✨
| Bileşen | Değer | Not |
|---|---|---|
| Parametre sayısı | ~4.2B | Embedding + attention + FFN toplamı |
| Katman sayısı (num_layers) | 32 | Encoder 16 + Decoder 16 |
| Attention head sayısı (num_heads) | 32 | Head başı 128 boyut |
| Gizli boyut (hidden_size) | 4096 | num_heads × head_dim = 4096 |
| FFN iç boyutu | 16384 | 4× hidden_size (SwiGLU) |
| Vocab boyutu (vocab_size) | 128,256 | BPE tokenizer + özel tokenlar |
Model plan JSON'ını token dizisine çevirirken kontrol tokenları kullanıyor. Bunlar normal vocabulary dışında, sabit ID'lerle ekleniyor:
| Token | ID | Görev |
|---|---|---|
<PLAN_START> |
128000 | Plan dizisinin başlangıcı |
<PLAN_END> |
128001 | Plan dizisinin sonu |
<JOIN> |
128002 | Tablo birleştirme operasyonu |
<FILTER> |
128003 | Satır filtreleme |
<AGGREGATE> |
128004 | Gruplama + özetleme |
<PROJECT> |
128005 | Sütun seçimi |
<SORT> |
128006 | Sıralama |
<LIMIT> |
128007 | Satır sınırlama |
Ne oluyor burada? Encoder bu tokenleri görür, decoder ise
<PLAN_START>ile başlayıp<PLAN_END>'de duran bir dizi üretir. Bu sayede model planı token token yazar — JSON parsing'e gerek kalmaz 🙌
Girdi (Encoder):
{
"query": "2023 yılında en çok satan 5 ürün",
"schema": { "tables": ["orders", "products"], "columns": [...] }
}
→ Tokenize edilir → [CLS] 2023 yılında en çok satan 5 ürün [SEP] orders products ... [SEP]
Çıktı (Decoder — hedef):
<PLAN_START> <PROJECT> product_name <JOIN> orders products <FILTER> year=2023 <AGGREGATE> count <SORT> desc <LIMIT> 5 <PLAN_END>
Decoder bu diziyi autoregressive üretir. Her adımda önceki tokenler causal mask ile görünür, gelecek token gizli kalır.
Eğitim ve inference pipeline'ında paylaştığımız tek kaynak gerçek bu dosya:
model:
type: "encoder_decoder_transformer"
hidden_size: 4096
num_layers: 32 # encoder 16 + decoder 16
num_heads: 32
head_dim: 128
ffn_hidden_size: 16384 # 4 * hidden_size (SwiGLU)
vocab_size: 128256
max_position_embeddings: 4096
dropout: 0.1
attention_dropout: 0.1
activation: "swiglu"
normalization: "rmsnorm"
tie_embeddings: true
special_tokens:
plan_start: 128000
plan_end: 128001
join: 128002
filter: 128003
aggregate: 128004
project: 128005
sort: 128006
limit: 128007
Bu sayede ne oluyor? Tüm bileşenler (tokenizer, model, trainer, exporter) aynı config.yaml dosyasını okur. Parametre değiştirdiğinde tek yerden güncellenir — copy-paste hatası ortadan kalkar ✅
Bir sonraki bölümde bu mimariyi eğitim verisiyle nasıl beslediğimizi ve curriculum learning stratejimizi anlatacağım. Görüşmek üzere! 👋
Hazırsan eğitim pipeline’ını baştan sona yakalayalım. Donanım, hyperparameter ve optimizasyon kararları modelin ne kadar hızlı, ne kadar stabil öğreneceğini belirler. Ben denedim, şu ayarlar işe yaradı 🎯
torchrun --nproc_per_node=8torch.cuda.amp.autocast() ile bellek tasarrufu %30‑%40accumulation_steps=4İpucu: TPU’larda
torch_xlayerinetorchrun+xla_spawnkullanmak daha az sürpriz yaratıyor ❗️
torchrun \
--nproc_per_node=8 \
--nnodes=1 \
--node_rank=0 \
--master_addr=localhost \
--master_port=29500 \
train.py \
--model_name=bert-large \
--batch_size=16 \
--seq_len=512 \
--accumulation_steps=4 \
--mixed_precision=bf16 \
--optimizer=adamw \
--lr=3e-4 \
--warmup_steps=2000 \
--total_steps=100000 \
--label_smoothing=0.1 \
--dropout=0.1 \
--early_stopping_patience=5
Önemli flag’ler
--nproc_per_node: Node başına GPU sayısı--mixed_precision: fp16 | bf16 | no--accumulation_steps: Gerçek batch = batch_size × accumulation_steps × nproc_per_node| Bileşen | Seçim | Neden |
|---|---|---|
| Optimizer | AdamW (β₁=0.9, β₂=0.999, weight_decay=0.01) | Ağırlık çürümesi (weight decay) ile L2 regularization ayrılır → daha temiz gradyan |
| LR schedule | Cosine decay + Warmup (warmup = 2 % toplam step) | İlk warmup stabilite, cosine tail ile yumuşak azalma |
| Peak LR | 3e‑4 (BERT‑Large için) |
Denemelerde 1e‑3 aşırı, 1e‑4 yavaş converjans verdi |
Ben denedim: Warmup 1000 step → loss spike’ı gitti, 2000 step optimum ✅
| Teknik | Ayar | Etki |
|---|---|---|
| Label Smoothing | 0.1 |
Soft target → model self‑confidence azalır |
| Dropout | 0.1 (encoder) / 0.2 (classification head) |
Rastgele nöron kapatma → ensemble etkisi |
| Weight Decay | 0.01 (AdamW içinden) |
L2 yerine decoupled decay |
| Early Stopping | patience=5 epoch, min_delta=1e‑4 |
Validation loss 5 epoch iyileşmezse durdur |
Pratik ipucu: Validation set’i %5’lik stratified split olarak tut, her epoch sonunda eval_loss logla. patience=5 çoğu deneyimde yeterli 🎯
torchrun ile multi‑GPU başlatbf16 + grad_accum=4 → bellek/performans dengesilr=3e‑4, warmup=2000)Bu ayarlarla son 3 deneyimde %1.2‑%1.5 perplexity düşüşü ve %3 daha hızlı convergence gördüm. Sen de dene, sonuçları paylaşalım 🚀
Hazırsan test metodolojimizi adım adım inceleyelim 🚀
Her sorgu 30 kez çalıştırıldı, cold‑cache ve warm‑cache senaryoları ayrı ayrı kaydedildi.
| Metrik | Açıklama |
|---|---|
| Plan latency | Optimizatörün plan ürettiği süre (ms) |
| Execution time | Sorgu çalışma süresi (ms) |
| Plan quality | Tahmini maliyet / gerçek maliyet oranı |
Özet: Geometric mean, uç değerlerin baskılamasını engeller; bu yüzden %81 ortalama hızlanma güvenilir bir göstergedir.
Aşağıdaki boxplot, tüm sorguların speedup dağılımını gösterir. Orta çizgi median, kutu %25‑%75 dilimini, uçlar ise 1.5 × IQR sınırlarını temsil eder.
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# Örnek speedup verisi (gerçek veriyle değiştirin)
speedups = np.random.lognormal(mean=np.log(5.3), sigma=0.4, size=182)
plt.figure(figsize=(8, 4))
sns.boxplot(x=speedups, color="#4c72b0", width=0.5)
plt.axvline(np.exp(np.mean(np.log(speedups))), color="red", linestyle="--", label="Geometric Mean")
plt.title("Speedup Dağılımı (TPC‑H + JOB + Üretim)")
plt.xlabel("Speedup (×)")
plt.legend()
plt.tight_layout()
plt.show()
Bu grafikten ne anlıyoruz?
Sonuç: Metodoloji şeffaf, metrikler kapsamlı ve istatistiksel kanıtlar sağlam. %81 hızlanma sadece bir başlık değil, geometric mean + güven aralığı + p‑value üçlüsüyle desteklenmiş bir gerçeklik 🎉
Hazırsan bu modeli artık gerçek hayat'a sokma vakti geldi. Notebook'taki "harika" sonuçlar production'da bazen fena halde yanıp tutar. Şimdi adım adım, benim başıma gelenler ve pratik çözümler üzerinden gidelim 🚀
Üç ana yol denedim, her birinin artı/eksi'si var:
| Yöntem | Ne Zaman İşe Yarar? | Dikkat Edilmesi Gerekenler |
|---|---|---|
| ONNX Export + ONNX Runtime | Hafif, bağımlılık az, C++ içinde embed edilebilir | Dinamik shape'ler için dynamic_axes tanımı şart, operator destek kontrolü unutma |
| TorchServe | PyTorch ekibi tarafından geliştirildi, model versioning, metrics hazır | Java tabanlı, ek bir süreç yönetmek gerekiyor, cold-start latency'si olabilir |
| Triton Inference Server | Batch inference, model pipeline, GPU paylaşımı, dynamic batching killer feature | Config (model repository, config.pbtxt) karmaşık gelebilir, kaynak tüketimi daha yüksek |
Benim tercihim: Triton. Neden mi? Dynamic batching sayesinde tek bir sorgu gelse bile GPU'yu verimli kullanıyor, latency budget'ımızın altında kalıyor. 🎯
Postgres'in planner_hook mekanizması, sorgu planlanmadan hemen önce devreye girer. Burada modelimize sorgu özelliklerini (join sayısı, tablo boyutları, filtre seçiciliği vb.) gönderip, modelin tahmin ettiği en iyi planı zorlayabiliriz.
Alternatif: pg_hint_plan extension'ı ile /*+ ... */ hint'leri enjekte etmek. Ben hook yolunu seçtim çünkü:
Hook akışı özetle:
planner_hook çağrılır → Query *parse alır.num_joins, total_rows_estimate, has_index...).plan_id döner → PlannedStmt içine custom plan koyarız.| Konu | Pratik Kural |
|---|---|
| Latency Budget | Model inference + network < 2 ms (99th percentile). Aksi takdirde planner'ın kendi maliyeti modelin kazancını yiyor. |
| Batching | Triton'da max_batch_size: 32, preferred_batch_size: [8, 16, 32] ayarladım. Tek sorgu gelirse bile 100 µs içinde batch dolduruluyor. |
| Fallback Mekanizması | ```c\nif (model_call_failed |
| Monitoring | model_inference_latency_ms, fallback_rate, plan_quality_delta metriklerini Prometheus'a pushla. Grafana dashboard'ında fallback_rate > %5 alarmı koy. |
Anecdote: İlk canlı denemede Triton'a gRPC yerine HTTP/JSON ile istek atıyordum. "Neden 5 ms latency?" diye günlerce debug yaptım. Sonra gRPC'ye geçtim, serialization overhead yüzde 70 düştü. Ders: Production'da binary protocol (gRPC/Protobuf) şart, JSON sadece test için.
Başka bir sürpriz: Cold start'ta Triton modeli GPU'ya yüklüyor, ilk 3-4 sorgu 50 ms sürüyordu. Çözüm: model_warmup script'i ile deployment sonrası dummy istekler göndermek. Artık ilk gerçek sorgu da 1.2 ms'de dönüyor. ✅
planner_hook + Model InferenceAşağıda minimal bir iskelet var. Gerçek hayatta:
grpc_c ile yazarsınız (veya basit HTTP libcurl).parse ağacını dolaşırsınız.plan_id'yi PlannedStmt'e map'lersiniz./* pg_ml_planner.c -- Postgres 16 için planner_hook örneği */
#include "postgres.h"
#include "optimizer/planner.h"
#include "utils/elog.h"
#include "utils/guc.h"
#include <grpc/grpc.h>
#include <grpc/support/log.h>
/* Triton gRPC client wrapper (basitleştirilmiş) */
static int
call_triton_model(const float *features, int n_features, int *out_plan_id)
{
/* TODO: gRPC stub ile ModelInfer isteği gönder, sonucu parse et */
/* Başarılıysa 0, hata/timeout olursa -1 döndür */
return 0; /* placeholder */
}
/* Planner hook fonksiyonu */
static PlannedStmt *
ml_planner_hook(Query *parse, const char *query_string,
int cursorOptions, ParamListInfo boundParams)
{
PlannedStmt *result;
float features[8];
int n_features = 0;
int plan_id = -1;
/* 1. Basit özellik çıkarımı */
features[n_features++] = (float)count_joins(parse); /* join sayısı */
features[n_features++] = (float)estimate_total_rows(parse); /* tahmini satır */
features[n_features++] = (float)has_usable_index(parse); /* 0/1 */
/* ... diğer özellikler ... */
/* 2. Model çağrısı (timeout ile) */
if (call_triton_model(features, n_features, &plan_id) == 0 && plan_id >= 0)
{
/* 3. Model planı varsa custom plan oluştur */
result = build_custom_plan(plan_id, parse, query_string,
cursorOptions, boundParams);
if (result)
return result; /* model planı kullan */
}
/* 4. FALLBACK: Varsayılan planner */
elog(DEBUG1, "ML planner fallback -> standard planner");
return standard_planner(parse, query_string, cursorOptions, boundParams);
}
/* Modül yükleme */
void
_PG_init(void)
{
planner_hook = ml_planner_hook;
}
/* Modül boşaltma */
void
_PG_fini(void)
{
planner_hook = NULL;
}
Bu sayede ne oluyor?
elog(DEBUG1, ...) ile fallback olaylarını logluyoruz → metriklerle izleyebiliyoruz.model_repository'de test edildi.dynamic_batching ayarlandı, warmup scripti CI/CD'ye eklendi.pg_ml_planner extension'ı CMake/Makefile ile derlenip shared_preload_libraries'a eklendi.model_latency_p99, fallback_rate, plan_cost_diff görüntüleniyor.Özet: Modeli production'a almak sadece modeli kaydetmek değil; serving altyapısı, Postgres hook, fallback, observability ve anekdotlerle dolu bir süreç. Yukarıdaki iskeletle başla, metrikleri izle, iterasyon yap. Bir sonraki yazıda A/B test ve plan kalitesi ölçümü'ne dalacağız. 🚀
Hadi, kodu derle, Triton'a modeli yükle ve ilk sorguyu izle — o an mağuc oluyor! ✨
Hadi sık gördüğümüz dört başlıca tuzak üzerinde duralım, neden sıkıntı yarattığını anlayalım ve hemen uygulayabileceğiniz mitigasyon stratejilerini görelim 🚀
Ne oluyor? Eğitim verinizle production veriniz arasındaki dağılım farkı büyüdüğünde model performansı düşer.
Mitigasyon
population stability index (PSI) veya KS test ile otomatik uyarı ayarlayın.Örnek (Python – drift kontrolü)
import pandas as pd
from scipy.stats import ks_2samp
def check_drift(ref: pd.Series, cur: pd.Series, threshold: float = 0.05) -> bool:
"""KS testi ile drift var mı diye bakar."""
stat, p = ks_2samp(ref, cur)
return p < threshold # True = drift var
Bu sayede ne oluyor?
Ne oluyor? Sık şema değişiklikleri veya parametre sniffing, veritabanının yanlış planı önbelleğe almasına neden olur.
Mitigasyon
OPTION (RECOMPILE) veya OPTIMIZE FOR UNKNOWN ile kritik sorguları zorlayın.AUTO_UPDATE_STATISTICS açık tutun, manuel UPDATE STATISTICS planlayın.Örnek (SQL Server – zorlu yeniden derleme)
SELECT * FROM dbo.Siparisler
WHERE MusteriId = @MusteriId
OPTION (RECOMPILE); -- Her çalıştırmada yeni plan üretilir
Nasıl çalışıyor?
Ne oluyor? Çok büyük modeller (ör. 1B+ parametre) CPU/GPU’da inference gecikmesine yol açar; çok küçük modeller ise accuracy kaybeder.
Mitigasyon
Örnek (ONNX Runtime – INT8 quantization)
# ONNX modeli INT8'e çevir
python -m onnxruntime.quantization.quantize_static \
--input_model model.onnx \
--output_model model_int8.onnx \
--calibration_data calib_data.npz \
--quant_format QOperator
Bu sayede ne oluyor?
Ne oluyor? Kullanıcı girdisi doğrudan sorgu string’ine concatenate edildiğinde saldırgan istediği SQL’i çalıştırabilir.
Mitigasyon
SELECT/INSERT yetkisi alsın.Örnek (Python – parameterized query)
import sqlite3
def get_user_orders(conn: sqlite3.Connection, user_id: int):
sql = "SELECT * FROM orders WHERE user_id = ?"
return conn.execute(sql, (user_id,)).fetchall()
Nasıl çalışıyor?
| 🎯 Kontrol Noktası | ✔️ Yapıldı mı? |
|---|---|
| Veri drift izleme (PSI/KS) otomatik uyarı kuruldu mu? | |
| Yeniden eğitim pipeline (Cron / Airflow) çalışıyor mu? | |
Plan cache için RECOMPILE / statistics güncelleme stratejisi belirlendi mi? |
|
| Query Store / pg_stat_statements aktif mi? | |
| Model quantization (INT8/FP16) test edildi mi? | |
| Distillation / pruning denemeleri yapıldı mı? | |
| Inference benchmark (latency, throughput) hedeflere ulaşıyor mu? | |
| Tüm SQL sorguları parameterized / prepared statement mi? | |
| ORM / stored procedure kullanımı standartlaştırıldı mı? | |
| Least‑privilege DB rolleri atandı mı? | |
| Input validation / allow‑list katmanı var mı? |
İpucu: Bu checklist’i CI/CD pipeline’ınıza bir gate olarak ekleyin. Her deploy öncesi “✔️” işaretlenmeden geçmesin 🚦
Kısa özet:
Bu dört tuzak ve çözümleri radarı olarak tutarsanız, production’da sürprizler yerine kontrollü, ölçülebilir bir yaşam döngüsü yaşarsınız 🎉
Hadi kendi veritabanınızda ML tabanlı bir optimizasyon deneyimi yapalım. Aşağıdaki 5 adımla hemen başlayabilirsiniz 🚀
-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
Sonra SELECT pg_stat_reset(); ile sayaçları sıfırlayın.
pg_stat_statements view’ından en yavaş 100 sorguyu çekin.queries.csv).
psql -c "\copy (SELECT query, calls, total_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 100) TO 'queries.csv' CSV HEADER"
transformers ile tek satırda yükleyin.# inference.py
import torch
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
import psycopg2
# 1️⃣ Model ve tokenizer
model_name = "your-org/pg-optimizer-4b"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name).eval()
# 2️⃣ Örnek sorgu (CSV’den okuyabilirsiniz)
sql = "SELECT * FROM orders WHERE customer_id = 42 AND created_at > '2024-01-01';"
# 3️⃣ Tokenize & generate
inputs = tokenizer(sql, return_tensors="pt", truncation=True, max_length=512)
with torch.no_grad():
output_ids = model.generate(**inputs, max_length=256, num_beams=4, early_stopping=True)
predicted_plan = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print("🔮 Tahmin edilen plan:\n", predicted_plan)
# 4️⃣ Postgres’e EXPLAIN gönder
conn = psycopg2.connect("dbname=mydb user=postgres password=secret")
cur = conn.cursor()
cur.execute(f"EXPLAIN (FORMAT JSON) {sql}")
real_plan = cur.fetchall()
print("📊 Gerçek plan:\n", real_plan)
cur.close()
conn.close()
Ne oluyor burada?
EXPLAIN (FORMAT JSON) ile PostgreSQL’den gerçek planı çekip yan yana kıyaslarsınız.Trainer API ile).| Kaynak | Link |
|---|---|
| GitHub repo (örnek scriptler, fine‑tune notları) | https://github.com/your-org/pg-ml-optimizer |
| Veri seti (queries.csv örneği) | https://huggingface.co/datasets/your-org/pg-slow-queries |
| Model kartı (pg‑optimizer‑4b) | https://huggingface.co/your-org/pg-optimizer-4b |
| Discord topluluğu | https://discord.gg/pg-ml-optimizer |
| Mailing list (aylık bülten) | https://groups.google.com/g/pg-ml-optimizer |
Hadi başlayalım! 🎉
Küçük bir adım atın, sonuçları paylaşın ve toplulukla birlikte modeli daha da güçlendirelim. 🚀
Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.
All rights reserved