gRPC 스트리밍으로 대용량 로그 전송 구조 개선
문제 상황
로그 수집 서버에서 클라이언트로 대용량 로그를 전송할 때 REST API를 사용하고 있었다. 로그 크기가 100MB를 넘어가면서 응답 타임아웃이 빈번하게 발생했고, 서버 메모리도 급증하는 문제가 있었다.
gRPC 서버 스트리밍 도입
gRPC의 서버 스트리밍을 사용하면 데이터를 청크 단위로 나눠 전송할 수 있다. proto 파일을 다음과 같이 정의했다.
service LogService {
rpc StreamLogs(LogRequest) returns (stream LogChunk) {}
}
message LogChunk {
bytes data = 1;
int64 offset = 2;
bool is_last = 3;
}
Node.js 서버에서는 파일을 스트림으로 읽어 청크 단위로 전송하도록 구현했다.
const fs = require('fs');
function streamLogs(call) {
const filePath = call.request.file_path;
const stream = fs.createReadStream(filePath, { highWaterMark: 64 * 1024 });
let offset = 0;
stream.on('data', (chunk) => {
call.write({
data: chunk,
offset: offset,
is_last: false
});
offset += chunk.length;
});
stream.on('end', () => {
call.write({ data: Buffer.alloc(0), offset: offset, is_last: true });
call.end();
});
}
결과
100MB 파일 전송 기준으로 서버 메모리 사용량이 평균 320MB에서 90MB로 감소했다. 타임아웃 문제도 완전히 해결됐고, 클라이언트에서 실시간으로 다운로드 진행률을 표시할 수 있게 됐다.
프로토콜 버퍼의 스키마 기반 통신 덕분에 타입 안정성도 확보할 수 있었다. REST에서 gRPC로 전환하는 작업이 생각보다 간단했고, 스트리밍이 필요한 다른 API들도 순차적으로 마이그레이션할 계획이다.