gRPC 스트리밍으로 대용량 데이터 처리 개선하기
문제 상황
로그 분석 시스템에서 시간 범위로 로그를 조회하는 API가 있었다. 데이터가 많아지면서 REST API로는 한계가 있었다.
- 수십만 건의 로그를 JSON 배열로 만들어 반환
- 서버 메모리에 전체 데이터를 적재해야 함
- 클라이언트는 응답이 완료될 때까지 대기
gRPC Server Streaming 도입
gRPC의 Server Streaming을 사용하면 데이터를 청크 단위로 전송할 수 있다.
service LogService {
rpc GetLogs(LogRequest) returns (stream LogResponse);
}
message LogRequest {
int64 start_time = 1;
int64 end_time = 2;
}
message LogResponse {
string id = 1;
string message = 2;
int64 timestamp = 3;
}
서버 구현은 Node.js로 작성했다.
const grpc = require('@grpc/grpc-js');
function getLogs(call) {
const { start_time, end_time } = call.request;
// 스트림으로 데이터 전송
db.query('SELECT * FROM logs WHERE timestamp BETWEEN ? AND ?',
[start_time, end_time])
.stream()
.on('data', (row) => {
call.write({
id: row.id,
message: row.message,
timestamp: row.timestamp
});
})
.on('end', () => {
call.end();
})
.on('error', (err) => {
call.destroy(err);
});
}
클라이언트에서는 스트림을 받아 처리한다.
const call = client.getLogs({
start_time: startTime,
end_time: endTime
});
call.on('data', (log) => {
// 데이터 도착 즉시 처리
processLog(log);
});
call.on('end', () => {
console.log('스트림 종료');
});
결과
- 서버 메모리 사용량: 2GB → 200MB
- 첫 데이터 응답 시간: 3초 → 0.2초
- 클라이언트가 데이터를 받는 즉시 처리 가능
REST API는 유지하되, 대용량 조회는 gRPC로 분리했다. Protobuf 직렬화 덕분에 네트워크 전송량도 30% 정도 줄었다.