본문으로 건너뛰기
피드

고 1.27 미리보기: 제네릭 메서드, JSON v2, 표준 UUID까지 꽤 알찬 릴리스

backend 약 13분
vote
0
댓글
북마크

고 1.27은 타입 시스템, 런타임, 표준 라이브러리 쪽 변화가 한꺼번에 들어오는 큰 릴리스다. 제네릭 메서드가 드디어 들어오고, JSON v2가 기본 구현으로 올라오며, UUID 패키지와 포스트 양자 서명, 고루틴 누수 프로파일 같은 실무형 기능도 추가된다.

  • 1

    제네릭 메서드가 도입돼 타입에 붙은 메서드도 독립적인 타입 파라미터를 가질 수 있게 됨

  • 2

    작은 메모리 할당은 최대 30% 빨라지고, 실제 할당 많은 프로그램에서는 전체 성능이 약 1% 좋아질 것으로 예상됨

  • 3

    encoding/json/v2가 실험 플래그 없이 제공되고, 기존 encoding/json도 내부적으로 v2 구현을 쓰게 됨

  • 4

    표준 라이브러리에 UUID 패키지가 추가되고, TLS 1.3에는 포스트 양자 서명 ML-DSA가 들어옴

  • 5

    고루틴 라벨 traceback, 고루틴 누수 프로파일, synthetic time 테스트, 인메모리 HTTP 테스트 서버 등 운영과 테스트 편의 기능이 꽤 많음

  • 고 1.27의 메인 이벤트는 제네릭 메서드임. 이제 메서드 선언이 receiver의 타입 파라미터와 별개로 자기 타입 파라미터를 가질 수 있음

    • 예전에는 Box[T] 같은 제네릭 타입에 Map을 붙여도 Box[int]Box[string]으로 바꾸는 메서드를 자연스럽게 만들기 어려웠음
    • 그래서 이런 연산은 타입에 붙은 메서드가 아니라 패키지 레벨 제네릭 함수로 빠지는 경우가 많았는데, 1.27부터는 타입의 API로 더 깔끔하게 표현 가능함
    • 다만 인터페이스에는 아직 타입 파라미터가 붙은 메서드를 선언할 수 없고, 제네릭 메서드로 인터페이스를 만족시키는 것도 안 됨
  • 구조체 리터럴도 조금 더 편해짐. 임베디드 구조체에서 승격된 필드(promoted field)를 바로 키로 쓸 수 있음

    • 예전에는 User{Base: Base{ID: 7}, Name: "Mittens"}처럼 안쪽 구조체 이름까지 써야 했음
    • 이제는 승격된 ID를 구조체 리터럴 키로 직접 넣을 수 있어서, 임베딩을 많이 쓰는 코드에서 잡음이 줄어듦
  • 함수 타입 추론도 더 넓게 적용됨. 제네릭 함수를 특정 함수 타입이 기대되는 자리에 넣을 때 타입 인자를 덜 써도 됨

    • 기존에는 단순 변수 할당에서는 추론이 됐지만, 변환이나 복합 리터럴에서는 직접 타입 인자를 적어야 하는 경우가 있었음
    • 1.27에서는 func([]int) int 같은 함수 타입이 기대되는 슬라이스에 제네릭 헬퍼를 넣는 상황도 더 자연스럽게 처리됨

중요

