Node.js 인터뷰 질문 15
질문: Node.js에서 스트림(Streams)이란 무엇이며 어떤 종류가 있나요?
답변:
Node.js에서 스트림(Streams)은 데이터를 작은 청크(chunks)로 나누어 순차적으로 처리하는 추상적인 인터페이스입니다. 스트림을 사용하면 메모리 효율성이 향상되고, 특히 대용량 데이터를 처리할 때 유용합니다. 파일 읽기/쓰기, 네트워크 통신 등에서 널리 사용됩니다.
스트림의 장점
- 메모리 효율성: 전체 데이터를 메모리에 한 번에 로드하지 않고 조각으로 처리합니다.
- 시간 효율성: 전체 데이터를 기다리지 않고 도착하는 대로 처리할 수 있습니다.
- 조합 가능성: 여러 스트림을 파이프라인으로 연결하여 복잡한 데이터 처리 과정을 구성할 수 있습니다.
스트림의 종류
Node.js에는 네 가지 기본 스트림 타입이 있습니다:
1. 읽기 가능 스트림(Readable Streams)
데이터를 소비(읽기)할 수 있는 스트림입니다. 예: 파일 읽기, HTTP 요청, 프로세스 표준 입력(stdin) 등
const fs = require("fs");
// 파일을 읽기 가능 스트림으로 열기
const readableStream = fs.createReadStream("large-file.txt", {
encoding: "utf8",
highWaterMark: 16 * 1024, // 16KB 청크 크기
});
// 데이터 이벤트 구독
readableStream.on("data", (chunk) => {
console.log(`Received ${chunk.length} bytes of data.`);
console.log(chunk);
});
// 스트림이 끝났을 때
readableStream.on("end", () => {
console.log("No more data to read.");
});
// 오류 처리
readableStream.on("error", (err) => {
console.error("Error reading the file:", err);
});
2. 쓰기 가능 스트림(Writable Streams)
데이터를 쓸 수 있는 스트림입니다. 예: 파일 쓰기, HTTP 응답, 프로세스 표준 출력(stdout) 등
const fs = require("fs");
// 파일을 쓰기 가능 스트림으로 열기
const writableStream = fs.createWriteStream("output.txt");
// 데이터 쓰기
writableStream.write("첫 번째 데이터 청크\n");
writableStream.write("두 번째 데이터 청크\n");
// 쓰기 완료
writableStream.end("마지막 데이터 청크\n");
// 쓰기 완료 이벤트
writableStream.on("finish", () => {
console.log("All data has been written to the file.");
});
// 오류 처리
writableStream.on("error", (err) => {
console.error("Error writing to the file:", err);
});
3. 양방향 스트림(Duplex Streams)
읽기와 쓰기가 모두 가능한 스트림입니다. 예: TCP 소켓, Zlib 압축 등
const net = require("net");
// TCP 서버 생성
const server = net.createServer((socket) => {
// 'socket'은 양방향 스트림
socket.on("data", (data) => {
console.log(`Received: ${data}`);
// 데이터를 다시 클라이언트로 전송
socket.write(`Echo: ${data}`);
});
socket.on("end", () => {
console.log("Client disconnected");
});
});
server.listen(3000, () => {
console.log("Server started on port 3000");
});
4. 변환 스트림(Transform Streams)
데이터를 읽고, 변환한 후, 쓰는 양방향 스트림입니다. 예: 압축/해제, 암호화/복호화, 데이터 파싱 등
const { Transform } = require("stream");
const fs = require("fs");
// 대문자 변환 스트림 생성
const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
// 텍스트를 대문자로 변환
this.push(chunk.toString().toUpperCase());
callback();
},
});
// 파이프라인 설정: 파일 읽기 -> 대문자 변환 -> 파일 쓰기
fs.createReadStream("input.txt")
.pipe(upperCaseTransform)
.pipe(fs.createWriteStream("output.txt"))
.on("finish", () => {
console.log("Transformation completed!");
});
스트림 파이프(pipe)
여러 스트림을 함께 연결하여 데이터 파이프라인을 만들 수 있습니다:
const fs = require("fs");
const zlib = require("zlib");
// 파일 압축 예제
fs.createReadStream("large-file.txt")
.pipe(zlib.createGzip()) // 압축 변환 스트림
.pipe(fs.createWriteStream("large-file.txt.gz"))
.on("finish", () => {
console.log("File compression completed.");
});
// 체이닝 방식으로도 에러 처리 가능
fs.createReadStream("large-file.txt")
.on("error", (err) => console.error("Read error:", err))
.pipe(zlib.createGzip())
.on("error", (err) => console.error("Compression error:", err))
.pipe(fs.createWriteStream("large-file.txt.gz"))
.on("error", (err) => console.error("Write error:", err));
스트림 모드
읽기 가능 스트림은 두 가지 모드로 작동할 수 있습니다:
- 흐름 모드(Flowing Mode): 데이터가 자동으로 읽히고 'data' 이벤트로 전달됩니다.
- 일시 정지 모드(Paused Mode):
stream.read()
메서드를 명시적으로 호출하여 데이터를 읽어야 합니다.
// 흐름 모드
readableStream.on("data", (chunk) => {
console.log(chunk);
});
// 일시 정지 모드
readableStream.on("readable", () => {
let chunk;
while (null !== (chunk = readableStream.read())) {
console.log(chunk);
}
});
객체 모드 스트림(Object Mode Streams)
일반적으로 스트림은 문자열이나 Buffer 객체를 처리하지만, 객체 모드에서는 JavaScript 객체를 직접 처리할 수 있습니다:
const { Transform } = require("stream");
const objectTransform = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
// 입력 객체 변환
chunk.processed = true;
this.push(chunk);
callback();
},
});
스트림은 Node.js에서 대용량 데이터 처리의 핵심 기능으로, 메모리 효율적인 애플리케이션을 개발하는 데 필수적인 요소입니다.