콘텐츠로 이동

v1.9.3

릴리스 날짜: 2025-11-16GitHub 릴리스

go-zero v1.9.3 릴리스를 소개합니다. 이번 릴리스에는 framework의 안정성, 성능, 업계 권장 방식과의 정합성을 높이는 여러 중요한 개선과 버그 수정이 포함되어 있습니다.

  • Consistent Hash 부하 분산: 세션 고정을 위한 새로운 gRPC 부하 분산기를 추가했습니다.
  • gRPC 권장 방식 반영: gRPC 권장 사항에 맞춰 NonBlock 기본값을 변경했습니다.
  • 분산 추적 개선: gateway trace header 전파 문제를 수정했습니다.
  • ORM 개선: pointer 대상에 zero value를 scan할 때의 문제를 수정했습니다.

기여자: @zhoushuguang

zRPC 패키지에 새로운 consistent hash 부하 분산기를 추가했습니다. 이를 통해 gRPC 서비스에서 세션 고정을 사용할 수 있습니다.

주요 기능:

  • 세션 고정을 유지하기 위한 hash 기반 요청 라우팅
  • context의 hash key를 기준으로 요청 분산
  • node 변경 시 request 재분배 최소화
  • go-zero의 기존 core/hash/ConsistentHash 구현 기반

사용 예제:

// Set hash key in context
ctx := zrpc.SetHashKey(ctx, "user_123")
// Requests with the same key will be routed to the same backend
resp, err := client.SomeMethod(ctx, req)

설정:

c := zrpc.RpcClientConf{
Endpoints: []string{"localhost:8080", "localhost:8081"},
BalancerName: "consistent_hash", // Use consistent hash balancer
}

장점:

  • stateful service interaction을 사용할 수 있습니다.
  • backend service의 cache hit rate를 높입니다.
  • session data synchronization overhead를 줄입니다.
  • affinity를 지원하면서도 load distribution을 유지합니다.

