Uygulamanız gerçek zamanlı güncelleme gerektiriyorsa 2025'te varsayılan yanıt yönetilen hizmettir: Firebase Realtime Database, Supabase Realtime, Pusher, Ably veya barındırılan WebSocket katmanı. Bunlar bağlı istemcilere güncelleme iletmenin karmaşıklığını gizler. İyi çalışırlar — çalışmayana kadar.
Atlas Verified'da gerçek zamanlı abonelikler içeren yönetilen veri tabanı platformuyla başladık. Sistem büyüdükçe soyutlamanın sınırlarına ulaştık. Bağlantı havuzu kısıtları, gerçek zamanlı güvenilirlik sorunları, PostGIS uzamsal sorgu ihtiyacı ve özel tetikleyici mantığı isteği bizi aynı yöne itti: veri tabanı katmanının kontrolünü almak.
Kendi barındırdığımız PostgreSQL'e geçtik ve gerçek zamanlı sistemimizi PostgreSQL'in yerel LISTEN/NOTIFY mekanizmasını Server-Sent Events ile eşleştirerek kurduk. Bu yazı kararın nedenini, mimarinin işleyişini ve öğrendiklerimizi ele alır.
Yönetilen Platformdan Neden Ayrıldık
Yönetilen veri tabanı platformları çekici değer sunar: kimlik doğrulama, satır düzeyinde güvenlik, gerçek zamanlı abonelikler, otomatik API'ler ve pano — kullanıma hazır. Erken ürünlerde değerlidir. Tek satır arka uç kodu yazmadan işlevsel uygulama geliştirebilirsiniz.
Sorunlar uygulama olgunlaşıp gereklilikleriniz platform varsayımlarından ayrıştığında ortaya çıkar.
Bağlantı sınırları. Yönetilen platformlar veri tabanı bağlantılarını kendi sınırlarını getiren havuzlayıcı üzerinden yönlendirir. Her biri kendi bağlantı havuzuna ihtiyaç duyan API sunucusu, arka plan işçi sistemi, gerçek zamanlı dinleyici gibi hizmetler varsa mevcut bağlantılar hızla tükenir. Gerçek ürün sorunlarını çözmek yerine havuzlayıcı sınırlarını aşmak için mühendislik yapıyorduk.
Gerçek zamanlı güvenilirlik. Yönetilen platformun gerçek zamanlı sistemi PostgreSQL Write-Ahead Log üzerinde Change Data Capture ile çalışıyordu. Mimari açıdan zarif olsa da kontrol edemediğimiz hata türleri getiriyordu. WAL tabanlı çoğaltmanın doğal gecikmesi vardır. Hizmet bazen geride kalıp eski güncellemeler iletti. Ağ kesintisinden sonra yeniden bağlanma güvenilmezdi. Hata ayıklama kontrol etmediğimiz altyapıya erişim gerektiriyordu.
Eklenti gereklilikleri. Uygulamamız liman konumları, sevkiyat rotaları ve gemi konumları gibi coğrafi veriler içeren ticari belgeleri işler. PostgreSQL'in uzamsal eklentisi PostGIS bu sorgular için temeldir. Yönetilen platform PostGIS destekliyordu, ancak ihtiyacımız olan belirli sürüm ve yapılandırmalar her zaman mevcut değildi.
Özel tetikleyici mantığı. Genel "satır değişti" bildirimleri değil, uygulamaya özel olayları tetikleyen veri tabanı tetikleyicileri gerekiyordu. Belgenin işleme durumu değişince onu yükleyen bağlı istemciye bildirmeliyiz. Platformun gerçek zamanlı sistemi tablo düzeyindeydi; bize varlık düzeyi gerekiyordu.
Geçiş kararı maliyet veya ilkeyle ilgili değildi. Pratikti: platform sınırlarını aşmaya, özellikleri kendimiz geliştirmekten fazla zaman harcıyorduk.
LISTEN/NOTIFY: PostgreSQL'in Gizli Yayın/Abone Mekanizması
PostgreSQL, 2010'da çıkan 9.0 sürümünden beri yerleşik yayın/abone mekanizması içerir. 15 yılı aşkın süredir var olsa da LISTEN/NOTIFY çok az kullanılır. Çoğu geliştirici varlığını bilmez.
Mekanizma basittir. Her veri tabanı bağlantısı adlandırılmış kanalda LISTEN çalıştırıp abone olabilir. Her bağlantı NOTIFY çalıştırıp kanaldaki tüm dinleyicilere mesaj gönderebilir. İçerik 8,000 baytla sınırlı dizedir.
LISTEN/NOTIFY'ı gerçek zamanlı uygulamalarda güçlü kılanlar:
Sıfır ek altyapı yükü. Dağıtılacak, izlenecek veya ölçeklenecek ayrı mesaj aracısı yoktur. PostgreSQL varsa yayın/abone vardır. Bu kullanım için Redis, RabbitMQ, Kafka gerekmez.
İşlem garantileri. NOTIFY mesajları yalnızca kapsayan işlem tamamlandığında iletilir. İşlem satır ekleyip bildirim gönderir, sonra geri alınırsa bildirim asla iletilmez. Titiz orkestrasyon olmadan harici mesaj aracılarından alamayacağınız özelliktir.
Tetikleyici entegrasyonu. PostgreSQL tetikleyicileri çalışırken pg_notify çağırabilir. Böylece yazma yolunda uygulama kodu olmadan belirli veriler değiştiğinde veri tabanı dinleyicileri otomatik bilgilendirebilir.
Sınırlar gerçektir ve anlaşılmalıdır:
Kalıcılık yok. NOTIFY tetiklendiğinde kimse dinlemiyorsa mesaj kaybolur. Yeniden oynatma, mesaj kuyruğu veya bağlantısız istemcilere teslim garantisi yoktur. Dinleyiciler yeniden bağlantı ve durum kurtarmayı yönetmelidir.
İçerik boyutu. Bildirimlerde 8,000 bayt sınırı büyük nesneleri NOTIFY ile gönderemeyeceğiniz anlamına gelir. Tablo adı, işlem türü ve birincil anahtar gibi asgari içerik gönderir, gerekirse tam veriyi istemcinin almasını sağlarız.
Tek veri tabanı kapsamı. LISTEN/NOTIFY tek PostgreSQL örneği içinde çalışır. Kopyalara veya kümelere yayılmaz. Tüm yazmaların tek birincile gittiği mimarilerde bu sınırlama değildir.
Mimarimiz: Tetikleyiciler, SSE ve Kanal Çoklama
Parçalar şöyle bir araya gelir.
Veri tabanı tetikleyicileri gerçek zamanlı güncellemelerin önemli olduğu belgelere, mesajlara, konuşmalara, bildirimlere, sevkiyat doğrulamalarına ve başka tablolara kurulur. Satır eklenince, güncellenince veya silinince tetikleyici tablo adı, işlem türü, birincil anahtar ve filtre sütunlarını içeren JSON ile pg_notify çağırır.
Özel dinleyici bağlantısı API sunucumuzda bildirim kanalında LISTEN çalıştırır ve tüm tetikleyici olaylarını alır. Sorgu bağlantı havuzundan ayrı, yalnızca bildirim dinleyen uzun ömürlü özel PostgreSQL bağlantısıdır.
Olay veri yolu dinleyiciden bildirimleri alır ve aboneliklerine göre bağlı istemcilere yönlendirir. Belirli sevkiyat kimliğiyle eşleşen belgelere abone istemci yalnızca o sevkiyatın belgelerine ilişkin bildirimleri alır.
Server-Sent Events bildirimleri tarayıcıya iletir. Her bağlı istemci istediği kanal ve filtreleri belirterek API sunucusuna tek SSE bağlantısı açar. Olay veri yolu bildirimleri aboneliklerle eşleştirip ilgili SSE bağlantılarından gönderir.
WebSockets Yerine Neden SSE
Bu karardan sürekli memnun kaldık. Kullanımımız için önemli tüm boyutlarda Server-Sent Events, WebSockets'ten basittir.
Tasarımı gereği tek yönlü. Gerçek zamanlı ihtiyaçlarımız sunucudan istemciyedir: sunucu veri değişince istemciye söyler. İstemcinin sunucuya gerçek zamanlı mesaj göndermesi gerekmez — standart HTTP istekleri kullanır. SSE'nin tek yönlü modeli gerçek gerekliliğimize uyar.
HTTP'ye yerel uyum. SSE bağlantıları belirli içerik türüyle standart HTTP istekleridir. Özel yapılandırma olmadan her HTTP vekili, yük dengeleyici ve CDN üzerinden çalışır. WebSockets, bazı vekillerin doğru işlemediği ve bazı kurumsal güvenlik duvarlarının tamamen engellediği HTTP Upgrade gerektirir.
Otomatik yeniden bağlanma. Tarayıcının EventSource API'si yeniden bağlanmayı yerel yönetir. Bağlantı kesilirse tarayıcı üstel artan beklemeyle otomatik bağlanır. WebSockets'te mantığı kendiniz uygular ve en az bir kez yanlış yaparsınız.
WebSockets'in üstün olduğu çift yönlü iletişime ihtiyacımız yok. Gerçek zamanlı istemciden sunucuya mesaj gerektiren uygulamalarda WebSockets doğrudur. Sunucudan istemciye bildirim gönderenlerde SSE doğrudur.
ORM Olmadan Sorgu Katmanı Kurmak
Yönetilen platformdan ayrılınca otomatik sorgu API'sinden de ayrıldık. API sunucumuza veri tabanı erişim katmanı gerekiyordu.
Standart seçenekler Prisma, TypeORM, Drizzle gibi ORM'ler veya sürücüyle ham SQL'dir. Üçüncü yolu seçtik: altta ham SQL üretip çalıştırırken zincirlenebilir API sunan hafif sorgu oluşturucu.
Tanıdık API. Mevcut kodumuz platformun istemci kitaplığına göre yazılmıştı. Uyumlu API, geçişte asgari kod değişikliği demekti. Önceden platform istemcisinden çalışan sorgular aynı sözdizimiyle oluşturucumuzdan çalışabilirdi.
Sihir yok. ORM'ler göremediğiniz, bazen kontrol edemediğiniz SQL üretir. Sorgu yavaşsa üretilen SQL'i okumalısınız; bu genellikle onu üreten ORM çağrısına benzemez. Oluşturucumuz öngörülebilir SQL üretir — her zincirli yöntem belirli SQL yan tümcesine eşlenir ve sorguyu her zaman inceleyebilirsiniz.
İç içe birleştirmeler. Ticaret verisi ilişkiseldir. Sevkiyatın belgeleri, belgelerin düğümleri, düğümlerin doğrulama kontrolleri vardır. Her seferinde karmaşık JOIN sorgusu yazmadan iç içe seçimler ifade edip iç içe sonuç almak geliştirici deneyimini önemli iyileştirir.
Sonuç, birkaç yüz satır kodla veri tabanı etkileşimlerimizin büyük çoğunluğunu işleyen ve her zaman okuyup optimize edebileceğimiz SQL üreten sorgu katmanıdır.
Bağlantı Havuzu Bütçeleme
PostgreSQL'i kendiniz barındırdığınızda bağlantı bütçesi sizindir. max_connections ayarı mutlak tavanı belirler ve her bağlantı bellek tüketir.
Bağlantıları hizmetler arasında paylaşılan sınırlı kaynak bütçesi olarak düşünmenin, planlamayan ekipleri zorlayan sorunları önlediğini gördük.
Her hizmet azami bağlantı sayısını bildirir. Tüm azamilerin toplamı PostgreSQL sınırının çok altında kalmalı; psql oturumları, geçiş betikleri, izleme araçları gibi anlık bağlantılara alan bırakmalıdır. Açık görünür, ancak çoğu ekip üretimde bağlantılar tükenene kadar yapmaz. Önceden bütçeleyerek gece 3'teki o çağrıdan kaçındık.
Büyük Ölçekte Güvenli Geçişler
Yönetilen platformdan kendi PostgreSQL'imize geçmek yüzlerce tablo, işlev, tetikleyici ve endeksi taşımayı gerektirdi. Ancak büyük zorluk uygulama geliştikçe çalışmayı sürdüren geçiş sistemi kurmaktı.
Otomatik sistemimiz şöyle çalışır: geçiş dosyaları zaman damgasıyla adlandırılır ve özel dizinde bulunur. Çalıştırıcı dizini tarar, uygulanan geçişleri kaydeden takip tablosuyla karşılaştırır ve bekleyenleri sırayla çalıştırır. Her geçiş işlem içinde çalışır — başarısızsa temiz geri alınır ve sorun çözülene kadar dağıtımı engeller.
Geçiş çalıştırıcı her dağıtımda uygulama başlamadan otomatik çalışır. Böylece şema değişiklikleri manuel adım olmadan dağıtımın parçası olarak uygulanır. Geliştirici zaman damgalı SQL dosyası oluşturur, depoya gönderir ve sonraki dağıtım uygular.
Güvenliği koruyan temel kararlar: kısmen uygulanınca başarısız olmaması için geçişler idempotent korumalar kullanır. Her geçiş insan girdisi olmadan çalışan bağımsız SQL betiği olmalıdır. Başarısızsa işlem tamamen geri alınır — uygulama kısmen uygulanmış şemayla başlamaz.
Kazandıklarımız
Özel gerçek zamanlı sistemle kendi PostgreSQL'imize geçmek önemli mühendislik yatırımıydı. Ancak getiriler büyüktü:
Güvenilirlik. Gerçek zamanlı bildirimler veri değişiminden milisaniyeler sonra gelir. WAL çoğaltma gecikmesi, kritik yolda üçüncü taraf hizmet kesintisi yoktur.
Hata ayıklanabilirlik. Sorun olduğunda tüm yolu izleyebiliriz: tetikleyici çalışır, bildirim gönderilir, olay veri yolu yönlendirir, SSE iletir. Her adım kodumuzda, günlüklerimizde, altyapımızdadır.
Esneklik. Yeni tabloya gerçek zamanlı özellik eklemek, tetikleyici kuran tek SQL geçişidir. Platform yapılandırması, üçüncü taraf panosu, destek talebi yoktur.
Performans. Ayarlanmış havuzla doğrudan PostgreSQL bağlantıları, platform altyapısından yönlendirilen bağlantılardan daha iyi çalışır. Geçişten sonra sorgu gecikmesi önemli ölçüde düştü.
Karşılığı operasyonel sorumluluktur. Veri tabanını yönetir, yedekleri alır, çoğaltmayı izler ve kapasite planlarız. PostgreSQL operasyonlarına alışkın ekip için yönetilebilirdir. Olmayan ekip için yönetilen platformun soyutlamaları sınırlarına değebilir.
Atlas Verified, özel gerçek zamanlı altyapıyla kendi barındırdığı PostgreSQL üzerinde çalışır ve küresel tedarik zincirinde günlük binlerce ticari uyum doğrulaması işler.
