LabHub
배우기 러닝패스 코스

필드 번호를 바꿨더니 옛 클라이언트가 조용히 틀린 값을 읽었다 · 번호는 이름보다 오래 산다 · 실습

옛 클라이언트 앞에서 스키마를 바꾼다

LabHub 에서 이어서 보기

목표

v1 스키마를 코드에 박아 둔 "옛 클라이언트" 를 그대로 둔 채 스키마를 v2 로 바꿉니다.
새 필드를 새 번호로 더하고, 지운 필드를 reserved 로 봉인하고, **일부러 번호를
바꾼 바이트**를 옛 클라이언트가 어떻게 오류 없이 틀리게 읽는지 재현합니다. 그 뒤
기본값과 presence 의 함정을 실측하고, optionalreserved 를 갖춘 최종
스키마와 호환성 표를 남깁니다.

왜 중요한가

클라이언트와 서버는 절대 같은 순간에 배포되지 않습니다. 롤백도 있고, 로그에
남은 옛 바이트도 있습니다. 그래서 스키마 변경의 안전은 "컴파일이 되는가" 가
아니라 "옛 바이너리가 새 바이트를, 새 바이너리가 옛 바이트를 옳게 읽는가" 로
판단해야 합니다. 와이어에는 이름이 없고 번호만 있으므로, 번호를 바꾸는 것은
필드를 지우고 새로 만드는 것과 같습니다 — 그런데 파서는 아무 오류도 내지 않고
바뀐 번호의 값을 옛 이름으로 읽습니다. 이 실습은 그 사건을 재현해서 기억에
새기는 것이 목적입니다.

단계

1. python3 /opt/app/grpc/old_client.py /opt/app/grpc/order_v1.bin 을 돌려 출력 네 줄을 /root/grpc/evolve/01-old.txt 에 그대로 저장하세요.
2. /root/grpc/evolve/v2.proto 를 쓰세요. syntax = "proto3"; 로 시작하고 message Order 에 v1 의 int32 id = 1; int32 qty = 2; string note = 3; 을 그대로 두고 int64 unit_pricestring currency새 번호로 더합니다.
3. v2.proto 에서 note 를 지우고 reserved 3;reserved "note"; 를 남기세요. 나머지는 그대로입니다.
4. /root/grpc/evolve/make_v2.py 에서 /opt/app/grpc/pbmini.pyid=9001, qty=2, unit_price=15000, currency="KRW" 를 v2.proto 의 번호대로 인코딩해 /root/grpc/evolve/order_v2.bin 에 저장하고, 옛 클라이언트로 그 파일을 읽은 출력을 /root/grpc/evolve/04-old-reads-v2.txt 에 저장하세요.
5. /opt/app/grpc/renumbered.bin/opt/app/grpc/renumbered.proto(번호를 다시 매긴 스키마)로 만든 같은 주문입니다. 옛 클라이언트로 읽은 출력을 /root/grpc/evolve/05-renumbered.txt 에 저장하고, 그 아래에 truth: id=<진짜 id> qty=<진짜 qty> 한 줄과 why: <왜 오류 없이 틀린 값이 나왔는지> 한 줄을 적으세요.
6. pbmini 로 id=9002, currency="KRW" 이고 qty=0 인 주문을 두 가지로 만드세요. /root/grpc/evolve/06-zero.bin 은 암묵적 presence(0 이면 보내지 않음), /root/grpc/evolve/06-zero-explicit.bin 은 명시적 presence(0 도 보냄). /root/grpc/evolve/06-presence.txtimplicit=<16진수> explicit=<16진수> implicit_qty=<옛 클라이언트가 읽은 qty> explicit_qty=<옛 클라이언트가 읽은 qty> 네 줄을 적으세요.
7. 최종 /root/grpc/evolve/Order.proto 를 쓰세요. optional int32 qty = 2;, reserved 3; + reserved "note";, unit_price·currency, 그리고 message Customer { string name = 1; int32 tier = 2; } 와 Order 의 Customer customer = <새 번호>; 가 들어갑니다.
8. /root/grpc/evolve/compat.md 에 마크다운 표를 쓰세요. 첫 열은 add-new-number remove-and-reserve reuse-number renumber int32-to-string rename-only 여섯 키, 둘째 열은 safe 또는 unsafe, 셋째 열은 이유 한 줄입니다.

참고

단계 8개

  1. 옛 클라이언트로 v1 바이트를 읽는다
  2. 새 필드는 새 번호로
  3. 지운 번호를 봉인한다
  4. v2 바이트를 옛 클라이언트가 읽는다
  5. 번호를 바꾼 바이트를 옛 클라이언트가 읽는다
  6. qty=0 은 보낸 것인가 안 보낸 것인가
  7. optional 과 reserved 를 갖춘 최종 스키마
  8. 호환성 표를 쓴다