Gateway Trace Header 수정 (#5256, #5248)

섹션 제목: “Gateway Trace Header 수정 (#5256, #5248)”

기여자: @kevwan

gateway를 통해 upstream gRPC 서비스로 전달되어야 하는 OpenTelemetry 추적 전파 header가 제대로 전달되지 않아 분산 추적이 끊기던 문제를 수정했습니다.

문제: gateway가 중요한 W3C Trace Context header(traceparent, tracestate, baggage)를 gRPC metadata로 전달하지 않아, gateway 경계에서 trace context가 유실되었습니다.

해결:

  • ProcessHeaders 함수를 개선해 trace propagation header를 전달하도록 했습니다.
  • gRPC metadata 규칙에 맞춰 header를 올바르게 lowercase로 변환합니다.
  • HTTP → gRPC 경계를 넘어 distributed tracing을 유지합니다.

영향:

  • gateway를 통과하는 end-to-end tracing이 올바르게 동작합니다.
  • microservice architecture의 관측 가능성이 향상됩니다.
  • debugging과 performance analysis가 더 쉬워집니다.

기술 세부 사항:

// Trace headers now properly forwarded
var traceHeaders = map[string]bool{
"traceparent": true, // W3C Trace Context
"tracestate": true, // Additional trace state
"baggage": true, // W3C Baggage propagation
}

기여자: @kevwan

문제: 같은 process 안에서 여러 서비스(예: REST + RPC)를 실행할 때 trace agent가 여러 번 초기화될 수 있었습니다. 이로 인해 resource leak이나 예기치 않은 동작이 발생할 수 있었습니다.

해결:

  • sync.Once를 사용해 trace agent가 한 번만 초기화되도록 했습니다.
  • prometheus.StartAgent, logx.SetUp에서 사용하는 방식과 일관성을 맞췄습니다.
  • shutdown에도 sync.OnceFunc를 추가해 cleanup이 중복 실행되지 않도록 했습니다.

코드 변경:

var (
once sync.Once
shutdownOnceFn = sync.OnceFunc(func() {
if tp != nil {
_ = tp.Shutdown(context.Background())
}
})
)
func StartAgent(c Config) {
if c.Disabled {
return
}
once.Do(func() {
if err := startAgent(c); err != nil {
logx.Error(err)
}
})
}

장점:

  • multi-server process에서 resource conflict를 방지합니다.
  • global tracer provider instance가 하나만 생성되도록 보장합니다.
  • concurrent initialization이 더 안전해집니다.
  • shutdown 시 cleanup이 올바르게 수행됩니다.

ORM pointer 대상의 zero value scanning 수정 (#5270)

섹션 제목: “ORM pointer 대상의 zero value scanning 수정 (#5270)”

기여자: @lerity-yao(첫 기여입니다! 🎊)

문제: database 결과를 pointer type의 struct field로 scan할 때 zero value(0, false, 빈 문자열)와 NULL 값을 제대로 구분하지 못했습니다. 그 결과 nil pointer가 잘못 zero value로 설정되는 문제가 있었습니다.

해결: getValueInterface 함수를 개선해 scan 전에 nil pointer를 올바르게 초기화하도록 했습니다. 이를 통해 SQL driver가 zero value와 non-zero value를 정확히 채울 수 있습니다.

코드 변경:

func getValueInterface(value reflect.Value) (any, error) {
if !value.CanAddr() || !value.Addr().CanInterface() {
return nil, ErrNotReadableValue
}
// Initialize nil pointer before scanning
if value.Kind() == reflect.Pointer && value.IsNil() {
baseValueType := mapping.Deref(value.Type())
value.Set(reflect.New(baseValueType))
}
return value.Addr().Interface(), nil
}

영향:

type User struct {
Name string `db:"name"` // Always set
Age *int `db:"age"` // Can distinguish NULL vs 0
Active *bool `db:"active"` // Can distinguish NULL vs false
}
// Before: age=0 and age=NULL both resulted in nil pointer
// After: age=0 → *int(0), age=NULL → nil pointer ✓

장점:

  • NULL과 zero value를 올바르게 구분합니다.
  • optional field의 의미를 더 정확하게 표현합니다.
  • 예기치 않은 nil pointer dereference를 방지합니다.
  • Go의 SQL scanning 권장 방식과 맞아집니다.

🔄 호환성에 영향을 줄 수 있는 변경(하위 호환성 유지)

섹션 제목: “🔄 호환성에 영향을 줄 수 있는 변경(하위 호환성 유지)”

NonBlock 기본값을 true로 변경 (#5259)

섹션 제목: “NonBlock 기본값을 true로 변경 (#5259)”

기여자: @kevwan

동기: blocking dial을 권장하지 않는 gRPC 공식 권장 사항에 맞추기 위한 변경입니다.

변경 사항:

zrpc/config.go
type RpcClientConf struct {
// Before: NonBlock bool `json:",optional"`
// After:
NonBlock bool `json:",default=true"` // Now defaults to true
}

중요한 이유:

  1. Blocking dial은 deprecated된 방식입니다: grpc.WithBlock()은 anti-pattern입니다.
  2. Connection state는 동적입니다: dial 시점에 연결되어 있어도 이후 연결성이 보장되지는 않습니다.
  3. RPC가 대기를 처리합니다: 모든 RPC는 연결되거나 deadline에 도달할 때까지 자동으로 기다립니다.
  4. 코드가 단순해집니다: 호출 전에 “ready” 상태를 확인할 필요가 없습니다.

마이그레이션 가이드:

대부분의 사용자는 별도 조치가 필요 없습니다. 새로운 기본값이 권장 동작입니다.

명시적으로 blocking 동작이 필요한 경우(권장하지 않음):

// Option 1: Configuration
c := zrpc.RpcClientConf{
NonBlock: false, // Explicit blocking
}
// Option 2: Client option (deprecated)
client := zrpc.MustNewClient(c, zrpc.WithBlock())

하위 호환성:

  • NonBlock: false가 들어 있는 기존 설정은 계속 동작합니다.
  • 새로운 WithBlock() 옵션을 사용할 수 있습니다(deprecated 표시).
  • 이미 NonBlock: true를 사용하는 서비스에는 변경이 필요 없습니다.

문서: 자세한 migration guide와 변경 이유는 gRPC non-block 변경 PR을 참고하세요.


go-zero community에 새로 합류한 기여자들을 환영합니다! 🎉

  • @JackGod001 님이 #4343에서 첫 기여를 해 주셨습니다.
  • @stemlaud 님이 #5245에서 첫 기여를 해 주셨습니다.
  • @gfischer7 님이 #5254에서 첫 기여를 해 주셨습니다.
  • @lerity-yao 님이 #5270에서 첫 기여를 해 주셨습니다(ORM 수정).

기여해 주셔서 감사합니다. 앞으로도 프로젝트에서 계속 함께하기를 기대합니다.


Terminal window
go get -u github.com/zeromicro/go-zero@v1.9.3
Terminal window
# Update go.mod
go get -u github.com/zeromicro/go-zero@v1.9.3
go mod tidy


  • gRPC용 consistent hash balancer를 추가했습니다(zrpc/internal/balancer/consistenthash).
  • context 기반 hash key API인 SetHashKey()GetHashKey()를 추가했습니다.
  • replica count와 hash function을 설정할 수 있습니다.
  • 포괄적인 test coverage를 추가했습니다.
  • gateway에서 trace header propagation을 수정했습니다.
  • W3C Trace Context header를 올바르게 처리합니다.
  • gRPC 규칙에 맞춰 header를 대소문자 구분 없이 matching합니다.
  • sync.Once pattern으로 단일 초기화를 보장합니다.
  • pointer field에 zero value를 scan할 때의 문제를 수정했습니다.
  • NULL과 zero value를 올바르게 구분합니다.
  • nil pointer initialization을 포함하도록 getValueInterface()를 개선했습니다.
  • sql.Null* type을 지원합니다.
  • NonBlock 기본값을 true로 변경했습니다.
  • 호환성을 위해 deprecated된 WithBlock() 옵션을 추가했습니다.
  • blocking mode와 non-blocking mode를 명시적으로 처리합니다.
  • client initialization logic을 업데이트했습니다.
  • 모든 변경 사항에 대해 포괄적인 test coverage를 추가했습니다.
  • ORM test에 edge case 처리를 추가했습니다.
  • gateway trace header test case를 추가했습니다.
  • consistent hash balancer benchmark를 추가했습니다.

이번 릴리스를 가능하게 해 준 모든 기여자, issue 제보자, community member에게 감사드립니다. 여러분의 feedback과 contribution 덕분에 go-zero가 계속 좋아지고 있습니다.


문제가 있거나 다음 릴리스를 위한 제안이 있다면 다음 경로로 알려 주세요.

go-zero와 함께 즐겁게 개발하세요! 🚀