본문으로 건너뛰기
Abseil Code Review · 17/79

absl::Status payload — 구조화된 에러 컨텍스트

· Hawk · 4분 읽기

한 줄 요약: absl::Status의 payload는 메시지 외에 구조화된 데이터를 첨부하는 메커니즘이다. URL-based key로 namespace를 분리해 라이브러리 간 충돌을 막고, gRPC error_details와의 연동을 위해 설계되었다.

#어떤 문제를 푸는가

에러 메시지만으로는 부족한 경우가 있다.

return absl::FailedPreconditionError(
"rate limit exceeded; retry after 5 seconds; user=alice");
  • 읽기 어려움 — 사람이 파싱해야 함.
  • 자동 처리 불가 — UI가 “5초 후 재시도” 카운트다운 표시 못 함.
  • 확장 어려움 — 새 정보 추가하려면 메시지 형식 합의 필요.

구조화된 데이터가 필요하다. Status payload가 그것이다.

auto s = absl::FailedPreconditionError("rate limit exceeded");
s.SetPayload(
"type.googleapis.com/google.rpc.RetryInfo",
SerializeRetryInfo({.retry_after = absl::Seconds(5)}));
// 호출자는 payload를 꺼내 자동 처리 가능

#API

class Status {
public:
// payload 설정
void SetPayload(absl::string_view type_url, absl::Cord payload);
// payload 조회
absl::optional<absl::Cord> GetPayload(absl::string_view type_url) const;
// 모든 payload 순회
void ForEachPayload(
absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor
) const;
// payload 제거
bool ErasePayload(absl::string_view type_url);
};

absl::Cord는 큰 string을 효율적으로 다루는 type. payload는 보통 직렬화된 protobuf다.

#URL-based key

key가 string이 아니라 URL인 것이 핵심이다.

// 잘 정의된 namespace
"type.googleapis.com/google.rpc.RetryInfo"
"type.googleapis.com/google.rpc.DebugInfo"
"type.googleapis.com/google.rpc.QuotaFailure"
// 사용자 정의
"myorg.example.com/MyError"
"github.com/myproject/internal/error_type"

이유는 충돌 방지. 라이브러리 A가 “rate_limit”이라는 key를 쓰고 라이브러리 B도 같은 key를 쓰면 곱하기. URL은 자연스럽게 namespace를 분리한다.

Google은 type.googleapis.com/<protobuf message name> 형식을 표준으로 정했다. protobuf의 Any type과 같은 컨벤션.

#gRPC error_details와의 통합

gRPC는 status에 구조화된 에러 정보를 담는 방법으로 google.rpc.Status를 정의한다. details 필드가 임의의 protobuf를 담는다.

message Status {
int32 code = 1;
string message = 2;
repeated google.protobuf.Any details = 3;
}

Abseil의 Status payload는 이 details와 1

매핑된다.

// gRPC에서 받은 status를 Abseil status로 변환
absl::Status FromGrpcStatus(const grpc::Status& gs) {
absl::Status s(static_cast<absl::StatusCode>(gs.error_code()),
gs.error_message());
// details를 payload로 옮김
google::rpc::Status proto_status;
if (proto_status.ParseFromString(gs.error_details())) {
for (const auto& detail : proto_status.details()) {
s.SetPayload(detail.type_url(), absl::Cord(detail.value()));
}
}
return s;
}

#표준 protobuf 메시지

Google이 제공하는 표준 error detail은 google.rpc.error_details 정의에 있다.

Type URL용도
RetryInfo재시도 권장 시간
DebugInfo디버깅용 stack trace, detail
QuotaFailurequota 초과 항목 목록
PreconditionFailure전제 위반의 종류
BadRequest잘못된 필드 목록
RequestInfo원래 요청 식별자
ResourceInfo영향받은 리소스
Help도움말 링크
LocalizedMessage사용자에게 보여줄 다국어 메시지

이 protobuf들을 사용하면 gRPC ecosystem 전체와 호환된다.

#사용 예시

#RetryInfo

#include "google/rpc/error_details.pb.h"
absl::Status MakeRateLimitError() {
auto s = absl::ResourceExhaustedError("rate limit exceeded");
google::rpc::RetryInfo retry;
retry.mutable_retry_delay()->set_seconds(5);
s.SetPayload(
"type.googleapis.com/google.rpc.RetryInfo",
absl::Cord(retry.SerializeAsString()));
return s;
}
void HandleError(const absl::Status& s) {
auto retry_data = s.GetPayload("type.googleapis.com/google.rpc.RetryInfo");
if (retry_data) {
google::rpc::RetryInfo retry;
retry.ParseFromString(std::string(*retry_data));
absl::Duration delay = absl::Seconds(retry.retry_delay().seconds())
+ absl::Nanoseconds(retry.retry_delay().nanos());
// 자동 재시도 스케줄링
ScheduleRetry(delay);
}
}