> 작은 객체 할당은 일부 케이스에서 최대 30% 빨라짐. 전체 프로그램 기준으로는 할당이 많은 워크로드에서 약 1% 정도 이득이 예상되고, 대가로 바이너리가 약 60KB 커짐.

  • 런타임 쪽에서는 크기별 특화 메모리 할당 루틴이 들어감. 코드 수정 없이 그냥 조금 빨라지는 타입의 변화임

    • 대상은 80바이트 미만의 작은 할당 일부고, 워크로드에 따라 개선 폭은 다름
    • 끄고 싶으면 GOEXPERIMENT=nosizespecializedmalloc로 빌드할 수 있지만, 이 우회 옵션은 고 1.28에서 제거될 예정임
  • traceback에 pprof 고루틴 라벨이 붙는 것도 운영 디버깅에서 꽤 유용함

    • go.mod가 고 1.27 이상이면 panic, SIGQUIT, runtime.Stack 출력의 각 고루틴 헤더에 runtime/pprof 라벨이 표시됨
    • 예를 들어 요청 처리 중 pprof.Do{request: 42} 같은 라벨을 붙였다면, 크래시 덤프에서도 그 맥락이 같이 보임
    • 라벨에 민감한 값이 들어갈 수 있으니 GODEBUG=tracebacklabels=0로 끌 수 있고, 이 옵션은 계속 유지될 예정임
  • 고루틴 누수 탐지도 실험 기능에서 정식 pprof 프로파일로 올라옴

    • runtime/pprofgoroutineleak 프로파일을 제공하고, GC를 한 번 돌려 영원히 막힌 고루틴을 찾아 스택을 보고함
    • 대표적인 누수는 자기만 들고 있는 채널에 send하다가 영원히 block되는 케이스임
    • 실제 서비스에서는 stdout에 찍기보다 /debug/pprof/goroutineleak 엔드포인트를 수집하는 방식이 자연스러움
  • 보안 쪽에서는 crypto/mldsa가 추가됨. FIPS 204의 포스트 양자 디지털 서명 방식인 ML-DSA 구현임

    • 파라미터 세트는 MLDSA44, MLDSA65, MLDSA87 세 가지이고, 보안 수준과 키/서명 크기 사이에서 트레이드오프가 있음
    • crypto/x509의 키와 서명, crypto/tls의 TLS 1.3 서명 스킴에도 연결됨
  • 드디어 표준 라이브러리에 UUID 패키지가 들어옴. 이건 꽤 오래 기다린 사람 많을 듯

    • uuid 패키지는 RFC 9562 기준으로 UUID 생성과 파싱을 제공하고, 난수는 암호학적으로 안전한 소스를 씀
    • uuid.New()는 일반적인 용도에 맞는 알고리즘을 고르고, uuid.NewV4()는 순수 랜덤, uuid.NewV7()은 생성 시간순 정렬에 유리한 형태를 만듦
    • 랜덤 구성 요소를 가진 UUID는 비교 가능해서 ==로 직접 비교할 수 있음
  • encoding/json/v2가 실험 딱지를 떼고 기본 제공됨. 조용하지만 영향은 큰 변화임

    • 고 1.25부터 GOEXPERIMENT=jsonv2로만 쓰던 encoding/json/v2와 저수준 동반 패키지 encoding/json/jsontext를 그냥 import할 수 있음
    • 더 큰 변화는 기존 encoding/json v1 패키지가 내부적으로 v2 구현 위에서 동작한다는 점임
    • 호환성은 최대한 유지되며, 일부 에러 메시지 텍스트만 달라질 수 있음
    • 문제가 생기면 GOEXPERIMENT=nojsonv2로 기존 v1 구현을 되돌릴 수 있음

💡

