4 puan yazan GN⁺ 2026-02-28 | 1 yorum | WhatsApp'ta paylaş
  • Web Streams standardı, tarayıcı ile sunucu arasında tutarlı veri akışı için tasarlandı; ancak bugün karmaşıklık ve performans sınırlamaları nedeniyle geliştirici deneyimini düşürüyor
  • Mevcut API, lock yönetimi, BYOB, backpressure gibi tasarımsal kısıtlar yüzünden hem kullanımda hem uygulamada gereksiz yük oluşturuyor
  • Cloudflare, async iteration tabanlı yeni bir stream modeli öneriyor ve bu yaklaşımın 2 kat ile 120 kata kadar daha hızlı performans gösterdiğini belirtiyor
  • Yeni API, basit bir async iterable yapısı, açık backpressure politikaları, senkron/asenkron birlikte çalışma desteği ile verimliliği ve tutarlılığı artırıyor
  • Bu yaklaşım, Node.js, Deno, Bun, tarayıcılar gibi tüm runtime’larda birleşik bir streaming modeli mümkün kılabilir ve gelecekteki standart tartışmaları için başlangıç noktası olabilir

Web Streams’in yapısal sınırlamaları

  • WHATWG Streams standardı 2014–2016 arasında geliştirildi ve tarayıcı merkezli tasarlandı; o dönemde async iteration henüz olmadığından ayrı bir reader/writer modeli benimsendi
    • Bunun sonucu olarak lock yönetimi, karmaşık okuma döngüleri, BYOB buffer işleme gibi gereksiz prosedürler ortaya çıktı
  • Locking modeli, stream’i tekelleştirerek paralel tüketimi engelliyor; releaseLock() unutulduğunda stream’in kalıcı olarak kilitli kalması sorunu yaşanabiliyor
  • BYOB (Bring Your Own Buffer) özelliği bellek yeniden kullanımını hedefliyordu; ancak karmaşık buffer ayırma ve aktarım modeli nedeniyle pratikte az kullanılıyor ve uygulanması zor
  • Backpressure teoride desteklense de desiredSize değeri negatif olduğunda bile enqueue() başarılı olabildiği için gerçek denetim sağlayamayan bir yapı sunuyor
  • Her read() çağrısında Promise oluşturulması zorunlu olduğundan, yüksek frekanslı streaming’de performans düşüşü ve GC yükü yaratıyor

Pratikte ortaya çıkan sorunlar

  • fetch() yanıt gövdesi tüketilmediğinde bağlantı havuzunun tükenmesi yaşanıyor; tee() kullanıldığında ise sınırsız bellek buffer’lama oluşabiliyor
  • TransformStream, okumaya hazır olup olmadığına bakmadan hemen işlem yaptığı için yavaş tüketici ortamlarında buffer patlamasına yol açıyor
  • Sunucu tarafı render (SSR) senaryolarında binlerce küçük chunk işlenmesinin yarattığı GC thrashing nedeniyle performans ciddi biçimde düşüyor
  • Her runtime (Node.js, Deno, Bun, Workers) bunu hafifletmek için standart dışı optimizasyon yolları ekledi; ancak bu da uyumluluk ve tutarlılık kaybına neden oldu
  • Web Platform Tests, 70’ten fazla karmaşık test dosyası gerektiriyor; bu da aşırı iç durum yönetimi ve sezgisel olmayan davranışların sonucu olarak görülüyor

Yeni stream API tasarım ilkeleri

  • Stream, basit bir async iterable olarak tanımlanıyor; böylece for await...of ile doğrudan tüketilebiliyor
  • Pull-through dönüşüm benimsenerek işleme yalnızca tüketici veri istediğinde yapılıyor
  • Açık backpressure politikaları (strict, block, drop-oldest, drop-newest) sunularak bellek taşması önleniyor
  • Veri toplu chunk’lar (Uint8Array[]) halinde iletilerek Promise üretim maliyeti azaltılıyor
  • Yapı, yalnızca byte odaklı işleme ile sadeleştiriliyor; BYOB ve karmaşık controller kavramları kaldırılıyor
  • Senkron (synchronous) yol desteği, CPU ağırlıklı işlerde Promise ek yükünü ortadan kaldırıyor

Yeni API’nin örnekleri ve özellikleri

  • Stream.push() ile kolayca writer/readable çifti oluşturulabiliyor, Stream.text() ile tüm metin toplanabiliyor
  • Stream.pull(), yalnızca tüketim anında çalışan lazy pipeline yapıları kuruyor
  • Stream.share() ve Stream.broadcast(), açık çoklu tüketici yönetimi sağlıyor
  • Sync/Async birlikte çalışma API’leri (Stream.pullSync(), Stream.textSync()) sayesinde I/O içermeyen işlemlerde performans en üst düzeye çıkarılıyor
  • Web Streams ile birlikte çalışabilirlik için basit adapter fonksiyonlarıyla dönüşüm yapılabiliyor

