Veri yoğunluklu sistemlerde 5 kırılma noktası ve Laravel production stack
Klavye: ← → ile gezinin, F tam ekran, O genel bakış.

Muhammet Şafak — Sunumlar
Veri yoğunluklu sistemlerde 5 kırılma noktası
En ucuz müdahaleden en pahalısına bir sıra ve her bileşeni gerekçeli bir Laravel production stack'i.
Muhammet Şafak
Bölüm 01
Beş kırılma noktası
Working set'ten ayrı veri katmanına: en ucuz müdahaleden en pahalısına.
Ölçeği belirleyen
Request değil, veri profili.
Bir sistem dakikada 50 request alıp yine de veri tarafından boğulabilir; bir başkası saniyede 5.000 request'i rahat karşılarken veri katmanı el değmemiş durur.
Veri profili
Beş eksen
- Working set / RAM oranıSık okunan veri belleğe sığıyor mu?
- Okuma / yazma oranıYük hangi yöne ağır basıyor?
- Tek tablo boyutuTek bir tablo ne kadar büyüdü?
- Yazma throughput'uPrimary saniyede ne kadar yazıyor?
- Tutarlılık gereksinimiHangi okuma en güncel veriyi görmek zorunda?
Başlangıç mimarisi
Tek primary ile başlamak
PHP-FPM, PostgreSQL'e pgBouncer üzerinden, Redis'e doğrudan bağlanır. Birkaç on GB veri ve dakikada binlerce request için yeterli; working set RAM'e sığdığı sürece.
- PHP-FPMuygulama
- pgBouncerbağlantı havuzu
- PostgreSQL 16tek primary
- Redis 7cache · session · lock
Kırılma noktası 01 · Working set
Sinyal: cache hit ratio
Sık okunan veri RAM’e sığmıyor. %99 üzeri sağlıklı. %95’e doğru düşüş, disk IOPS’u ile birlikte okunduğunda working set’in RAM’den taştığını gösterir.
1SELECT2sum(blks_hit) * 1003 / nullif(sum(blks_hit) + sum(blks_read), 0)4 AS cache_hit_ratio5FROM pg_stat_database;Müdahale sırası
Önce ucuz olanı tüketin
- 1Adım 1: Index disiplini
gereksiz taramayı kes - 2Adım 2: Ölü veriyi ayır
sıcak veriyi küçült - 3Adım 3: Vertical scale
en son, daha çok RAM
Kırılma noktası 02 · Okuma yükü
Okuma replica'ya, yazma primary'ye
Sinyal: CPU yüksek, SELECT baskın, pgBouncer’da cl_waiting birikiyor. sticky, aynı request içinde yazmadan sonraki okumaları primary’ye yollar.
1'pgsql' => [2 'driver' => 'pgsql',3 'read' => ['host' => ['10.0.0.2']], // replica4 'write' => ['host' => ['10.0.0.1']], // primary5 'sticky' => true,6 // ...ortak ayarlar7],Bedeli
Replica'nın getirdiği yeni sorun
- GecikmeReplication lag: replica geride kalır; read-after-write garantisi kaybolur.
- İzleyinpg_stat_replication'da replay_lsn farkını lag_bytes olarak okuyun.
- PrimaryBakiye, stok, yetki gibi tutarlılık isteyen okumalar primary'de kalır.
- SınırRead replica yazma kapasitesine hiçbir şey katmaz.
Kırılma noktası 03 · Tek tablo
Zamana göre bölünen tablo
Sinyal: n_dead_tup birikiyor, last_autovacuum geride kalıyor. Partition’ları elle açmak yerine pg_partman kullanılabilir.
1CREATE TABLE events (2 id bigserial,3 occurred_at timestamptz NOT NULL,4 payload jsonb NOT NULL5) PARTITION BY RANGE (occurred_at);67CREATE TABLE events_2026_05 PARTITION OF events8 FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');Kazanım ve koşul
Partitioning ne kazandırır?
- Partition pruningSorgu yalnız ilgili partition'a bakar.
- Ucuz silmeEski veri DROP TABLE events_2026_01 ile gider.
- Sınırlı vacuumVacuum tüm tabloyu değil, partition'ı tarar.
- Primary keyPartition anahtarını içermeli: (id, occurred_at).
- BRINZamana göre sıralı veride küçük ve ucuz index.
Kırılma noktası 04 · Yazma yükü
Sinyal: I/O wait, checkpoint ve WAL baskısı
- 1Adım 1: Amplifikasyonu azalt
idx_scan = 0 index'leri sil, fillfactor/HOT, batch insert - 2Adım 2: Append verisini çıkar
append ağırlıklı veri primary'den ayrılır - 3Adım 3: Veri alanını böl
en son: functional sharding
Gerçekçi beklenti
Çoğu sistem 4. noktaya varmaz.
Acele etmeyin: çoğu sistem 4. noktaya hiç ulaşmaz, ulaşanların çoğu da aslında 1. noktayı (eksik index, şişmiş working set) atlamış olduğu için erken gelir.
Kırılma noktası 05
Ayrı veri katmanı
- HamleVeri-baskın alan ayrı bir PostgreSQL instance'ına taşınır.
- GeçişpgBackRest restore, logical replication ve kısa bir cutover.
- Bedelİkinci backup, monitoring ve upgrade; iki veri arasında JOIN yok.
- KuralAyırma kararı veri profiliyle verilir, organizasyon şemasıyla değil.
Yanlış teşhis
Yanlış kırılma noktaları
| Görünen | Gerçek | Doğru hamle |
|---|---|---|
| "Veritabanı yavaş" | N+1: uygulama 200 sorgu atıyor | Eager loading; sorguları say |
| "Replica lazım" | Tek sorguda eksik index | EXPLAIN ANALYZE, index ekle |
| "Sharding lazım" | Replica, partitioning denenmedi | Önce 2. ve 3. noktayı tüket |
| "NoSQL'e geçelim" | jsonb, BRIN, partitioning atıl | PostgreSQL'i sonuna kadar kullan |
| "Yazma için cache" | Cache okumayı azaltır, yazmayı değil | 4. noktaya bak |
Ortak hata kalıbı
Hiçbiri yazma kapasitesi eklemez.
Read replica okumayı ölçekler. Cache okumayı azaltır. Partitioning tek tabloyu evcilleştirir. Hiçbiri yazma throughput'unu artırmaz.
jsonb · LISTEN/NOTIFY · partitioning · BRIN · logical replication
%20'si kullanıldı. Bu bir sınır değil.
Çoğu ekip PostgreSQL'in %20'sini kullanıp "PostgreSQL yetmiyor" sonucuna varır.
Özet
En ucuzdan en pahalıya
- 1Adım 1: Working set
index + RAM - 2Adım 2: Okuma yükü
read replica - 3Adım 3: Tek tablo
partitioning - 4Adım 4: Yazma yükü
amplifikasyon + ayrıştırma - 5Adım 5: Veri-baskın alan
ayrı veri katmanı
Bölüm 02
Laravel stack
"Her şey gerekli" demiyorum — gerekli olduğunu anlayana kadar minimum tutmayı savunan biriyim — ama her bileşenin kazandırdığı somut.
Anatomi
Her bileşen neden orada?
İstek Cloudflare'den PHP-FPM'e iner. PHP-FPM üç arka servise ayrı bağlanır: pgBouncer üzerinden PostgreSQL, Redis ve RabbitMQ. Worker'lar Supervisor altında Horizon ile koşar.
- CloudflareDDoS, WAF, rate limiting, TLS, CDN
- NginxTLS, static asset, fastcgi tek config
- PHP-FPMopcache + preload
- pgBouncertransaction mode bağlantı havuzu
- PostgreSQLpgBackRest: full + incremental + WAL PITR
- Redis · RabbitMQcache/session/lock · queue
Redis
Tek instance, üç şapka
Cache (Cache::remember), session (SESSION_DRIVER=redis) ve lock (Cache::lock) tek Redis’te güvenle çalışır; yeter ki bu üç ayar yerinde olsun.
1maxmemory-policy volatile-lru2appendonly no3save 900 1 300 10 60 10000Queue (opsiyonel)
RabbitMQ ile Redis kuyruğu
| Redis | RabbitMQ | |
|---|---|---|
| Persistence | Bellekten servis eder; diske RDB ya da AOF ile iner | Kalıcı mesajları diske yazar |
| Yeniden teslim | retry_after zaman aşımına bakar | Consumer'ın bağlantısına bakar |
| Routing | Yönlendirme kararı uygulamada kalır | Exchange'ler karar verir |
| Monitoring | Horizon; uygulamanın dispatch ettiğiyle sınırlı | Broker düzeyinde yönetim paneli |
Çıkarınca olan
Hangi parçası kaldırılırsa ne kaybedersiniz?
| Bileşen | Çıkarınca olan |
|---|---|
| pgBouncer | Connection storm'larında PostgreSQL bağlantısı tükenir, request'ler 500 atar |
| Supervisor | Worker'lar crash sonrası geri gelmez, alarm kurmak zorundasınız |
| Horizon | Görünürlük çöker, "queue neden yavaş" sorusunu kör cevaplarsınız |
| opcache | Her request PHP dosyasını disk'ten okur ve parse eder, ~5x yavaşlama |
| pgBackRest | pg_dump kalır: PITR gider, incident'ta hata payınız bir günlük yedek |
| Redis lock | Race condition'lara açıksınız, distributed-safe mutex'iniz kalmaz |
Boring stack
Nicel değerli, değişimi kolay.
Boring stack'in gücü tam burada: her parça nicel olarak değerli ve değişimi kolay.

Teşekkürler
Önce veri profilini ölçün, sonra en ucuz müdahaleyi seçin.