#BadRequest — 필드 검증

absl::Status ValidateUser(const User& u) {
if (u.name().empty() && u.email().empty()) {
google::rpc::BadRequest bad;
if (u.name().empty()) {
auto* v = bad.add_field_violations();
v->set_field("name");
v->set_description("must not be empty");
}
if (u.email().empty()) {
auto* v = bad.add_field_violations();
v->set_field("email");
v->set_description("must not be empty");
}
auto s = absl::InvalidArgumentError("validation failed");
s.SetPayload("type.googleapis.com/google.rpc.BadRequest",
absl::Cord(bad.SerializeAsString()));
return s;
}
return absl::OkStatus();
}

UI는 어느 필드가 잘못됐는지 정확히 알 수 있다.

#내부 구현

// 의사 코드
class StatusRep {
StatusCode code;
std::string message;
// payload는 보통 없음 — error의 일부 시나리오에서만
std::unique_ptr<absl::flat_hash_map<std::string, absl::Cord>> payloads;
};

payload는 lazy. 첫 SetPayload 호출 시점에 hash map 할당. payload가 없는 status는 추가 메모리 비용 없음.

이 lazy 구조는 “대부분의 error는 payload 없음”이라는 관측을 활용한다. payload는 RPC boundary나 자동 처리가 필요한 특수 케이스에서만 쓰이고, 일상적인 validation error에는 message만 충분하다.

#코드 리뷰 포인트

// 회피 — 그냥 string으로 구조화된 정보 인코딩
return absl::FailedPreconditionError(
absl::StrCat("retry_after=", 5, "; user=", user));
// Good — payload 사용
auto s = absl::FailedPreconditionError("rate limit");
google::rpc::RetryInfo r;
r.mutable_retry_delay()->set_seconds(5);
s.SetPayload("type.googleapis.com/google.rpc.RetryInfo",
absl::Cord(r.SerializeAsString()));
return s;
// 회피 — URL 형식 안 지킴
s.SetPayload("my_error", payload);
// 충돌 위험. 다른 라이브러리도 "my_error"를 쓸 수 있음.
// Good — namespace 명시
s.SetPayload("myorg.example.com/MyError", payload);
// 회피 — payload에 raw binary 직접 넣기
s.SetPayload("...", absl::Cord(reinterpret_cast<const char*>(&data), sizeof(data)));
// portability, versioning, language compatibility 문제.
// Good — protobuf 또는 다른 명시적 직렬화
s.SetPayload("...", absl::Cord(my_message.SerializeAsString()));

리뷰에서:

  1. 메시지에 구조화 정보를 인코딩하지 않았는가 — payload로 옮길 것.
  2. URL이 namespace 분리되어 있는가.
  3. payload가 protobuf 같은 portable 형식인가.

#자주 보는 안티패턴

// 회피 — payload를 너무 자주 사용
return absl::NotFoundError("user not found").SetPayload(...);
// 단순한 NotFound는 message만으로 충분.
// payload는 자동 처리가 필요할 때만.
// 회피 — payload type을 caller마다 다르게
// Server A: SetPayload("retry_info_v1", ...);
// Server B: SetPayload("RetryInfo", ...);
// 표준 URL을 모르고 각자 정의.
// Good — Google 표준 URL 사용
"type.googleapis.com/google.rpc.RetryInfo"
// 회피 — payload를 message에도 중복 인코딩
auto s = absl::InvalidArgumentError(
absl::StrCat("validation failed: field=", field));
s.SetPayload("...", SerializeBadRequest({field}));
// message는 사람용 요약, payload는 기계용 구조. 분리.
// Good
auto s = absl::InvalidArgumentError("validation failed");
s.SetPayload("...", SerializeBadRequest({field}));

#std와의 비교

표준 C++에는 비교할 만한 것이 없다. std::error_code에는 message조차 첨부할 수 없다. std::exception은 message 하나뿐.

언어 비교:

언어메커니즘
RustError::source() chain
Goerrors.Wrap, error.As()
Javaexception chain
Python__cause__, custom attributes
C++ (Abseil)URL-keyed payload map

Abseil의 URL key는 protobuf Any type에서 차용. cross-language portability를 노린 설계다.