> JSON v2는 map 키를 기본 정렬하지 않음. 골든 테스트나 서명 입력처럼 안정적인 출력이 필요하면 json.Deterministic 옵션을 명시해야 함.

  • 실험 기능으로 portable SIMD 패키지도 들어옴. 하드웨어 벡터 명령이 있으면 쓰고, 없으면 순수 고 에뮬레이션으로 fallback하는 방식임

    • GOEXPERIMENT=simd를 켜야 사용할 수 있음
    • Float32s 같은 타입의 lane 수가 고정돼 있지 않고, 머신에 따라 4개일 수도 16개일 수도 있음
    • 표준 라이브러리 내부에서는 이미 Swiss Table 맵 구현의 해시 함수 쪽에 SIMD 기반 인트린식을 쓰기 시작함
  • 소소하지만 바로 쓰기 좋은 표준 라이브러리 개선도 많음

    • strings.CutLastbytes.CutLast가 추가돼 마지막 구분자 기준 split을 LastIndex 없이 처리할 수 있음
    • hash/maphash에는 Hasher[T] 인터페이스가 추가돼 해시 테이블이나 Bloom filter 같은 자료구조가 타입별 해시/동등성 규칙을 받을 수 있음
    • math/big.Int.DivideTrunc, Floor, Round, Ceil 같은 명시적 반올림 모드로 몫과 나머지를 같이 계산함
    • math/rand/v2(*Rand).N은 직접 만든 난수 소스에서 정수나 duration 타입의 bounded random 값을 뽑을 수 있게 해줌
  • 테스트 쪽 변화는 동시성 코드 다루는 팀에게 꽤 반가움

    • testing/synctest.Sleep은 synthetic clock을 전진시키고 고루틴이 안정될 때까지 기다리는 작업을 한 번에 처리함
    • fake clock 안에서는 2초 sleep도 실제 시간 없이 즉시 지나가므로, 동시성 테스트가 훨씬 결정적으로 돌아감
    • httptest.NewTestServer는 실제 TCP listener 없이 인메모리 fake network 위에서 서버를 만들고, t.Cleanup도 자동 등록함
    • HTTP round-trip이 프로세스 내부 pipe로만 지나가서 포트 충돌이나 실제 네트워크 stack 변수에 덜 흔들림
  • 호환성에서 조심할 만한 변화도 있음. 특히 timer channel과 HTTP body close는 기존 가정이 있으면 확인 필요함

    • time.After, time.NewTimer, time.NewTicker 등이 반환하는 채널은 이제 항상 unbuffered이고, 예전 buffered 동작을 되살리던 asynctimerchan GODEBUG는 제거됨
    • HTTP/1 응답 body를 닫으면 unread content를 제한 내에서 읽고 버려 connection reuse가 가능해짐
    • 큰 다운로드를 early close로 중단하는 패턴에 기대고 있었다면 Transport.DisableKeepAlives 같은 설정을 봐야 함
    • HTTP/2 서버는 RFC 9218 client priority를 반영하고, 예전 round-robin에 가깝게 돌리고 싶으면 Server.DisableClientPriority = true를 쓸 수 있음
  • 툴체인도 은근히 실무형으로 다듬어짐

    • go teststdversion vet check를 기본으로 돌려서 go.mod에 적힌 고 버전보다 새 표준 라이브러리 심볼을 쓰는 실수를 잡아줌
    • go doc pkg@version으로 특정 모듈 버전의 문서를 볼 수 있음
    • go doc -ex는 runnable example 목록을 보여줌
    • go mod tidy는 고 1.27 이상 모듈에서 require block을 direct와 indirect 두 덩어리로 정리하면서 주석은 보존함
    • compile, link, asm, cgo, cover, pack 도구가 GCC 형식의 response file을 지원해서 긴 명령줄에 걸리는 빌드 시스템에 도움이 됨
  • 릴리스 노트 밖의 숨은 변화도 꽤 큼. 고 1.26에서 1.27 사이에 약 1,600개 커밋이 들어갔음

    • net/http의 HTTP/2 지원은 오랫동안 h2_bundle.go라는 12,226줄짜리 생성 파일에 묶여 있었는데, 이제 net/http/internal/http2라는 실제 패키지로 정리됨
    • HTTP/3는 아직 공개 API는 없지만, net/http 내부에 pluggable hook이 들어가고 테스트 스위트 상당 부분이 HTTP/3에서도 돌도록 준비됨
    • 컴파일러에는 known bits dataflow, loop-invariant code motion, switch lookup table 최적화가 기본으로 켜짐
    • 타입 메타데이터는 linker에서 .go.type 섹션으로 재정리됐고, reflect.typelinks 같은 내부 심볼에 //go:linkname로 기대던 라이브러리는 테스트가 필요함
    • 허가되지 않은 //go:linkname 사용도 더 빡세져서, 표준 라이브러리가 승인하지 않은 내부 연결에 기대는 코드는 1.27에서 빨리 검증해봐야 함

