# JavaScript를 위한 더 나은 Streams API가 필요합니다

> Clean Markdown view of GeekNews topic #27080. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=27080](https://news.hada.io/topic?id=27080)
- GeekNews Markdown: [https://news.hada.io/topic/27080.md](https://news.hada.io/topic/27080.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-02-28T20:32:56+09:00
- Updated: 2026-02-28T20:32:56+09:00
- Original source: [blog.cloudflare.com](https://blog.cloudflare.com/a-better-web-streams-api/)
- Points: 4
- Comments: 1

## Topic Body

- **Web Streams 표준**은 브라우저와 서버 간 일관된 데이터 스트리밍을 위해 설계되었지만, 현재는 **복잡성과 성능 한계**로 인해 개발자 경험이 저하되고 있음  
- 기존 API는 **락(lock) 관리, BYOB, 백프레셔(backpressure)** 등 설계적 제약으로 인해 사용성과 구현 모두에서 불필요한 부담을 초래함  
- Cloudflare는 **비동기 반복(async iteration)** 기반의 새로운 스트림 모델을 제안하며, 이 방식은 **2배에서 최대 120배 빠른 성능**을 보임  
- 새 API는 **단순한 async iterable 구조**, **명시적 백프레셔 정책**, **동기/비동기 병행 지원**을 통해 효율성과 일관성을 높임  
- 이 접근은 **Node.js, Deno, Bun, 브라우저 등 모든 런타임에서 통합적 스트리밍 모델**을 가능하게 하며, 향후 표준 논의의 출발점이 될 수 있음  
  
---  
  
### Web Streams의 구조적 한계  
- WHATWG Streams 표준은 2014~2016년에 개발되어 브라우저 중심으로 설계되었으며, 당시 **async iteration이 존재하지 않아** 별도의 reader/writer 모델을 도입함  
  - 이로 인해 **락 관리, 복잡한 읽기 루프, BYOB 버퍼 처리** 등 불필요한 절차가 생김  
- **락(locking) 모델**은 스트림을 독점적으로 점유해 병렬 소비를 막으며, `releaseLock()` 누락 시 스트림이 영구적으로 잠기는 문제가 발생함  
- **BYOB(Bring Your Own Buffer)** 기능은 메모리 재사용을 목표로 했지만, **복잡한 버퍼 분리·전송 모델**로 인해 실제 활용도가 낮고 구현 난이도가 높음  
- **백프레셔(backpressure)** 는 이론상 지원되지만, `desiredSize` 값이 음수여도 `enqueue()`가 성공하는 등 **실제 제어가 불가능한 구조**임  
- 각 `read()` 호출마다 **Promise 생성이 강제**되어, 고빈도 스트리밍에서는 **성능 저하와 GC 부하**를 유발함  
  
### 실무에서 드러난 문제들  
- `fetch()` 응답 본문을 소비하지 않으면 **연결 풀 고갈**이 발생하며, `tee()` 사용 시 **무제한 메모리 버퍼링**이 생김  
- `TransformStream`은 **읽기 준비 여부와 무관하게 즉시 처리**되어, 느린 소비자 환경에서 **버퍼 폭증**을 초래함  
- 서버사이드 렌더링(SSR)에서는 **수천 개의 작은 청크 처리로 인한 GC 쓰레싱**이 발생해 성능이 급감함  
- 각 런타임(Node.js, Deno, Bun, Workers)은 이를 완화하기 위해 **비표준 최적화 경로**를 도입했으나, 이로 인해 **호환성과 일관성 저하**가 발생함  
- Web Platform Tests는 70개 이상의 복잡한 테스트 파일을 요구하며, 이는 **과도한 내부 상태 관리와 비직관적 동작**의 결과임  
  
### 새로운 스트림 API 설계 원칙  
- **스트림은 단순한 async iterable**로 정의되어, `for await...of`로 직접 소비 가능  
- **Pull-through 변환**을 채택해 소비자가 데이터를 요청할 때만 처리 수행  
- **명시적 백프레셔 정책(strict, block, drop-oldest, drop-newest)** 을 제공해 메모리 폭주 방지  
- **배치 청크(Uint8Array[])** 단위로 데이터를 전달해 Promise 생성 비용을 줄임  
- **바이트 단위 전용 처리**로 단순화하며, BYOB나 복잡한 컨트롤러 개념 제거  
- **동기(synchronous) 경로 지원**으로 CPU 중심 작업에서 Promise 오버헤드를 제거  
  
### 새로운 API의 예시와 특징  
- `Stream.push()`로 간단히 **writer/readable 쌍 생성**, `Stream.text()`로 전체 텍스트 수집 가능  
- `Stream.pull()`은 **지연(lazy) 파이프라인**을 구성해 소비 시점에만 실행  
- `Stream.share()`와 `Stream.broadcast()`는 **명시적 다중 소비자 관리**를 지원  
- **Sync/Async 병행 API**(`Stream.pullSync()`, `Stream.textSync()`)로 I/O 없는 연산에서 성능 극대화  
- Web Streams와의 상호 운용을 위해 **간단한 어댑터 함수**로 변환 가능  
  
### 성능 비교 및 전망  
- Node.js 기준 벤치마크에서 **최대 80~90배**, 브라우저에서는 **최대 100배 이상** 빠른 처리 속도 확인  
  - 예: 3단 변환 체인에서 275GB/s vs 3GB/s  
- 성능 향상은 **비동기 오버헤드 제거, 배치 처리, pull 기반 설계**에서 기인  
- 이 구현은 순수 TypeScript/JavaScript로 작성되었으며, **네이티브 구현 시 추가 향상 가능성** 존재  
- Cloudflare는 이 접근을 **표준 논의의 출발점**으로 제시하며, 개발자 커뮤니티의 피드백을 요청함  
  
### 결론  
- Web Streams는 당시 제약 속에서 합리적이었지만, **현대 JavaScript의 언어 기능과 개발 패턴에 부합하지 않음**  
- 새로운 async iterable 기반 모델은 **단순성, 성능, 명시적 제어**를 모두 충족하며, **런타임 간 일관된 스트리밍 생태계** 구축 가능성 제시  
- Cloudflare는 GitHub의 [jasnell/new-streams](https://github.com/jasnell/new-streams)에서 **참조 구현과 문서, 예제 코드**를 공개함  
- 목표는 새로운 표준 제정이 아니라, **“더 나은 스트림 API”를 논의하기 위한 실질적 출발점 마련**임

## Comments



### Comment 52096

- Author: neo
- Created: 2026-02-28T20:32:56+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=47180569)   
- 이 글에서 제안한 API보다 **더 나은 Stream 인터페이스**를 직접 설계했음  
  기존 제안은 `async iterator of UInt8Array` 형태인데, 나는 `next()`가 동기 혹은 비동기 결과를 모두 반환할 수 있는 구조를 제안함  
  이렇게 하면  
  기존 구조보다 **단일 반복자**로 단순하게 순회 가능함  
  동기 입력에 동기 변환을 적용하면 전체 처리가 동기로 가능해 코드 중복을 줄일 수 있음  
  불필요한 Promise 생성이 줄어들어 성능 향상이 있음  
  **동시성 제어**가 가능해 async iterator의 한계를 극복할 수 있음  
    - 네가 제안한 방식이 더 낫다고 하지만, 사실 상대방의 방식이 더 **기초적인 원시 형태**로서 우수하다고 생각함  
      네 방식으로는 그들의 구조를 쉽게 만들 수 없고, 반대로는 가능함  
      I/O 중심의 반복자는 T 단위의 청크를 반환해야 **버퍼 낭비**를 막을 수 있음  
    - 제안한 스트림 개념이 흥미롭지만, 그들의 설계는 **AsyncIterator 호환성**을 전제로 함  
      `Uint8Array`를 사용하는 이유는 OS 수준의 바이트 스트림과 맞추기 위함임  
      실제로 C 기반 프로젝트에서도 이런 구조가 가장 효율적이므로, 타입 정보를 가진 프로토콜은 그 위에 쌓이는 형태가 자연스러움  
    - Node 24에서 **동기 함수 호출과 async 함수 호출의 속도 차이**를 마이크로벤치마크로 측정했는데, 약 90배 정도 느림  
      예전 버전에서는 105배까지 차이 났음  
      async 처리 최적화가 Node 16에서 있었고, 그때 일부 테스트가 깨졌던 기억이 있음  
    - `Uint8Array`라는 타입은 존재하지 않음  
      `Uint8Array`는 단순히 바이트 배열을 표현하는 원시 타입이며, 타입 정보는 **프로토콜이 아닌 애플리케이션 레벨**에서 다뤄야 함  
    - 이 구조는 **Clojure의 transducer** 개념과 비슷함  
      참고: [Clojure Transducers 문서](https://clojure.org/reference/transducers)  
  
- **Async iterable**도 완벽한 해결책은 아님  
  Promise와 스택 전환 오버헤드가 커서 작은 단위의 데이터를 다룰 때 성능이 나쁨  
  Lit-SSR에서는 이를 해결하기 위해 **동기 iterable 안에 thunk를 포함**하는 방식을 사용했음  
  async 작업이 필요할 때만 thunk를 호출하고 await하도록 하여, SSR 성능을 12~18배 향상시켰음  
  다만 Streams API는 이런 **취약한 계약 구조**를 채택하기 어렵기 때문에, `write()`와 `writeAsync()`처럼 선택적 비동기 처리가 가능한 구조가 이상적이라 생각함  
  - 네가 말한 문제를 내 **stream iterator**가 해결할 수 있음  
    동기 generator를 활용한 예시를 [GitHub 코드](https://github.com/bablr-lang/stream-iterator/blob/trunk/lib/index.js)로 공유함  
    핵심은 `step.value.then(value => this.next(value))` 부분임  
  - conartist6의 제안(`next(): {done, value: T} | Promise`)이 마음에 듦  
    2013년의 “**Do not unleash Zalgo**” 논쟁 이후, `MaybeAsync` 형태를 피하는 경향이 있었지만  
    이 공포가 너무 과장되어 **빠르고 유연한 API 설계**를 막고 있다고 생각함  
    여러 값을 한 번에 끌어오는 유틸리티도 만들 수 있고, generator 속도 문제는 실제로는 크지 않다고 느낌  
  
- Node.js에서 **Web Streams**를 다루는 건 고통스러움  
  브라우저 중심으로 설계되어 서버 환경에서는 불편함  
  단순한 변환에도 transform stream을 감싸야 하고, `.pipe()`처럼 직관적인 체이닝이 어려움  
  **Async iterable** 접근법이 훨씬 자연스럽고 `for-await-of`와 잘 어울림  
  Web Streams 스펙은 너무 **추상화 중심적**이라 실용성이 떨어짐  
  - Node에서 Web Streams를 실제로 쓰는 사람이 있다는 게 놀라움  
    나는 그게 단순히 **클라이언트–서버 간 호환성용**이라고 생각했음  
  
- 진짜 이점은 성능뿐 아니라 **환경 간 일관성(convergence)** 임  
  ReadableStream이 브라우저, Worker, 기타 런타임에서 동일하게 동작하면  
  코드 이식성과 **backpressure 버그 감소** 효과가 큼  
  스트림 계층의 표준화는 신뢰성 있는 스트리밍 시스템 구축의 핵심임  
  - 맞음, 단순히 성능이 아니라 **표준화의 가치**가 큼  
  
- 예전에 **Repeater**라는 추상화를 만들었음  
  Promise 생성자를 async iterable로 옮긴 개념으로, 이벤트를 push/stop으로 제어함  
  [Repeater 라이브러리](https://github.com/repeaterjs/repeater)는 주간 650만 다운로드를 기록할 정도로 안정적임  
  최근에는 streams를 더 선호하지만, `tee()` 관련 비판은 여전히 유효함  
  **async iterable을 기본 추상화로 삼는 방향**이 옳다고 생각함  
  - Repeater의 `stop`이 함수이자 Promise로 동작하는 게 흥미로웠음  
    [소스 코드](https://github.com/repeaterjs/repeater/blob/638a53f2729f5197875ec12a9d578c274c5f6709/packages/repeater/src/repeater.ts#L446-L455)를 보고 나서  
    전통적인 패턴과 다르지만, **인체공학적 설계**를 위한 의도된 선택일 수도 있다고 생각함  
  - 주제와는 다르지만, **Konami 코드** 예시가 너무 반가웠음  
    이메일 서명에도 “Up, Up, Down, Down, Left, Right, Left, Right, B, A”를 쓸 정도로 향수가 깊음  
  
- 나도 **AsyncIterable을 더 간결하게 쓰기 위한 래퍼**를 만든 적 있음  
  [fluent-async-iterator](https://github.com/juliantcook/fluent-async-iterator)인데,  
  Lambda나 CLI 파이프라인에서 소규모 데이터 스트리밍에 유용했음  
  지금쯤은 더 나은 API가 나왔길 바랐음  
  
- `ReadableStream.tee()`의 **backpressure 동작**이 Node.js의 `pipe()`와 반대라 혼란스러움  
  명세서에는 “가장 느린 출력이 속도를 결정해야 한다”고 되어 있는데, 실제 구현은 빠른 쪽이 소비되지 않아도 막힘  
  새로운 Stream API처럼 **push 기반의 간결한 구조**가 더 낫다고 생각함  
  Node와 Web Streams는 무한 큐를 두어 동기적으로 `res.write()`를 남발할 수 있게 하지만,  
  이 API는 **generator 기반의 yield 흐름**을 강제해 더 안전함  
  
- Node.js에서 **undici(fetch)** 사용 시 커넥션 풀 고갈 문제가 생기는 건  
  **가비지 컬렉션 언어의 한계** 때문임  
  명시적으로 리소스를 닫지 않으면 GC 타이밍에 따라 누수가 발생함  
  C++의 **RAII(reference counting)** 접근이 오히려 더 안전함  
  
- 리소스 해제 관련해서는 `using/await using` 패턴이 점점 확산되길 바람  
  C#의 `using`처럼 **dispose/disposeAsync**를 지원하는 구조를 DB 드라이버에 적용 중임  
  
- 벤치마크 수치(예: 530GB/s)는 **M1 Pro의 메모리 대역폭(200GB/s)** 을 초과하므로 신뢰하기 어려움  
  구현 품질 관리가 부족한 **vibe-coded 벤치마크**일 가능성이 높음
