- Published on
Protobuf Edition 2026 — 조여지는 기본값 세 개와, protoc가 처음 강제하는 스키마 크기 한계
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 발표보다 나흘 먼저 나온 RC
- 에디션이라는 장치를 한 문단으로
- protoc가 edition = "2026"을 받아주기 시작한 지점
- 바뀌는 기본값 ①: enforce_naming_style이 STYLE2026으로
- 바뀌는 기본값 ②: default_symbol_visibility가 STRICT로
- 바뀌는 기본값 ③: enforce_proto_limits — 여기가 진짜 변화입니다
- 검사는 protoc가 켜고, 런타임은 끕니다
- 새로 들어오는 것들 (짧게)
- 정직한 트레이드오프와 한계
- 그래서, 지금 뭘 해야 하나
- 마치며
- 참고 자료
들어가며 — 발표보다 나흘 먼저 나온 RC
2026년 7월 13일, protobuf.dev의 뉴스 페이지에 Edition 2026 발표가 올라왔습니다. 사흘 전 일입니다. 그런데 GitHub 릴리스 목록을 보면 v36.0-rc1은 그보다 나흘 앞선 7월 9일에 이미 published 상태였습니다. 발표문이 "36.x에서 2026년 3분기에 릴리스할 계획"이라고 쓰는 동안, 그 36.x의 첫 RC는 이미 나와 있었던 셈입니다.
이 글은 그 발표문을 요약하는 글이 아닙니다. 발표문은 짧고, 몇 군데는 실제 구현과 어긋나 있으며, 정작 가장 흥미로운 변화 하나를 통째로 빠뜨리고 있습니다. 그래서 v36.0-rc1 태그의 실제 소스 — descriptor.proto, descriptor.h, code_generator.h, command_line_interface.cc — 를 직접 읽고 확인한 내용을 정리합니다.
먼저 결론부터. Edition 2026은 문법을 하나도 추가하지 않습니다. 발표문의 마지막 줄이 그대로입니다 — "There is no new or removed grammar in Edition 2026." 대신 기본값 세 개를 조입니다. 그리고 그중 하나는 protoc가 15년 넘게 하지 않던 일입니다.
에디션이라는 장치를 한 문단으로
Protocol Buffers 자체나 바이너리 직렬화 포맷 비교는 이미 다뤘으니, 에디션만 짧게 짚습니다. 예전에는 파일 맨 위에 syntax = "proto2" 또는 syntax = "proto3"를 적었고, 그 한 글자가 필드 프레즌스·enum 개폐·기본 패킹 여부 등 서로 무관한 동작 여러 개를 한 덩어리로 결정했습니다. 에디션은 이 덩어리를 개별 기능(feature) 으로 분해하고, 파일 맨 위에는 edition = "2024" 같은 연도만 적게 합니다. 각 기능은 에디션마다 다른 기본값을 갖고, 필요하면 파일이나 특정 요소에서 개별적으로 덮어쓸 수 있습니다.
핵심은 이겁니다 — 에디션을 올리지 않으면 아무것도 바뀌지 않습니다. 기본값은 에디션에 묶여 있으므로, edition = "2023"으로 남아 있는 파일은 Edition 2026의 기본값 변경을 전혀 겪지 않습니다. 새 에디션은 "이제부터 이렇게 하는 게 낫다"를 기본값으로 선언하는 장치이지, 기존 파일을 깨는 장치가 아닙니다.
descriptor.proto의 Edition enum을 보면 릴리스된 에디션이 이렇게 나열돼 있습니다.
// Editions that have been released. The specific values are arbitrary and
// should not be depended on, but they will always be time-ordered for easy
// comparison.
EDITION_2023 = 1000;
EDITION_2024 = 1001;
EDITION_2026 = 1002;
2025가 없습니다. 값은 1000, 1001, 1002로 연속이니 건너뛴 게 아니라 애초에 만들어지지 않았습니다. (주석이 "값 자체는 임의이며 의존하지 말 것, 다만 항상 시간순"이라고 못박고 있으니, 연도 사이 간격에 의미를 부여할 이유도 없습니다.)
protoc가 edition = "2026"을 받아주기 시작한 지점
"Edition 2026이 나왔다"가 정확히 어느 릴리스부터인지는 code_generator.h 한 줄로 확인됩니다.
// The maximum edition supported by protoc.
constexpr auto ProtocMaximumEdition() { return Edition::EDITION_2026; }
이 값을 릴리스 태그별로 훑어보면 경계가 선명합니다.
v32.0 (2025-08-14) ProtocMaximumEdition() = EDITION_2024
v33.0 (2025-10-15) ProtocMaximumEdition() = EDITION_2024
v34.0 (2026-02-25) ProtocMaximumEdition() = EDITION_2024
v35.0 (2026-05-19) ProtocMaximumEdition() = EDITION_2024
v35.1 (2026-06-11) ProtocMaximumEdition() = EDITION_2024 <- 현재 stable
v36.0-rc1 (2026-07-09) ProtocMaximumEdition() = EDITION_2026 <- 여기서 처음
즉 v36.0-rc1이 edition = "2026"을 파일 맨 위에 적을 수 있는 최초의 protoc입니다. 그 이전 protoc에 2026 파일을 물리면 최대 지원 에디션 초과로 거절당합니다. 그리고 오늘(2026-07-16) 기준으로 stable은 여전히 v35.1이고, v36.0은 GA가 아니라 RC입니다.
같은 방식으로 과거를 검증해 보면 트랙 레코드도 확인됩니다. Edition 2024 발표는 2025년 6월 27일에 "32.x에서 2025년 3분기에" 릴리스하겠다고 했고, v32.0은 2025년 8월 14일에 나왔으며 거기서 ProtocMaximumEdition()이 EDITION_2024로 올라갔습니다. 약속대로였습니다. 이번 발표문도 같은 형식으로 "36.x, 2026년 3분기"라고 말하고 있고, RC는 이미 나와 있습니다.
바뀌는 기본값 ①: enforce_naming_style이 STYLE2026으로
descriptor.proto의 기본값 테이블은 이렇습니다.
enum EnforceNamingStyle {
ENFORCE_NAMING_STYLE_UNKNOWN = 0;
STYLE2024 = 1;
STYLE_LEGACY = 2;
STYLE2026 = 3;
}
// edition_defaults:
// EDITION_LEGACY -> STYLE_LEGACY
// EDITION_2024 -> STYLE2024
// EDITION_2026 -> STYLE2026
발표문은 규칙을 이렇게 설명합니다 — x라는 필드가 있는 메시지 안에서 has_x, set_x, get_x, clear_x, x_value는 금지되고, 필드 이름으로 descriptor를 쓸 수 없다.
그런데 실제 구현(descriptor.cc, 2026-04-07 커밋)을 읽으면 발표문보다 정교합니다. 금지 접두사는 has_, get_, set_, clear_ 네 개이고 금지 접미사는 _value 하나인데, 무조건 금지가 아니라 조건부입니다.
if (absl::StartsWith(name, prefix)) {
absl::string_view without_prefix = name;
without_prefix.remove_prefix(prefix.size());
if ((message->FindFieldByName(without_prefix) != nullptr ||
message->FindOneofByName(without_prefix) != nullptr)) {
*error = absl::StrCat("should not begin with ", prefix,
" if a field named ", without_prefix,
" exists. This can cause collisions in "
"generated code.");
return false;
}
}
같은 메시지 안에 짝이 되는 x가 실제로 존재할 때만 has_x가 에러입니다. x가 없다면 has_x라는 이름은 그냥 통과합니다. 즉 이건 명명 규칙이라기보다 충돌 검사입니다 — 생성 코드에서 has_x() 접근자와 has_x 필드가 같은 심볼로 부딪히는 상황만 정확히 겨냥합니다. 그리고 짝을 찾을 때 필드뿐 아니라 oneof 이름까지 뒤지고(FindOneofByName), 검사 자체도 필드와 oneof 양쪽에 적용됩니다. 반면 descriptor는 조건 없이 금지입니다.
옵트아웃이 2단이라는 점도 발표문에는 없습니다. 소스에 에러 메시지 상수가 두 개 있습니다.
constexpr absl::string_view kNamingStyleOptOutMessage =
" (features.enforce_naming_style = STYLE_LEGACY can be used to opt out of "
"this check)";
constexpr absl::string_view kNamingStyleCollisionsOptOutMessage =
" (features.enforce_naming_style = STYLE2024 can be used to opt out of "
"this check)";
STYLE2024로 내리면 2026에서 새로 생긴 충돌 검사만 끄고 2024의 스타일 검사는 유지합니다. 전부 끄려면 STYLE_LEGACY로 내려야 합니다. 마이그레이션할 때 실제로 쓸 손잡이는 대개 앞쪽입니다.
마지막으로 사소하지만 재미있는 흠 하나. 위 enum에서 STYLE_LEGACY가 2이고 STYLE2024가 1이라 숫자 순서와 엄격함 순서가 어긋납니다. 그래서 "이 스타일 이상인가"를 판정하는 헬퍼에 특수 케이스가 박혀 있습니다.
bool IsStyleOrGreater(const DescriptorT* descriptor,
FeatureSet::EnforceNamingStyle style) {
return internal::InternalFeatureHelper::GetFeatures(*descriptor)
.enforce_naming_style() >= style &&
// Required because STYLE_LEGACY comes after STYLE2024 in enum
// definition.
internal::InternalFeatureHelper::GetFeatures(*descriptor)
.enforce_naming_style() != FeatureSet::STYLE_LEGACY;
}
enum 값 순서를 나중에 바꿀 수 없으니 코드로 우회한 자국입니다. 프로토콜 진화를 다루는 프로젝트가 자기 enum의 진화에 걸린 셈인데, 이런 게 남아 있는 게 오히려 정상입니다.
바뀌는 기본값 ②: default_symbol_visibility가 STRICT로
Edition 2024가 export / local 키워드와 default_symbol_visibility 기능을 도입했고, 그때 기본값은 EXPORT_TOP_LEVEL — 최상위 심볼은 export, 중첩 심볼은 local — 이었습니다. Edition 2026은 이걸 STRICT로 올립니다.
descriptor.proto의 주석이 값의 의미를 그대로 설명합니다.
// Default pre-EDITION_2024, all UNSET visibility are export.
EXPORT_ALL = 1;
// All top-level symbols default to export, nested default to local.
EXPORT_TOP_LEVEL = 2;
// All symbols default to local.
LOCAL_ALL = 3;
// All symbols local by default. Nested types cannot be exported.
// With special case caveat for message { enum {} reserved 1 to max; }
// This is the recommended setting for new protos.
STRICT = 4;
STRICT에서 달라지는 건 세 가지입니다 — 모든 심볼이 기본 local이고, export/local 키워드는 최상위 message/enum 선언에만 쓸 수 있으며, 중첩 심볼에 이 키워드를 붙이면 문법 에러입니다. LOCAL_ALL과의 차이가 여기 있습니다. LOCAL_ALL은 기본값만 local로 바꿀 뿐 중첩 타입을 export로 되살릴 수 있지만, STRICT는 그 탈출구 자체를 없앱니다.
다만 탈출구가 완전히 없지는 않고, C++ 네임스페이스 오염을 피하려고 쓰던 관용구 하나에 카브아웃이 있습니다. 최상위 message가 local이고 모든 필드가 reserved일 때에 한해 중첩 export enum이 허용됩니다.
local message MyNamespaceMessage {
export enum Enum {
MY_VAL = 1;
}
// Ensure no fields are added to the message.
reserved 1 to max;
}
메시지를 순수한 네임스페이스 껍데기로만 쓰는 경우 — 필드를 하나도 담지 않겠다고 reserved 1 to max;로 못박은 경우 — 만 봐주겠다는 뜻입니다. 조건이 이렇게 빡빡한 건 이게 일반적인 우회로로 쓰이는 걸 막으려는 의도로 읽힙니다.
이 변경의 명분은 1-1-1 베스트 프랙티스입니다. .proto 파일 하나에 메시지 하나, 그리고 그 파일만 임포트하게 해서 의존성 비대화를 막자는 것. 이게 왜 크기 문제냐면, 심볼 가시성 문서가 설명하듯 protobuf는 디스크립터 데이터를 FileDescriptorSet/FileDescriptor 단위로 묶게 돼 있어서, 한 파일 안의 모든 정의와 그 파일이 임포트한 것들이 통째로 딸려오기 때문입니다.
바뀌는 기본값 ③: enforce_proto_limits — 여기가 진짜 변화입니다
그리고 이게 7월 13일 발표문에 한 글자도 없는 기능입니다. 발표문의 "Changes to Existing Features"에는 앞의 둘만 있고, "New Features"에는 JSON enum 값 커스텀 문자열, C++ namespace 옵션, C++ 커스텀 옵션 분리, C# nullable 참조 타입만 있습니다. 그런데 v36.0-rc1의 descriptor.proto에는 이게 들어 있습니다.
message ProtoLimitsFeature {
enum EnforceProtoLimits {
PROTO_LIMITS_UNKNOWN = 0;
// Default pre-EDITION_2026: there are no limit enforcement at the protoc
// level. Practical limits still exist, but they will tend to fail while
// compiling protoc-generated code, and these limits tend to be language
// or toolchain specific.
LEGACY_NO_EXPLICIT_LIMITS = 1;
// A set of limits enforced by Edition 2026 by default. For a detailed
// list of all the limits please consult the Edition 2026 documentation.
PROTO_LIMITS2026 = 2;
}
}
저 주석의 두 번째 문장이 이 기능의 존재 이유 전부입니다. 한계는 원래부터 있었습니다. 다만 protoc는 그걸 몰랐고, 그래서 실패가 엉뚱한 데서 터졌습니다. descriptor.h의 주석이 더 직설적입니다.
// Enforce protobuf limits at the descriptor level. Pre Edition 2026, there
// is no limit enforcement in the protobuf compiler when parsing proto files.
// As a result, it is possible to write a protobuf file that compiles in
// protoc, and code generation works as intended, but the code generation
// output is uncompilable because it runs into language-specific or
// compiler-specific limits.
//
// Starting in Edition 2026, certain limits will start to be enforced. The
// goal of this enforcement is to help ensure that any file that is accepted
// by the protobuf compiler will be compilable on standard tool chains.
void EnforceProtoLimits(bool enforce) { enforce_proto_limits_ = enforce; }
"protoc에서는 컴파일되고 코드 생성도 의도대로 되는데, 생성된 코드가 컴파일이 안 되는" 상황. protobuf를 크게 굴려 본 사람은 이 문장이 무슨 뜻인지 압니다. enum에 값을 계속 추가하다가 어느 날 javac가 상수 관련 에러를 뱉고, 그게 내 .proto 때문이라는 걸 깨닫기까지 한참 걸리는 그 경험입니다.
지금까지 이 한계들이 어떻게 관리됐는지 보면 더 재미있습니다. protobuf.dev에는 Proto Limits라는 문서가 있는데, 그 문서가 스스로를 이렇게 소개합니다.
This information is a collection of discovered limitations by many engineers, but is not exhaustive and may be incorrect/outdated in some areas.
즉 여러 엔지니어가 얻어맞으면서 발견한 것들을 모아 둔 문서이고, 완전하지도 않고 일부는 틀렸거나 낡았을 수 있다고 인정합니다. 실제로 그 안의 숫자들은 이런 식입니다 — 메시지당 필드는 65,535개가 상한이지만 Boolean 같은 단일 필드만 있는 메시지는 "~2100개(proto2)", "~3100개(proto3, optional 미사용)"에서 깨지고, enum은 "가장 낮은 한계가 Java에서 ~1700개"입니다. 물결표가 붙은 민속지식입니다.
Edition 2026은 이 민속지식을 protoc가 강제하는 숫자로 바꿉니다. descriptor.h의 상수가 이렇습니다.
// Edition 2026 introduces default limits for proto files as the descriptor gets
// built from protoc. To opt out of these limits for edition 2026, you may use
// features.enforce_proto_limits = LEGACY_NO_EXPLICIT_LIMITS.
inline constexpr int kLimit2026FieldsPerMessage = 1500;
inline constexpr int kLimit2026OneofsPerMessage = 1000;
inline constexpr int kLimit2026FieldsPerOneof = 1200;
inline constexpr int kLimit2026ValuesPerEnum = 1700;
네 숫자는 세 군데에서 일치합니다 — 위 헤더 상수, 이 기능을 넣은 커밋 메시지(2026-05-14), 그리고 Feature Settings for Editions 문서의 enforce_proto_limits 절. 셋 다 같은 값입니다.
Edition 2024 이하 Edition 2026
메시지당 필드 제한 없음(protoc 기준) 1500
메시지당 oneof 제한 없음 1000
oneof당 필드 제한 없음 1200
enum당 값 제한 없음 1700
여기서 눈에 띄는 건 enum의 1700입니다. Proto Limits 문서가 말한 "Java에서 ~1700"과 정확히 같은 숫자입니다. 필드의 1500도 문서의 "~2100(proto2)"보다 낮게 잡혀 있습니다. 즉 새 한계는 임의로 정한 라운드 넘버가 아니라, 실제로 깨지는 지점보다 안쪽에 그어진 선으로 보입니다. (다만 protobuf 측이 이 선정 근거를 공개 문서로 밝힌 건 제가 확인한 범위에는 없습니다. 숫자가 일치한다는 사실만 확인된 것이고, 인과는 추정입니다.)
그리고 실무에서 아플 지점 하나. 이 옵트아웃은 파일 단위로 못 겁니다. descriptor.proto에서 이 기능의 targets를 보면 ENUM, MESSAGE, FIELD, ONEOF만 있고 FILE이 없습니다.
optional ProtoLimitsFeature.EnforceProtoLimits enforce_proto_limits = 9 [
retention = RETENTION_SOURCE,
targets = TARGET_TYPE_ENUM,
targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_FIELD,
targets = TARGET_TYPE_ONEOF,
feature_support = {
edition_introduced: EDITION_2026
},
...
];
Feature Settings 문서도 같은 말을 코드 주석으로 적어 뒀습니다 — "Must be set at the level of the oversized message, enum, or oneof as this feature is not allowed at the file level." 파일 맨 위에 한 줄 적어서 통째로 끄는 게 불가능하고, 비대한 메시지·enum·oneof마다 찾아가서 개별로 붙여야 합니다. 거대 레거시 스키마를 Edition 2026으로 올릴 때 이게 그대로 작업량이 됩니다.
또 하나. 위 feature_support의 edition_introduced: EDITION_2026은 이 기능이 Edition 2026 이전에는 설정 자체가 불가능하다는 뜻입니다. 앞의 두 기능은 edition_introduced: EDITION_2024라서 Edition 2024 파일에서도 손댈 수 있지만, 크기 한계 검사는 2026으로 올라가야만 만날 수 있습니다. 미리 켜 보고 준비하는 길이 없습니다.
검사는 protoc가 켜고, 런타임은 끕니다
이 세 검사가 항상 도는 건 아닙니다. command_line_interface.cc를 보면 protoc CLI가 명시적으로 켭니다.
descriptor_pool->EnforceSymbolVisibility(true);
descriptor_pool->EnforceNamingStyle(true);
descriptor_pool->EnforceProtoLimits(true);
반면 DescriptorPool의 멤버 기본값은 꺼져 있습니다 — descriptor.h에 enforce_proto_limits_ = false;, enforce_symbol_visibility_ = false;로 초기화돼 있습니다. 그러니까 런타임에 디스크립터 풀을 직접 만들어 쓰는 코드(동적 스키마 로딩 같은)는 이 검사를 자동으로 받지 않습니다. 이건 합리적입니다 — 이미 protoc를 통과해 존재하는 디스크립터를 런타임에 또 거부해 봐야 얻을 게 없으니까요.
그리고 세 기능 모두 retention = RETENTION_SOURCE입니다. 소스에서만 의미가 있고 생성된 디스크립터에는 실려 나가지 않습니다. 컴파일 타임 린트지 런타임 동작이 아닙니다.
여기서 반드시 짚어야 할 것 — 와이어 포맷은 아무것도 바뀌지 않습니다. 에디션은 인코딩을 건드린 적이 없고 Edition 2026도 마찬가지입니다. 필드 번호와 와이어 타입으로 굴러가는 바이트열은 그대로입니다. 이 글의 어떤 것도 이미 배포된 서비스 간의 호환성에 영향을 주지 않습니다.
새로 들어오는 것들 (짧게)
발표문이 "New Features"로 든 것들은 대체로 각 언어 바인딩 사정입니다.
- enum 값의 JSON 커스텀 문자열. 기존
json_name이 키 이름을 바꿨다면, 이제 값도 바꿉니다 —FOO_BAR = 5 [(pb.enumvalue.json).string = "custom_string_here"];. 발표문이 든 이유는 충돌 회피, 마이그레이션 보조, 외부 요구사항 충족입니다. - C++
namespace옵션.option (pb.file.cpp).namespace = "clock_time";처럼 생성 바인딩의 네임스페이스를.proto패키지와 독립적으로 지정합니다. - C++ 커스텀 옵션이
descriptor.proto밖으로. C++ 전용 옵션이 별도 파일로 빠지고,import option "google/protobuf/cpp_features.proto";로 가져다 씁니다. 이 절에는repeated_type = PROXY와RepeatedFieldProxy이야기도 섞여 있는데, 발표문 마크다운의 코드 펜스가 깨져 있어 어느 절에 속한 내용인지 원문만으로는 애매합니다. - C# nullable 참조 타입. opt-in이고,
v36.0-rc1릴리스 노트에도 "Advertise that C# supports Edition 2026, and move C# Nullable Reference Type support into that edition"으로 대응 항목이 있습니다.
정직한 트레이드오프와 한계
아직 GA가 아닙니다. 오늘 기준 stable은 v35.1이고 v36.0은 rc1입니다. 발표문 자신이 이렇게 못박습니다.
These describe changes as we anticipate them being implemented, but due to the flexible nature of software some of these changes may not land or may vary from how they are described in this topic.
"착륙하지 않을 수도 있고 설명과 다를 수도 있다." 이 글의 숫자와 동작은 전부 v36.0-rc1 태그 기준이고, GA에서 바뀔 수 있습니다.
발표문과 코드가 어긋납니다. enforce_proto_limits는 코드에 있고 문서(Feature Settings)에도 있는데 7월 13일 발표문에는 없습니다. 발표문만 읽고 Edition 2026을 판단하면 가장 파급력 큰 변화를 통째로 놓칩니다. 반대로 Feature Settings 문서의 enforce_proto_limits 예제는 "비대한 메시지를 유지하려면 오버라이드가 필요하다"고 써 놓고 정작 PROTO_LIMITS2026을 설정하는(끄는 게 아니라 켜는) 코드를 보여 줍니다. 문서가 갓 쓰여서 거친 상태입니다. 규범적 사실은 descriptor.proto의 targets/edition_defaults에서 확인하는 게 안전합니다.
언어 지원은 따로 붙습니다. 여기 나온 건 대부분 protoc와 C++ 런타임 이야기입니다. 각 언어 런타임이 Edition 2026을 지원한다고 광고해야 실제로 쓸 수 있고(C# 항목이 릴리스 노트에 따로 있는 이유가 그것), 서드파티 구현이나 protoc를 쓰지 않는 툴체인은 별도 문제입니다.
이득이 눈에 보이는 종류가 아닙니다. 이 세 기능 중 무엇도 메시지를 작게 만들거나 파싱을 빠르게 하지 않습니다. 전부 컴파일 타임 린트입니다. 얻는 건 "나중에 이상한 데서 터질 일이 지금 명확한 에러로 터진다"이고, 그게 값이 나가는 팀이 있고 아닌 팀이 있습니다.
그래서, 지금 뭘 해야 하나
당장 할 게 없는 경우가 대부분입니다. syntax = "proto2" / syntax = "proto3"를 쓰고 있다면 Edition 2026은 오늘 당신과 아무 상관이 없습니다. descriptor.proto의 기본값 테이블상 proto2/proto3/Edition 2023은 세 기능 모두 레거시 값(STYLE_LEGACY, EXPORT_ALL, LEGACY_NO_EXPLICIT_LIMITS)이고, 에디션을 올리지 않는 한 그대로입니다. 서두를 이유가 없습니다.
Edition 2024를 이미 쓰고 있고 2026을 볼 생각이라면, 순서는 이렇게 잡는 게 맞습니다. 이름 충돌 검사는 STYLE2024로, 가시성은 명시적 export/local 표기로 미리 정리할 수 있습니다. 반면 크기 한계는 앞서 본 대로 2026 이전에 켜 볼 방법이 없으니, 스키마에 1500 필드를 넘는 메시지나 1700 값을 넘는 enum이 있는지는 그냥 세어 보는 수밖에 없습니다. 있다면 그게 마이그레이션 비용의 대부분이 될 겁니다 — 파일 단위로 못 끄니까요.
Edition 2026으로 올릴 이유가 없는데 검사만 갖고 싶다면 대체로 방법이 없거나, 있어도 부분적입니다. 이건 에디션 설계상 의도된 결과입니다 — 기본값의 묶음을 연도로 파는 게 에디션이니까요.
반대로 값을 하는 경우는 명확합니다. 스키마가 크고, 여러 언어로 생성하고, "왜 Java 빌드만 깨지지" 같은 일을 겪어 본 조직. 그런 곳에서 protoc가 스키마를 받아준 순간 표준 툴체인에서 컴파일된다는 보장은 꽤 값이 나갑니다. descriptor.h 주석이 내건 목표가 정확히 그것입니다 — "any file that is accepted by the protobuf compiler will be compilable on standard tool chains."
마치며
Edition 2026은 화려한 릴리스가 아닙니다. 문법 추가가 0이고, 와이어 포맷 변경도 0이며, 성능 개선도 0입니다. 대신 기본값 세 개를 조입니다 — 이름 충돌을 금지하고, 심볼을 기본 local로 만들고, 스키마 크기에 처음으로 선을 긋습니다.
그리고 그게 에디션이라는 장치가 원래 하려던 일입니다. proto2/proto3 시절에는 "이 기본값은 틀렸다"는 걸 알아도 바꿀 방법이 없었습니다. 바꾸는 순간 전 세계의 .proto가 깨지니까요. 에디션은 기본값을 연도에 묶어서, 옛 파일은 옛 기본값으로 두고 새 파일부터 나은 기본값을 주는 길을 냈습니다. Edition 2026은 그 기계가 실제로 도는 첫 사례에 가깝습니다 — 한 번에 세 개를, 각각 옵트아웃을 달아서, 기존 파일은 건드리지 않고.
가장 마음에 드는 건 크기 한계 쪽입니다. "여러 엔지니어가 발견한, 완전하지 않고 틀렸을 수도 있는" 위키 문서에 물결표로 적혀 있던 숫자가, 컴파일러가 강제하는 상수 네 개가 됐습니다. 민속지식이 사양이 되는 순간은 대체로 조용하고, 대체로 이렇게 생겼습니다.
다만 오늘 기준으로 이건 아직 RC이고, 발표문은 자기 기능 하나를 빠뜨렸으며, 문서 예제에는 버그가 있습니다. 확인은 descriptor.proto에서 하십시오. 발표문이 아니라.
참고 자료
- Changes Announced on July 13, 2026 — Edition 2026 발표 (원본 마크다운)
- descriptor.proto @ v36.0-rc1 — Edition enum,
enforce_naming_style,default_symbol_visibility,enforce_proto_limits의 기본값 테이블 - descriptor.h @ v36.0-rc1 —
kLimit2026*상수와EnforceProtoLimits주석 - code_generator.h @ v36.0-rc1 —
ProtocMaximumEdition() - Feature Settings for Editions — 기능별 에디션 기본값 표
- Symbol Visibility — STRICT의 정확한 규칙과 카브아웃
- Proto Limits — Edition 2026 이전의 "발견된 한계" 문서
- Protocol Buffers v36.0-rc1 릴리스 (2026-07-09)
- Changes Announced on June 27, 2025 — Edition 2024 발표(트랙 레코드 비교용)
- gRPC와 Protocol Buffers 기초(관련 글)
- 바이너리 직렬화 포맷 비교 — protobuf/thrift/avro/flatbuffers/capnproto(관련 글)