gRPC 프로토콜 버전 관리 전략

문제 상황

결제 서비스와 주문 서비스 간 gRPC 통신에서 Payment 메시지에 새 필드를 추가하는 작업을 진행했다. 문제는 두 서비스의 배포 타이밍이 달라 일시적으로 프로토콜 불일치가 발생할 수 있다는 점이었다.

하위 호환성 유지 원칙

1. 필드 번호 재사용 금지

message Payment {
  string id = 1;
  int64 amount = 2;
  // string old_field = 3; // 삭제된 필드
  reserved 3; // 번호 예약 필수
  string payment_method = 4;
}

삭제된 필드 번호는 reserved로 명시해야 한다. 재사용 시 역직렬화 오류가 발생할 수 있다.

2. 필드 추가는 항상 안전

message Payment {
  string id = 1;
  int64 amount = 2;
  string payment_method = 3;
  string receipt_url = 4; // 새 필드 추가
}

새 필드를 추가하는 것은 안전하다. 구버전 클라이언트는 해당 필드를 무시하고, 신버전은 기본값으로 처리한다.

3. 필드 타입 변경 제한

타입 변경은 매우 제한적이다. int32int64, sint32sint64 간 변경은 가능하지만, string에서 int64로의 변경은 불가능하다.

배포 전략

  1. Consumer First: 메시지를 받는 쪽(Consumer)을 먼저 배포
  2. Producer Second: 메시지를 보내는 쪽(Producer)을 나중에 배포
  3. Monitoring: 프로토콜 버전 불일치 메트릭 수집
// 버전 정보를 헤더에 포함
md := metadata.Pairs(
    "proto-version", "v2.1.0",
)
ctx := metadata.NewOutgoingContext(context.Background(), md)

Deprecation 처리

필드를 제거하려면 최소 2번의 배포 사이클이 필요하다.

message Payment {
  string id = 1;
  int64 amount = 2;
  string old_method = 3 [deprecated = true];
  string payment_method = 4;
}

첫 배포에서 deprecated 마킹 후, 모든 서비스가 신규 필드로 전환했음을 확인한 뒤 제거한다.

결론

gRPC의 하위 호환성은 신경 쓸 부분이 많지만, 원칙을 지키면 무중단 배포가 가능하다. 프로토콜 변경 시 리뷰에서 필드 번호 재사용 여부를 반드시 체크하도록 팀 가이드라인에 추가했다.