#정리

  • absl::Status::SetPayload는 메시지 외에 구조화된 데이터를 첨부.
  • key는 URL 형식. namespace 충돌 방지.
  • gRPC error_details와 1
    매핑.
  • Google이 표준 error_details protobuf 제공 (RetryInfo, BadRequest 등).
  • 일상적인 에러에는 불필요. RPC boundary, 자동 처리 시에 사용.

#다음 편

Part 3-05에서 Status와 exception 사이 변환을 본다. exception을 던지는 라이브러리와 Status 기반 코드를 어떻게 묶고, gRPC status / std::error_code와는 어떻게 변환하는지.

#관련 항목

Abseil Code Review · 18 of 79

  1. 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
  2. 2 Abseil 개요 — Google이 std를 보완한 이유
  3. 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
  4. 4 Abseil 빌드와 의존성 — Bazel vs CMake
  5. 5 Abseil LTS vs HEAD 릴리스 모델 분석
  6. 6 Abseil Versioning과 ABI 호환성 정책
  7. 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
  8. 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
  9. 9 absl::LogSeverity — 로그 레벨 타입
  10. 10 Abseil type_traits — negation·conjunction·void_t
  11. 11 Abseil Conformance·Policy 분석
  12. 12 Abseil Memory utilities 분석
  13. 13 Abseil raw_logging — heap-free 로깅
  14. 14 Abseil thread_annotations — clang TSA 통합
  15. 15 absl::Status — exception-free error handling
  16. 16 absl::StatusOr<T> — 값 또는 에러
  17. 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
  18. 18 absl::Status payload — 구조화된 에러 컨텍스트
  19. 19 absl::Status ↔ exception 변환 패턴
  20. 20 absl::string_view — non-owning 문자열 참조
  21. 21 absl::string_view 함정 — dangling·c_str·임시 객체
  22. 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
  23. 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
  24. 24 absl::StrJoin — 컨테이너 결합과 Formatter
  25. 25 absl::StrFormat — type-safe printf·FormatSpec
  26. 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
  27. 27 Abseil Escape — CEscape·HexEscape·Base64
  28. 28 absl::flat_hash_map — Swiss Table 기반 hash map
  29. 29 absl::flat_hash_set — set 버전 Swiss Table
  30. 30 absl::node_hash_map — stable pointer가 필요할 때
  31. 31 absl::btree_map — sorted·cache-friendly B-tree
  32. 32 absl::FixedArray — 런타임 크기 stack 배열
  33. 33 absl::InlinedVector — small buffer optimization
  34. 34 Abseil Swiss Table internals — control byte·SIMD probing
  35. 35 absl::Mutex — reader-writer·fairness·deadlock 검출
  36. 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
  37. 37 absl::Notification — once-only signal
  38. 38 absl::BlockingCounter·Barrier — 다중 thread 조율
  39. 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
  40. 40 absl::Time·Duration 분석 — 단단한 type
  41. 41 absl::Time Format·Parse
  42. 42 absl::CivilTime 분석
  43. 43 absl::time_zone 분석
  44. 44 absl::Time mocking — 테스트 친화 시간
  45. 45 absl::BitGen — 모던 난수 생성기
  46. 46 Abseil Random Distributions — Uniform·Exponential
  47. 47 Abseil Mocking Random — 테스트 결정성
  48. 48 Abseil Random Seeding·Entropy
  49. 49 absl::int128·uint128 분석
  50. 50 absl::bits — popcount·countl_zero
  51. 51 absl::optional vs std::optional
  52. 52 absl::variant 분석
  53. 53 absl::span 분석
  54. 54 absl::any 분석
  55. 55 absl::compare — three-way 비교
  56. 56 Abseil utility — apply·in_place
  57. 57 Abseil AbslHashValue 분석
  58. 58 Abseil HashState chaining
  59. 59 Abseil Custom hashable 구현
  60. 60 Abseil LOG·VLOG·CHECK 분석
  61. 61 Abseil LogSink 분석
  62. 62 Abseil LogEntry·structured logging
  63. 63 Abseil Stack trace·failure_signal_handler
  64. 64 ABSL_FLAG 정의 분석
  65. 65 Abseil ParseCommandLine 동작
  66. 66 Abseil Flag introspection·validation
  67. 67 Google 스타일의 Abseil 사용 패턴
  68. 68 Abseil 자주 보는 anti-pattern
  69. 69 std → absl 마이그레이션 전략
  70. 70 absl::Cleanup — 함수 종료 시 실행 보장
  71. 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
  72. 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
  73. 73 absl::bind_front와 Overload — 함수 객체 보조 도구
  74. 74 absl::Cord — 분산 시스템용 대용량 문자열
  75. 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
  76. 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
  77. 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
  78. 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
  79. 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피