요약

  • 문제: 동적 payload와 넓은 조회 범위가 타입 안정성과 직렬화 비용을 악화
  • 역할: v2 프로토콜 설계, 조회·직렬화 경계 조정, 호환성 테스트와 운영 보완
  • 선택: 기존 v1을 제거하지 않고 타입이 명확한 v2를 병행
  • 결과: 동일한 내부 비교에서 개선 경향과 회귀 테스트 통과를 확인했으나, 공개 가능한 벤치마크 조건은 재검증이 필요

배경

서비스 간 통신에서 gRPC를 사용하고 있었지만, 일부 응답은 명시적인 protobuf 메시지보다 동적인 구조에 가까웠습니다. 이 방식은 빠르게 필드를 추가하기 쉽지만, 계약이 코드 밖으로 밀려나고 소비자가 런타임에 구조를 해석해야 합니다.

또한 호출 경로에서 실제 응답에 필요하지 않은 관계 데이터까지 조회·직렬화할 가능성이 있었습니다. 문제를 단순히 “gRPC가 느리다”로 규정하지 않고 데이터 조회, 변환, 직렬화, 네트워크 경계를 나눠 보았습니다.

문제와 근거

기존 작업 기록에서 확인한 근거는 다음과 같습니다.

  1. 같은 주문 조회 흐름을 REST와 gRPC로 비교하는 테스트가 있었습니다.
  2. 요청 규모가 커질수록 v1의 상대 비용이 커지는 결과가 관찰되었습니다.
  3. 동적 구조를 명시적 타입으로 바꾸고 필요한 필드만 조회하도록 조정한 뒤 같은 비교에서 개선 경향이 나타났습니다.
  4. v1 호환성을 포함한 자동 테스트가 통과했습니다.
  5. 이후 운영에서 gRPC 서버 불안정으로 REST fallback이 발생해, 성능 외에 장애 격리와 관측성 문제가 드러났습니다.

원문에는 특정 테스트 서버의 식별자와 정확한 수치가 포함되어 있어 이 글에는 옮기지 않았습니다. 테스트 환경, 반복 수, warm-up, embed 포함 여부가 확인되기 전에는 일반적인 처리량·성능 주장으로 사용하지 않습니다.

역할과 의사결정 범위

  • v2 protobuf 메시지와 서비스 경계를 설계했습니다.
  • 필요한 필드만 조회하도록 데이터 접근 범위를 조정했습니다.
  • 기존 v1 호출을 유지한 채 v2를 병행해 마이그레이션 위험을 줄였습니다.
  • 자동 테스트로 계약과 하위 호환성을 확인했습니다.
  • 운영 fallback 사고 이후 CPU, worker, health check와 로그를 분리해 관측 범위를 보완했습니다.

TODO: 실제 리뷰·배포 참여자와 최종 운영 책임 범위를 사용자에게 확인해야 합니다.

제약 조건

  • 기존 v1 소비자를 즉시 변경할 수 없음
  • protobuf 계약 변경 시 생성 코드와 소비자 배포 순서를 고려해야 함
  • ORM 관계 조회와 직렬화 비용을 네트워크 시간과 분리해 측정해야 함
  • gRPC 장애 시 REST fallback이 전체 서비스 부하로 전파될 수 있음
  • 비공개 endpoint, payload, 테스트 데이터 식별자를 공개할 수 없음

대안과 트레이드오프

1. v1을 직접 변경

코드 중복이 적지만 기존 소비자가 깨질 수 있습니다. 배포 순서와 롤백이 복잡해져 선택하지 않았습니다.

2. v2를 병행

계약과 구현이 일시적으로 두 벌 존재하지만, 소비자가 점진적으로 이동할 수 있고 비교·롤백이 쉽습니다. 하위 호환성을 우선해 이 방식을 선택했습니다.

3. REST로 통일

운영 경로를 단순화할 수 있지만 기존 gRPC의 타입 계약과 내부 통신 구조를 포기해야 합니다. 문제의 원인이 프로토콜 자체보다 조회·직렬화 경계에도 있었으므로 우선 선택하지 않았습니다.

구현

  1. 동적 응답에서 실제 소비되는 필드를 식별했습니다.
  2. 해당 필드를 명시적인 protobuf 메시지로 정의했습니다.
  3. v2 서비스와 stub을 추가하되 v1을 유지했습니다.
  4. 데이터 조회에서 불필요한 관계·필드를 제외했습니다.
  5. ORM 객체를 protobuf 메시지로 변환하는 경계를 단순화했습니다.
  6. v1·v2 계약과 호출 경로를 자동 테스트로 검증했습니다.
  7. 운영 장애 이후 gRPC 서버의 실행 자원과 health check, fallback 로그를 분리했습니다.

비공개 proto 원문, endpoint, 내부 코드와 설정은 싣지 않습니다.

실패와 막힘

측정값을 곧바로 성과로 일반화할 수 없었다

초기 비교는 개선 방향을 확인하는 데 유용했지만, 벤치마크 조건이 글에 충분히 남아 있지 않았습니다. 한 환경에서의 비교 수치를 실제 운영 처리량으로 표현하지 않기로 했습니다.

fallback은 복구가 아니라 부하 전파가 될 수 있었다

gRPC 서버가 불안정해졌을 때 REST fallback이 작동했지만, 호출량이 대체 경로로 몰리면 서비스 전체의 CPU와 worker에 영향을 줄 수 있습니다. fallback 성공 여부만 볼 것이 아니라, 전환량과 원인, 대체 경로의 여유를 함께 관측해야 했습니다.

검증과 결과

  • v1과 v2의 동일 기능 결과를 자동 테스트로 비교했습니다.
  • 기존 기록에는 전체 테스트 통과가 남아 있습니다.
  • 동일한 내부 테스트에서 v2의 개선 경향을 확인했습니다.
  • 운영 장애 후 gRPC와 REST fallback의 CPU·worker·health check를 분리해 볼 수 있도록 보완했습니다.

다만 아래 항목은 공개 전 재검증이 필요합니다.

  • 하드웨어와 실행 환경
  • 데이터 표본과 요청 규모
  • warm-up 및 반복 횟수
  • 관계 데이터 포함 여부
  • 네트워크와 DB 시간을 분리한 측정인지

이 확인 전까지 정확한 지연시간, 처리량, 개선율을 주장하지 않습니다.

한계와 후속 과제

  • v1 제거 기준과 소비자 마이그레이션 상태를 확인해야 합니다.
  • 데이터 조회, 변환, 직렬화, 네트워크를 분리한 벤치마크가 필요합니다.
  • fallback의 임계값과 circuit breaker 적용 여부를 검토해야 합니다.
  • health check가 실제 요청 처리 가능성을 충분히 반영하는지 검증해야 합니다.
  • 장애 시 롤백과 소비자별 호환성 런북이 필요합니다.

회고

프로토콜 변경에서 중요한 것은 새 코드의 속도만이 아니라 기존 소비자가 안전하게 이동할 수 있는 경로입니다. 성능 수치는 측정 조건이 함께 남아야 재사용 가능한 근거가 됩니다. fallback은 장애를 숨기는 장치가 아니라 또 하나의 부하 경로이므로 별도 용량과 관측성을 가져야 합니다.

관련 글

관련 작업의 원본 로그는 비공개 작업 기록에 있으며, 공개 가능한 상세 글 경로는 아직 확인되지 않았습니다.