Streams API 4. 왜 모든 언어에는 Stream API가 존재할까
작성일:2026.03.13|조회수:14

Streams API를 공부하다 보면 묘한 기시감이 온다. JavaScript에서 ReadableStream, WritableStream, TransformStream을 보고 있는데, Java의 InputStream, Go의 io.Reader, Rust의 Read와 Write가 멀리서 손을 흔든다. 이름도 다르고 문법도 다른데, 이상할 정도로 같은 문제를 향해 있다. 읽고, 처리하고, 쓰는 구조다.
이쯤 되면 질문이 바뀐다. 왜 언어마다 비슷한 stream abstraction이 반복될까. 이것은 유행이라기보다 필연에 가깝다. stream은 특정 언어가 멋으로 만든 API가 아니라, 네트워크와 운영체제의 I/O 모델을 사람이 다루기 쉽게 번역한 결과다. 이 글에서는 언어별 Stream API의 공통점을 따라가며, 결국 우리가 어떤 문제를 계속 다시 풀고 있는지 살펴본다.
네트워크는 생각보다 “메시지적”이지 않다
애플리케이션을 작성하다 보면 우리는 종종 데이터를 메시지처럼 생각한다. 서버가 “응답 하나”를 보낸다고 말하고, 클라이언트가 “요청 하나”를 보낸다고 말한다. JSON 하나를 받고, 이미지 하나를 받고, 텍스트 하나를 받는다고 이해한다. 애플리케이션 레벨에서는 이 말이 크게 틀리지 않는다. 하지만 네트워크, 특히 TCP 수준으로 내려가 보면 이야기가 달라진다. TCP는 애플리케이션에게 “완성된 메시지”를 전달하는 프로토콜이 아니라 연속된 바이트 흐름을 전달하는 프로토콜에 가깝다.
예를 들어 서버가 hello world라는 문자열을 보냈다고 해 보자. 애플리케이션 관점에서는 이것이 하나의 완결된 메시지처럼 보일 수 있다. 그러나 네트워크는 그 문자열을 “한 덩어리의 의미 있는 데이터”로 이해하지 않는다. 실제 전송 과정에서는 hel, lo w, orld처럼 임의의 경계에서 나뉘어 전달될 수도 있다. 애플리케이션이 받는 쪽에서는 이 조각들을 다시 이어 붙여야만 비로소 원래의 의미를 회복할 수 있다. 즉, 네트워크가 주는 것은 메시지가 아니라 바이트의 흐름이다. 이 점이 매우 중요하다. 왜냐하면 이 순간부터 애플리케이션은 데이터를 “조금씩 읽고, 조금씩 처리하고, 조금씩 넘기는” 방식으로 다룰 수밖에 없기 때문이다.
바로 여기서 스트림 모델이 등장한다. 스트림은 거창한 추상화처럼 보이지만, 사실 네트워크의 이 본질을 다루기 위한 매우 자연스러운 형식이다. 바이트가 끊임없이 흐르고, 우리는 그 일부를 읽고, 해석하고, 다시 다른 곳으로 보내야 한다. 이 구조를 가장 단순하게 표현하면 결국 read와 write다. 읽고, 쓰는 것. 대부분의 Stream API는 사실 이 두 동작에서 출발한다.
운영체제는 이미 오래전부터 스트림처럼 동작하고 있었다
이 모델은 네트워크에만 한정되지 않는다. 운영체제의 기본 I/O 인터페이스도 거의 같은 사고방식을 가지고 있다. POSIX 계열 시스템에서 가장 대표적인 I/O 인터페이스는 아래와 같다.
read(fd, buffer, size)
write(fd, buffer, size)여기서 중요한 것은 fd가 단순히 “파일”만을 가리키는 것이 아니라는 점이다. 운영체제 입장에서는 파일, 네트워크 소켓, 프로세스 간 파이프, 표준 입력과 출력 모두가 비슷한 형태의 I/O 대상으로 취급된다. 즉, 운영체제는 이미 매우 오래전부터 다양한 입출력 대상을 “읽고 쓸 수 있는 흐름”으로 다루고 있었다.
이 관점은 Unix 철학과도 맞닿아 있다. 파일도 읽고 쓸 수 있고, 파이프도 읽고 쓸 수 있고, 네트워크 소켓도 읽고 쓸 수 있다. 서로 다른 자원처럼 보이지만, 프로그래밍 모델 차원에서는 놀랄 만큼 비슷하다. 개발자가 다루는 것은 “무엇인가로부터 데이터를 조금씩 읽고, 무엇인가에 데이터를 조금씩 쓰는 일”이다. 결국 운영체제는 I/O를 개별적인 특수 기능의 모음으로 보기보다, 공통된 데이터 흐름 모델 위에서 바라보는 셈이다.
이런 배경을 이해하면 왜 스트림이라는 개념이 여러 언어에서 반복해서 나타나는지 감이 잡힌다. 언어는 완전히 새로운 세계를 만드는 것이 아니라, 운영체제와 네트워크가 이미 제공하고 있는 모델을 개발자가 다루기 쉽게 끌어올린다. 그러니 언어가 달라도 비슷한 API가 나오는 것은 이상한 일이 아니다. 오히려 그렇지 않은 편이 더 이상할 것이다.
언어의 Stream API는 모두 같은 문제를 각자의 방식으로 풀고 있다
프로그래밍 언어의 스트림 API는 사실 이 운영체제 모델을 조금 더 사용하기 쉽게 감싼 것이다. 데이터를 한 번에 모두 메모리에 올려 처리하는 것이 아니라, 흐름으로 들어오는 데이터를 순차적으로 읽고, 필요하면 중간에서 변환하고, 다시 다른 목적지로 보내는 문제다.
// JAVA
InputStream
OutputStream
// GO
io.Reader
io.Writer
// RUST
Read
Write이 공통점은 단순히 이름의 유사성에서 끝나지 않는다. 실제로 각 언어의 API는 거의 비슷한 철학을 공유한다. 입력과 출력을 나누고, 생산자와 소비자를 분리하고, 중간에 변환기를 둘 수 있게 하고, 큰 데이터를 작은 조각으로 나누어 처리할 수 있게 한다. 이는 곧 스트림이라는 개념이 특정 언어 문법에 의존하는 기능이 아니라, 더 아래층에 있는 현실적인 I/O 문제를 다루기 위한 일반 모델이라는 뜻이다.
이 관점에서 보면 Web Streams API도 훨씬 또렷하게 보인다. ReadableStream은 데이터가 흘러나오는 쪽을, WritableStream은 데이터가 흘러들어가는 쪽을, TransformStream은 그 사이에서 변환을 수행하는 쪽을 담당한다. 이 구조는 낯선 웹 전용 발명품이 아니라, 이미 다른 언어와 시스템에서 오랫동안 반복되어 온 구조를 웹 환경에 맞게 표준화한 결과에 가깝다.
왜 하필 “스트림”이라는 모델이 반복될까
여기서 한 걸음 더 들어가 보면, 스트림이 여러 언어에 존재하는 이유는 단지 역사적 관성 때문만은 아니다. 이 모델이 실제 문제를 푸는 데 매우 유리하기 때문이다. 현실의 데이터는 생각보다 자주 “한 번에 다루기 어려운 것”의 형태로 존재한다. 네트워크 응답은 언제 도착할지 모르고, 파일은 너무 커서 통째로 메모리에 올리기 부담스럽고, 영상과 오디오는 계속 흘러들어오며, 로그나 이벤트는 끝없이 이어질 수 있다. 이런 데이터는 배열처럼 고정된 덩어리로 생각하기보다 흐름으로 생각하는 편이 훨씬 자연스럽다.
스트림 모델은 이런 현실을 그대로 반영한다. 전체가 완성되기 전에 일부를 먼저 처리할 수 있고, 메모리를 과도하게 쓰지 않아도 되며, 여러 단계를 파이프라인으로 연결하기 쉽다. 앞선 글에서 살펴본 백프레셔, 바이트 스트림, BYOB 같은 개념도 모두 이 큰 그림 안에서 이해할 수 있다. 결국 스트림은 단순한 편의 API가 아니라, 연속적으로 도착하는 데이터를 효율적으로 다루기 위해 여러 시스템이 수렴한 공통 설계라고 볼 수 있다.
Stream API는 언어 기능이 아니라 I/O 모델의 번역본에 가깝다
이제 방향을 조금 바꿔 생각해 볼 수 있다. 우리는 종종 ReadableStream이나 WritableStream을 JavaScript의 기능이라고 말한다. 물론 실용적으로는 맞는 표현이다. 하지만 개념적으로는 이것이 조금 부족하다. 더 정확히 말하면, Stream API는 네트워크와 운영체제가 제공하는 I/O 모델을 각 언어가 자기 식으로 번역한 결과물에 가깝다.
이렇게 이해하면 몇 가지가 동시에 정리된다. 왜 여러 언어가 비슷한 구조를 가지는지 설명된다. 왜 스트림을 배우다 보면 파일, 네트워크, 파이프, 표준 입출력 같은 이야기가 자꾸 함께 등장하는지도 설명된다. 그리고 왜 스트림이 단순한 문법 설탕이 아니라, 성능과 메모리, 흐름 제어, 경계 처리 같은 실제 시스템 문제와 깊게 연결되는지도 설명된다.
특히 Web Streams API를 공부하는 입장에서는 이 관점이 중요하다. pipeTo, pipeThrough, tee, TextDecoderStream, BYOB 같은 개별 API를 따로따로 외우는 것보다, 이 모든 것이 결국 **“흐르는 데이터를 어떻게 읽고, 가공하고, 전달할 것인가”**라는 오래된 문제의 웹 표준 버전이라고 이해하는 편이 훨씬 오래 남는다. 표면 API는 시대와 환경에 따라 달라질 수 있지만, 그 아래의 문제는 꽤 오래 같은 모습으로 남아 있기 때문이다.
댓글
댓글을 불러오는 중...