Performans karşılaştırması ve görünüm

  • Node.js tabanlı benchmark’larda 80–90 kata kadar, tarayıcılarda ise 100 katın üzerinde daha hızlı işlem görüldü
    • Örnek: 3 aşamalı dönüşüm zincirinde 275GB/s vs 3GB/s
  • Performans artışı, asenkron ek yükün kaldırılması, toplu işleme, pull tabanlı tasarım sayesinde elde ediliyor
  • Bu uygulama saf TypeScript/JavaScript ile yazıldı; native uygulamada ek iyileştirme potansiyeli bulunuyor
  • Cloudflare, bu yaklaşımı standart tartışmaları için bir başlangıç noktası olarak sunuyor ve geliştirici topluluğundan geri bildirim istiyor

Sonuç

  • Web Streams, ortaya çıktığı dönemin kısıtları içinde makuldü; ancak modern JavaScript’in dil özellikleri ve geliştirme kalıplarıyla artık uyumlu değil
  • Async iterable tabanlı yeni model, sadelik, performans ve açık denetimi birlikte sağlarken runtime’lar arasında tutarlı bir streaming ekosistemi kurma potansiyeli sunuyor
  • Cloudflare, GitHub’daki jasnell/new-streams deposunda referans uygulama, dokümantasyon ve örnek kodları paylaşıyor
  • Hedef yeni bir standart dayatmak değil; “daha iyi bir stream API’si” tartışmasını başlatacak somut bir çıkış noktası oluşturmak