기술 맥락

  • 고 1.27의 핵심 선택은 ‘언어를 크게 흔들기보다 라이브러리와 런타임의 마찰을 줄이는 쪽’이에요. 제네릭 메서드는 타입에 속해야 자연스러운 API를 다시 타입 쪽으로 가져오는 변화라서, 라이브러리 설계에서 패키지 함수가 불필요하게 늘어나는 문제를 줄여줘요.

  • JSON v2를 기존 encoding/json 밑으로 넣은 것도 흥미로운 결정이에요. 새 API만 따로 두면 생태계 전환이 느려지거든요. 그래서 겉보기 동작은 유지하고 내부 구현을 바꾸되, 문제가 생기면 GOEXPERIMENT=nojsonv2로 되돌릴 수 있게 한 거예요.

  • 런타임 성능 개선은 ‘개발자가 코드를 바꾸지 않아도 되는 최적화’에 가깝습니다. 80바이트 미만 작은 할당을 크기별 루틴으로 보내면 allocation-heavy 서비스에서 전체 1% 정도라도 얻을 수 있는데, 서버 비용이 큰 조직에서는 이런 1%가 생각보다 큽니다.

  • 테스트 기능 개선은 고가 동시성 코드를 언어의 정체성처럼 밀어온 것과 맞닿아 있어요. synthetic time과 인메모리 HTTP 서버는 flaky test를 줄이는 데 직접적이라서, 타이머와 goroutine, HTTP handler가 섞인 코드를 운영하는 팀일수록 체감이 커요.

고는 여전히 ‘천천히 바뀌는 언어’처럼 보이지만, 1.27은 개발자가 매일 만지는 코드 구조, 테스트, 디버깅, JSON 처리까지 꽤 넓게 건드린다. 특히 제네릭 메서드와 JSON v2는 라이브러리 작성자 입장에서 바로 체감될 변화다.

댓글

댓글

댓글을 불러오는 중...

backend

SQLite를 프로덕션에서 쓰려면 WAL, 락, VFS를 제대로 알아야 한다

이 글은 SQLite를 단순한 로컬 임베디드 DB가 아니라 저지연 서버용 데이터베이스로 쓰는 방법을 정리한다. WAL 모드, busy timeout, BEGIN IMMEDIATE, cache_size, mmap_size, VFS 기반 복제까지 실전 설정이 꽤 구체적으로 나온다. 다만 분산 쓰기나 테라바이트급 데이터셋에는 여전히 PostgreSQL 같은 서버형 DB가 맞다는 선도 분명히 긋는다.

backend

데카트론 독일, 웹 결제에 유럽식 계좌이체 ‘웨로’ 붙였다

데카트론 독일이 자사 웹사이트 결제 수단에 유럽 결제 이니셔티브의 웨로를 도입했다. 카드 네트워크를 거치지 않는 실시간 계좌 간 결제로 수수료를 낮추고, 멤버십과 결제를 묶어 고객 유지까지 노리는 움직임이다.

backend

SMTP를 HTTP 위에서 다시 만든다면? 기존 부품만 조립한 현대식 이메일 실험

글쓴이는 SMTP를 대체하려는 게 아니라, HTTP·WebFinger·Ed25519·HPKE·JMAP 같은 이미 검증된 기술로 이메일의 문제를 다시 설계해보는 HMTP 실험을 소개해. 핵심은 주소 형태만 user@domain으로 유지하고, 전송·발견·서명·암호화·스팸 방어·읽기 모델을 전부 현대 웹 인프라 위에 얹는 거야.

backend

버셀 실험실의 Scriptc, TypeScript를 Node 없이 네이티브 바이너리로 컴파일

Vercel Labs가 공개한 Scriptc는 일반 TypeScript 코드를 Node, V8, JavaScript 엔진 없이 작은 네이티브 실행 파일로 컴파일하는 실험적 컴파일러다. 정적으로 컴파일 가능한 코드와 동적 실행이 필요한 코드를 명확히 나누고, Node와 결과가 바이트 단위로 맞는지 800개 이상 테스트로 검증한다.

backend

고의 새 가비지 컬렉터 ‘그린 티’, 힙을 훑는 방식이 어떻게 달라졌나

고 1.25에서 도입되고 1.26에서 기본값이 된 새 가비지 컬렉터 ‘그린 티’를 실제 힙 배치와 성능 측정으로 뜯어본 글이다. 핵심은 포인터를 무작정 따라가는 대신 메모리 span 단위로 훑어 캐시 친화성을 높였다는 점이고, 동시에 고의 비이동식 컬렉터가 여전히 sparse page 회수에 약하다는 한계도 같이 보여준다.