Uzmanlık
Backend geliştirici: API'nin arkasındaki kuyruk, veritabanı ve sunucu
Senkron bir akışı RabbitMQ'ya taşıdığımda gördüm ki kullanıcının beklemesi yıllardır kazara bir sıra üretiyormuş; asıl iş, o sıra kalkınca yüzeye çıkanları düzeltmekti.
- Başlangıç
- 2010 Başlangıç
- yıl
- 16 yıl
- yazı
- 26 yazı
- proje
- 4 proje
Bu ekosistemde ben
Backend geliştirme benim için bir dil değil, bir sorumluluk sınırı: kullanıcının gördüğü ekranın arkasında kalan her şey. Sözleşme, kuyruk, veritabanı ve sunucu aynı işin dört yüzü; biri hakkında verilen karar diğer üçünü de bağlıyor.
Gözlemlenebilirlik, altyapının kod olarak yönetilmesi ve geri alma stratejisi gibi başlıkları mimari konular olarak sade.dev tarafında yazıyorum.
Backend'de ne yapıyorum
Ortak noktaları kullanıcının gördüğü ekranın arkasında kalmaları: sözleşme, kuyruk, veritabanı ve sunucu.
-
API sözleşmesi
Yanıt biçimi, hata sözleşmesi ve sürümleme kararı kod yazılmadan önce netleşiyor. Aksi hâlde istemci tarafındaki her ekip kendi varsayımını yazıyor ve o varsayım altı ay sonra sizin sorununuz oluyor.
-
Asenkron teslim hattı
Kuyruk, consumer ve outbox relay'i; senkron beklemeyi kullanıcının önünden alan yapı. Aynı disiplinin ikinci yarısı idempotency: bir isteğin yan etkisiz olarak tekrar edilebilmesi.
-
Veritabanı kararları
Index ve plan davranışı sezgiyle değil ölçümle seçiliyor; sezgi burada özellikle kötü çalışıyor. Ölçümün kendisi bu sayfada değil, yöntemi ve ortamıyla birlikte research defterinde duruyor.
-
Arama ve sunucu katmanı
İlişkiselin yetmediği sınırda Elasticsearch; ortam Docker ile, deploy hattı GitHub Actions ile, önde nginx. Kapsam uygulama seviyesinde kalıyor.
Kuyruk teslim garantisi vermez
Sık gördüğüm yanılgı, iş kuyruğa taşındığında dayanıklı hâle geldiğinin sanılması. Zincir sırayla okunur; her adım bir öncekinin açık bıraktığı yeri kapatıyor.
- 01 Yazma ile publish iki ayrı sistem
- Veritabanına INSERT ile kuyruğa publish arasında atomiklik yok. Yazma başarılı olup publish ağ takılmasıyla düşerse veritabanında kayıt vardır ve event hiç yola çıkmamıştır.
- 02 Mesaj aynı transaction'a yazılır
- Mesaj kuyruğa doğrudan basılmıyor; aynı transaction içinde bir outbox tablosuna satır olarak yazılıyor. Kayıt ile mesajın kaderi böylece tek bir işleme bağlanıyor.
- 03 Relay satırları yayımlar
- Ayrı bir relay o satırları okuyup kuyruğa veriyor. Mesajın kaybolmadığı yer burası — ama garantinin tamamlandığı yer değil.
- 04 Garantiyi idempotent tüketici tamamlar
- Outbox at-least-once verir: mesaj kaybolmaz, tekrar edebilir. İşlenen her event id'si aynı transaction içinde processed_messages tablosuna yazılınca tekrar zararsızlaşıyor.
Veri ve sunucu tarafında kapsamım
Veritabanı kararlarını sezgiyle vermiyorum; sunucu tarafında ise kapsam uygulama seviyesinde kalıyor.
-
Ölçülmüş index kararları
Index tercihleri, sorgu planları ve tablo büyüdükçe değişen davranış ölçülerek seçiliyor. Ölçüm olmadan verilen karar, bugün hızlandırdığı sorguyu yarın yavaşlatabiliyor.
-
Tam metin arama katmanı
İlişkisel deponun yetmediği yerde devreye giriyor. Kök bulma, eş anlamlı genişletme ve alakalılık sıralaması ilişkiselde de var; ayrı bir katmanı getiren şey bu işlerin varlığı değil, ölçekte ve çok dilli metinde nereye kadar taşıdığı.
-
Uygulama seviyesinde sunucu
Çalışma ortamının paketlenmesi, test ve dağıtım hattının kurulması, uygulamanın önündeki katmanın yapılandırılması.
Değişikliği güvenli kılan sıra
Arka uçta asıl zorluk yeni bir özelliği yazmak değil, çalışan bir sistemi bozmadan değiştirmek.
- 01 Şemayı eski ve yeni kodun aynı anda ayakta olduğu pencereye göre planlamak
- Eski ve yeni kod bir süre birlikte çalışıyor; şema değişikliği o pencereyi hesaba katmadan yazıldığında kırılan taraf her zaman çalışan sistem oluyor.
- 02 Veri taşımasını geri alınabilir adımlara bölmek
- Tek seferde yürüyen bir taşımanın geri dönüşü yok. Adımlara bölünmüş bir taşıma, yanlış giden adımda durabiliyor.
- 03 Test yüzeyini üç yerde ayrı tutmak
- İş kuralının doğrudan sınandığı yer, dış sınırın taklit edildiği yer ve akışın uçtan uca yürütüldüğü yer. Değişikliğin bedeli, getirdiği rahatlıktan küçük kalmalı.
Altyapıyı mimari konu olarak burada yazmıyorum
Gözlemlenebilirlik, IaC ve rollback stratejisi gibi başlıklar bu sayfada kendi başlarına durmuyor; buradaki kapsamım uygulama seviyesinde kalıyor. Altyapıyı bir mimari konu olarak yazdığım yer sade.dev.
Bu alandaki son yazılar
Partial index'i hız için seçmeyin
10 milyon ölü satırlı bir Postgres kuyruk tablosunda dört index stratejisi: kazanan beklediğim gerekçeyle kazanmadı, bir ayar onu 1.635 kat yavaşlattı.
Aynı mesaj, farklı sonuç: event-driven mimaride determinizm
Event-driven mimaride aynı mesaj neden farklı sonuç üretir? Gerçek bir fatura akışından: determinizmi bozan gizli girdiler ve geri kazandıran dört hamle.
Bu alandaki ölçümler
PHP ile Go'yu aynı OAuth2 API'de ölçtüm: 10.000 yazmada fark yok, 50.000 okumada var
Her istekte OAuth2 token'ı doğrulayıp PostgreSQL'e yazan ya da okuyan aynı API, dört çekirdekte PHP-FPM, FrankenPHP worker ve Go ile saniyede 10.000 yazmayı ve 50.000 okumayı kaç CPU'yla karşılıyor?
Bulgu
Saniyede 10.000 yazmada üç aday da hedefi beş tekrarın beşinde tutturdu, p99 üçünde de 2,5 ms'yi aşmadı: bu yükte dil bir kapasite kalemi değil. Saniyede 50.000 okumayı dört çekirdekte yalnız Go tutturdu (p99 6,45 ms); PHP-FPM 23.528'de, FrankenPHP 22.859'da kaldı. İstek başına CPU okumada Go'da 64, FrankenPHP'de 117, PHP-FPM'de 168 mikrosaniye. 50.000 okuma için instance hesabı Go'ya 3,4, iki PHP adayına 8,2–8,8 çekirdek yazdırıyor. FrankenPHP'nin CPU tasarrufu kapasiteye dönmüyor: doygunken dört çekirdeğin birini boşta bırakıyor.
bugün ölçüldü
Yedi PHP framework'ü aynı yükte ölçtüm: fark, istek gerçek iş yaptıkça küçülüyor
Aynı donanımda, aynı PHP sürümünde ve aynı yedi rotayla, Laravel, Symfony, CodeIgniter, Yii2, Phalcon, Laminas ve Slim saniyede kaç isteği ne gecikmeyle karşılıyor?
Bulgu
Boş bir rotada en hızlı ile en yavaş arasında 4,4 kat var (Slim 25.975, Laravel 5.966 istek/sn). Ama istek gerçek iş yapmaya başlayınca fark kapanıyor: veritabanından tek satır çeken bir istekte 3,7 kata, yirmi satır çekende 3,5 kata iniyor. Phalcon boş rotada üçüncüyken veritabanı isteğinde beşinciye düşüyor — C eklentisi olmak, sorgu beklerken bir işe yaramıyor. Asıl pahalı olan şey framework seçimi değil: Laravel'in kendi varsayılan web middleware grubu, aynı yanıtı 5.858'den 2.176 istek/sn'ye düşürüyor — yani tek bir varsayılan, framework'ler arasındaki farkın çoğundan daha pahalı.
27 gün önce ölçüldü
Partial index on beş dakikada üç yüz beş kat şişti — ve autovacuum bir kez bile gelmedi
Sürekli churn altındaki bir kuyruk tablosunda partial index'in küçüklüğü kalıcı mı, ve varsayılan autovacuum ayarları ona yetişiyor mu?
Bulgu
Canlı küme beş bin satırda sabit dururken partial index 0,125 MB'dan 38,2 MB'a çıktı — üç yüz beş kat. Küçüklüğü canlı satır sayısından geliyor, şişme hızı iş hacminden, ve ikisi arasında hiçbir bağ yok. Composite index oransal olarak daha az şişti (%42) ama mutlak olarak daha çok (+126 MB) ve şişerken belleğe sığmayı bıraktı: gecikmesi 0,52 ms'den 61 saniyeye çıktı, kuyruğu 126 bine tırmandı. On beş dakikada 1,75 milyon ölü satır birikti ve autovacuum **bir kez bile koşmadı** — varsayılan eşik tablonun tamamına göre ölçekleniyor (50 + 0,2 × 10 milyon ≈ 2 milyon), churn ise küçük canlı kümede oluyor.
25 gün önce ölçüldü
Bu alanda geliştirdiklerim
Üretim veritabanına giden ad-hoc SQL'i onaya, maskelemeye ve değiştirilemez bir ize bağlayan self-hosted portal; QueryProxy ürününe dönüştü.
Şu an ne yapıyor
Geliştiricinin yazdığı SQL'i üretim veritabanında doğrudan değil, bir onaydan geçirerek çalıştırıyor; sonuçlar diske yazılırken maskeleniyor ve her istek değiştirilemez bir kayda düşüyor. Prod erişimi tek kişide toplanmış ekipler kurabilir.
Çok hesaplı gelir-gider takibini tek modelde toplayan finans çekirdeği; web + iOS + Android'de çalışan Parantaj'a dönüştü.
Şu an ne yapıyor
Tek hesap/işlem modeli çoklu hesap yönetimini, bütçeyi, hedefleri ve raporlamayı aynı çekirdekte taşıyor; web, iOS ve Android istemcileri yayında. Kişisel ve kurumsal finansını tek yerden takip etmek isteyen kullanabilir.
Swipenor: skoru istemci hesaplarsa sunucu neye güvenir?
Kaydırmalı doğru/yanlış bilgi yarışması; asıl mesele oyun değil, istemcinin ürettiği skoru imzayla doğrulanabilir kılmak.
Şu an ne yapıyor
Kaydırmalı doğru/yanlış bilgi yarışması bir mobil uygulama olarak çekirdek döngüsüyle uçtan uca çalışıyor; skoru istemci üretse de server_nonce ve HMAC imzasıyla sunucu tarafında doğrulanıyor. swipenor.com uygulamanın tanıtım sayfasıdır, oyunun kendisi değil.
Bu alanda üretime çıkan işler
QueryProxy
Devam ediyorFounder & Developer
QueryProxy, geliştiricilerin üretim veritabanına kimlik bilgisi almadan erişebilmesi için geliştirdiğim self-hosted bir sorgu onay portalıdır. Yazılan SQL çözümlenerek denetlenir, bir DBA onayından geçtikten sonra kuyrukta çalışır, sonuçlar diske yazılırken maskelenir ve her adım değiştirilemez bir denetim kaydına düşer.
Parantaj
Devam ediyorOwner
Kişisel ve kurumsal finansal yönetim platformu. Gelir-gider takibi, bütçe planlama, detaylı raporlama ve çoklu hesap yönetimi sunar.
BabelQueue
Devam ediyorFounder & Developer
BabelQueue, farklı dillerde yazılmış servislerin aynı kuyruğu serileştirme kilidine takılmadan paylaşmasını sağlayan, dilden bağımsız bir mesaj kuyruğu standardıdır. PHP'nin serialize()'ı gibi dile özgü formatlar yerine; her dilin doğal olarak okuyabildiği, schema_version 1'de dondurulmuş katı bir JSON zarfı tanımlar. Redis ve RabbitMQ üzerinde, sidecar ya da broker eklentisi olmadan, %2'nin altında ek yükle çalışır.
Bu alanda sorulanlar
Bu eksende 24 soru yanıtlandı.
Container'daki Go worker'ım SIGTERM almıyor ve graceful kapanmıyor, bu bir PID 1 sorunu mu?
`CMD`'yi exec form'a çevirin ve `os.Interrupt` yerine `signal.NotifyContext(..., syscall.SIGTERM)` kullanın; tini yalnız fork varsa gerekir.
API hata gövdelerimi RFC 7807 problem+json biçimine mi taşımalıyım?
7807 yerine onu güncelleyen RFC 9457'yi baz alın, üretimi tek bir exception handler'a toplayın ve `type` URI'sini sözleşme gibi yönetin.
Heap erişimini önlemek ve index-only scan almak için covering index'e (INCLUDE) ne zaman geçmeliyim?
Sıcak sorguda `EXPLAIN (ANALYZE, BUFFERS)` baskın maliyet olarak heap erişimini gösteriyorsa `INCLUDE` ekleyin, `Heap Fetches: 0` için autovacuum'u sıkın.
Birlikte kullandığım teknolojiler
-
RabbitMQ
Servisler arası asenkron teslim; outbox relay'inin çıkış kapısı.
-
Redis
Önbellek, sayaç ve kısa ömürlü kilit.
-
PostgreSQL
Kuyruk tablosu ve index davranışını üzerinde ölçtüğüm veritabanı.
-
MySQL
En uzun süre birlikte çalıştığım ilişkisel depo.
-
Elasticsearch
İlişkiselin yetmediği yer: tam metin arama ve log analizi.
-
Docker
Geliştirme ortamı ve dağıtım paketi.
-
nginx
Uygulamanın önündeki katman.
-
GitHub Actions
Test ve deploy hattı; sunucuya SSH ile çıkan adımlar burada tanımlı.
Sık sorulanlar
7 soru
-
Backend tarafında kaç yıldır çalışıyorsun?
2010'den bu yana, 16 yıldır. PHP'ye başladıktan iki yıl sonra işin ağırlığı sözleşmeden kuyruğa kaydı ve orada kaldı; bu sitede kayıtlı 26 yazının çıktığı yer o dönem.
-
Backend tarafında en sık hangi problemle geliyorlar?
Çoğunlukla "kuyruğa taşıdık ama bazı işler kayboluyor" ile geliyorlar. Cevap neredeyse her seferinde aynı yerde: veritabanına yazma ile kuyruğa publish etme iki ayrı sistem ve aralarında hiçbir atomiklik garantisi yok.
-
API tasarımına nereden başlıyorsun?
Sözleşmeden. Yanıt biçimi, hata sözleşmesi ve sürümleme kararları kod yazılmadan önce netleşmezse, istemci tarafındaki her ekip kendi varsayımını yazıyor. Bu sitede 26 yazının önemli bir kısmı bu üç başlığın etrafında.
-
Kuyruk kullanmak teslim garantisi verir mi?
Hayır. Transactional outbox mesajın kaybolmamasını sağlar ama size at-least-once verir — mesaj tekrar edebilir. Garantiyi tamamlayan şey kuyruk değil, tüketicinin idempotent olmasıdır.
-
Index kararlarını nasıl veriyorsun?
Ölçerek. Partial index'i yalnızca "küçük ve hızlı" diye seçmek bir tuzak: generic plan devreye girdiği anda partial index kullanılamıyor. İyi haber, uçurumun çitli olması — Postgres bu geçişi kendiliğinden yapmıyor, oraya düşmek için ayarı elle zorlamak gerekiyor. Ölçümün tamamı research defterinde duruyor.
-
Sunucu tarafını da sen mi kuruyorsun?
Evet, uygulama seviyesinde. Docker'la ortamı, GitHub Actions'la deploy hattını ve nginx'i kendim kuruyorum; altyapının kendisini bir mimari konu olarak yazdığım yer sade.dev.
-
Kaç projede bu yığını kullandın?
Bu sitede kayıtlı projelerin 4 tanesi doğrudan bu kuyruk/veritabanı yığınının üstünde duruyor.
Birlikte çalışalım
Bu ekosistemde bir işiniz varsa, ne yaptığınızı anlatın; nasıl kurulacağını konuşalım.