Aynı kuyruğu iki dilin serileştirme kilidine takılmadan paylaşması
Dile özgü serileştirme yerine dondurulmuş bir JSON zarfı; polyglot kuyruk standardı BabelQueue'ya dönüştü.
Şu an ne yapıyor
Dondurulmuş JSON zarfı dört dilde — PHP, Python, Go, Node.js — aynı baytları okutuyor; sidecar ya da broker eklentisi gerekmiyor. Polyglot kuyruk kuran ekipler spesifikasyonu ve SDK'ları bugün kullanabilir.
- Başlangıç
- Mart 2026 — Haziran 2026
- Labs’tan çıkış
Tür
Teknolojiler
Bu iş ürüne dönüştü:
BabelQueuePHP’nin serialize()’ı ile Python’un pickle’ı aynı işi yapar; birbirlerinin ürettiği
baytları okuyamazlar. Sorun formatın kendisinde değil, formatın bir dile
ait olmasında — ve kuyruk, iki ucunda farklı diller duran ilk yer.
Dile hapsolmuş bayt
Polyglot mimaride sinsi olan şu: bir servis kuyruğa kendi dilinin formatıyla yazıyor, başka bir dildeki tüketici o baytları açamıyor. Mesaja gömülen dile özgü tip bilgisi işi üreten dilin içine hapsediyor.
Denenen şey, araya sidecar ya da broker eklentisi koymadan, her dilin standart
kütüphanesiyle ayrıştırabildiği tek bir zarf tanımlamaktı. Zarf iş kimliğini,
dağıtık izleme için trace_id’yi, veri yükünü, metadata’yı ve deneme sayısını
taşıyor.
Dondurulmuş zarf
Katı bir JSON zarfı — schema_version 1’de dondurulmuş — ve bu zarfın Redis ile
RabbitMQ üzerinde çalışan referans uygulaması. Ölçüm hedefi ek yükün %2’nin
altında kalmasıydı; sözleşme “bir dil üretir, başka bir dil tüketir, aynı
baytlar” ilkesine göre yazıldı.
Kimliği sınıf adından ayırmak
İki karar zarfın biçiminden daha çok belirledi.
İş kimliği sınıf adı değil, URN tabanlı kararlı bir tanımlayıcı. Kuyruğa
yazılan işin “ne olduğunu” söylemenin en kolay yolu sınıf adını yazmaktır ve
tek dilli bir sistemde işe yarar. İki dilde yaramaz: App\Jobs\SendInvoice
Python tarafında bir sınıf değil, anlamsız bir dize. Kimlik, onu üreten dilin
ad alanından bağımsız olmak zorundaydı.
Şema schema_version 1’de donduruldu. Genişletilebilir bir sözleşme yazmak
daha esnek olurdu; alan eklemek serbest kalırdı. Bedeli, bugün yazılan bir
üreticinin yarın eklenen bir tüketiciyle aynı teli paylaşacağının garanti
edilememesiydi. Dondurulmuş şema bu garantiyi veriyor ve karşılığında genişleme
serbestisini alıyor — bir standart için doğru takas, bir uygulama için
olmazdı.
İkisinin ortak sonucu benimsemenin artımlı olabilmesi: BabelQueue olmayan mevcut işler bozulmadan çalışmaya devam ediyor, geçiş kuyruk kuyruk yapılıyor.
Tek jenerik SDK yerine dil başına adaptör
Üçüncü karar dağıtım tarafındaydı. Zarf bir spesifikasyon olduğuna göre tek bir referans kütüphane yazıp “gerisi sizin işiniz” demek yeterdi; okuyan herkes kendi diline taşırdı.
O yol sözleşmeyi kâğıtta bırakırdı. Bir kuyruk standardının benimsenmesi zarfı anlamakla değil, mevcut iş altyapısına takılmasıyla oluyor — ve o altyapı dile değil framework’e ait. Bu yüzden her dil kendi SDK’sıyla ve o dilin gerçekten kullanılan framework adaptörleriyle geliyor. Labs aşamasında kapsam dört dildi: PHP’de Laravel ve Symfony, Python’da Celery ve Django, Go’da Redis/RabbitMQ transport’ları, Node.js’te BullMQ ve NestJS.
Bedeli bakım yüzeyi: dil başına bir SDK, bir sürüm hattı, bir paket kaydı. Karşılığında geçiş, mevcut kuyruk kodunu yeniden yazmayı değil bir adaptör takmayı gerektiriyor.
Spesifikasyona giden yol
Zarf bir spesifikasyona ve çok dilli SDK setine dönüştü: dört dil altıya çıktı — Java’da Spring Boot, .NET’te MassTransit eklendi — ve dağıtım her ekosistemin kendi kanalına bağlandı: Packagist, PyPI, pkg.go.dev, npm, Maven Central, NuGet. BabelQueue artık kendi alan adı olan bir standart. Ayrıntısı portfolyoda.
Bu işin arkasındaki araştırmalar
Tüm araştırmalarPartial index on beş dakikada üç yüz seksen 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,1 MB'dan 38,2 MB'a çıktı — üç yüz seksen 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.
16 gün önce ölçüldü
Partial index kuyruk tablosunu kırk bir kat küçültüyor — planner onu seçtiği sürece
Milyonlarca ölü satır biriken bir Postgres kuyruk tablosunda partial index ne kazandırıyor, ve planner onu ne zaman seçmiyor?
Bulgu
10 milyon ölü satırda partial index saniyede 11.537 iş çıkarıyor, index'siz tablo 7. Composite index'le hız farkı küçük (%6,9) ama boyut farkı değil: 7,6 MB'a karşı 310,4 MB, ve partial tabloyla birlikte büyümüyor çünkü yalnız 5.000 canlı satırı indeksliyor. Asıl bulgu bunların hiçbiri: planner hazırlanmış bir deyimde generic plana geçtiği anda partial index tamamen devre dışı kalıyor — 11.752 tps 7'ye, 0,68 ms 1,1 saniyeye düşüyor. Bin altı yüz yetmiş üç kat. Aynı koşulda composite index etkilenmiyor.
16 gün önce ölçüldü
Postgres partial index'i generic plana çevirmedi: kırk çalıştırma, kırk custom plan
Hazırlanmış bir deyimde Postgres partial index'li sorguyu kendiliğinden generic plana çeviriyor mu — yoksa bin altı yüz yetmiş üç katlık uçuruma ancak elle mi düşülüyor?
Bulgu
Postgres geçişi reddediyor. Partial index'te kırk çalıştırmanın kırkı da custom plan — sayaç 40/0. Reddetmesinin sebebi tam olarak felaketin kendisi: generic plan partial index'i kullanamaz, bu yüzden tahmini maliyeti yüksek çıkar ve planner onu seçmez. Composite index ise ders kitabındaki gibi altıncı çalıştırmada geçiyor (5/35) ve hiçbir şey kaybetmiyor. Yani bin altı yüz yetmiş üç katlık uçurum gerçek ama çitli: ona düşmek için `plan_cache_mode = force_generic_plan` yazmak gerekiyor.
16 gün önce ölçüldü