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% 정도 줄었다.