1 yorum

 
GN⁺ 2026-02-28
Hacker News görüşleri
  • Bu yazıda önerilen API'den daha iyi bir Stream arayüzü bizzat tasarladım
    Mevcut öneri async iterator of UInt8Array biçiminde; ben ise next() çağrısının hem senkron hem de asenkron sonuç döndürebildiği bir yapı öneriyorum
    Böylece
    mevcut yapıya kıyasla tek bir iterator ile daha basit dolaşım mümkün oluyor
    senkron girdiye senkron dönüşüm uygulanırsa tüm işlem senkron yürütülebiliyor ve kod tekrarını azaltıyor
    gereksiz Promise üretimi azaldığı için performans artışı sağlıyor
    eşzamanlılık kontrolü mümkün olduğu için async iterator'un sınırlamalarını aşabiliyor

    • Senin önerdiğin yaklaşımın daha iyi olduğunu söylüyorsun ama aslında karşı tarafın yaklaşımının daha temel bir ilkel biçim olarak üstün olduğunu düşünüyorum
      Senin yaklaşımından onların yapısını kolayca kurmak mümkün değil, tersiyse mümkün
      I/O odaklı iterator'ların T biriminde chunk döndürmesi, buffer israfını önlemek için gerekli
    • Önerdiğin stream kavramı ilginç, ancak onların tasarımı AsyncIterator uyumluluğunu temel alıyor
      Uint8Array kullanılmasının nedeni OS düzeyindeki byte stream'lerle uyum sağlamak
      Gerçekte C tabanlı projelerde de bu yapı en verimli olanı, bu yüzden tür bilgisi taşıyan protokollerin bunun üzerine inşa edilmesi daha doğal
    • Node 24'te senkron fonksiyon çağrısı ile async fonksiyon çağrısı arasındaki hız farkını mikrobenchmark ile ölçtüm; yaklaşık 90 kat daha yavaş
      Eski sürümlerde fark 105 kata kadar çıkıyordu
      async işleme optimizasyonu Node 16'da gelmişti ve o sırada bazı testlerin bozulduğunu hatırlıyorum
    • Uint8Array diye bir tür yok
      Uint8Array sadece byte dizisini ifade eden ilkel bir tür ve tür bilgisi protokol değil uygulama seviyesinde ele alınmalı
    • Bu yapı Clojure'un transducer kavramına benziyor
      Bkz: Clojure Transducers dokümanı
  • Async iterable da kusursuz bir çözüm değil
    Promise ve stack geçişi overhead'i yüksek olduğu için küçük veri parçalarıyla çalışırken performansı kötü
    Lit-SSR bunu çözmek için senkron iterable içinde thunk barındıran bir yaklaşım kullandı
    async iş gerektiğinde yalnızca thunk çağrılıp await ediliyor; bu da SSR performansını 12 ila 18 kat artırdı
    Ancak Streams API'nin böyle kırılgan bir sözleşme yapısını benimsemesi zor olduğundan, write() ve writeAsync() gibi isteğe bağlı asenkron işleme izin veren bir yapı ideal olurdu diye düşünüyorum

    • Bahsettiğin sorunu benim stream iterator yaklaşımım çözebilir
      Senkron generator kullanan bir örneği GitHub kodu olarak paylaşıyorum
      Kilit nokta step.value.then(value => this.next(value)) kısmı
    • conartist6'nın önerisini (next(): {done, value: T} | Promise) beğendim
      2013'teki “Do not unleash Zalgo” tartışmasından sonra MaybeAsync biçiminden kaçınma eğilimi vardı ama
      bu korkunun aşırı büyütüldüğünü ve hızlı, esnek API tasarımını engellediğini düşünüyorum
      Birden fazla değeri tek seferde çekebilen yardımcı araçlar da yapılabilir ve generator hız sorununun pratikte o kadar büyük olmadığını hissediyorum
  • Node.js'te Web Streams ile çalışmak acı verici
    Tarayıcı merkezli tasarlandığı için sunucu ortamında kullanışsız
    Basit bir dönüşüm için bile bir transform stream sarmalamak gerekiyor ve .pipe() gibi sezgisel zincirleme zor
    Async iterable yaklaşımı çok daha doğal ve for-await-of ile iyi örtüşüyor
    Web Streams spesifikasyonu fazlasıyla soyutlama odaklı olduğu için pratik değeri düşük

    • Node'da gerçekten Web Streams kullanan insanlar olması şaşırtıcı
      Ben bunun sadece istemci-sunucu uyumluluğu için var olduğunu sanıyordum
  • Asıl avantaj sadece performans değil, ortamlar arası tutarlılık (convergence)
    ReadableStream tarayıcıda, Worker'da ve diğer runtime'larda aynı şekilde çalışırsa
    kod taşınabilirliği artar ve backpressure hataları azalır
    stream katmanının standardize edilmesi, güvenilir streaming sistemleri kurmanın anahtarı

    • Aynen, mesele yalnızca performans değil; standardizasyonun değeri büyük
  • Eskiden Repeater adında bir soyutlama yapmıştım
    Bu, Promise constructor kavramının async iterable'a taşınmış hali; event'ler push/stop ile kontrol ediliyor
    Repeater kütüphanesi haftada 6,5 milyon indirmeye ulaşacak kadar stabil
    Son dönemde streams'i daha çok tercih ediyorum ama tee() ile ilgili eleştiriler hâlâ geçerli
    async iterable'ı temel soyutlama yapmak doğru yön gibi geliyor

    • Repeater'daki stopun hem fonksiyon hem de Promise gibi davranması ilgimi çekmişti
      Kaynak kodu inceledikten sonra
      bunun geleneksel kalıplardan farklı olsa da ergonomik tasarım için bilinçli bir tercih olabileceğini düşündüm
    • Konudan bağımsız ama Konami kodu örneğini görmek çok hoşuma gitti
      E-posta imzamda bile “Up, Up, Down, Down, Left, Right, Left, Right, B, A” yazacak kadar nostaljik bağım var
  • Ben de AsyncIterable'ı daha özlü kullanmak için bir sarmalayıcı yazmıştım
    fluent-async-iterator adını vermiştim,
    Lambda veya CLI pipeline'larında küçük ölçekli veri akışlarında işe yarıyordu
    Şimdiye kadar daha iyi bir API çıkmış olmasını umuyordum

  • ReadableStream.tee()'nin backpressure davranışı, Node.js'teki pipe()ın tersine işlediği için kafa karıştırıcı
    Spesifikasyonda “hızı en yavaş çıktı belirlemeli” deniyor ama gerçek uygulamada hızlı taraf tüketilmese bile akış tıkanıyor
    Yeni bir Stream API'sindeki gibi push tabanlı, daha sade bir yapı daha iyi olur diye düşünüyorum
    Node ve Web Streams sonsuz kuyruklar kullanarak senkron şekilde res.write() çağrılarının arka arkaya yapılmasına izin veriyor ama
    bu API generator tabanlı yield akışını zorunlu kılarak daha güvenli oluyor

  • Node.js'te undici(fetch) kullanırken bağlantı havuzunun tükenmesi sorunu yaşanmasının nedeni
    çöp toplayıcılı dillerin sınırları
    Kaynağı açıkça kapatmazsan GC zamanlamasına bağlı sızıntılar oluşuyor
    C++'taki RAII(reference counting) yaklaşımı bu açıdan daha güvenli

  • Kaynak serbest bırakma konusunda using/await using kalıbının giderek yaygınlaşmasını umuyorum
    C#'taki using gibi dispose/disposeAsync destekleyen yapıyı DB driver'lara uyguluyorum

  • Benchmark rakamları (ör. 530GB/s), M1 Pro'nun bellek bant genişliğini (200GB/s) aştığı için güvenilir görünmüyor
    Uygulama kalite kontrolü zayıf bir vibe-coded benchmark olma ihtimali yüksek