20160916

Go 1.7 context 패키지로 정리된 코드

Go 1.7에서 context 패키지가 드디어 표준 라이브러리로 올라왔다. 기존 golang.org/x/net/context 쓰던 코드 갈아치우는중. 이제 net/http, database/sql도 context 받는 함수 시그니처가 표준으로 생김.

package main

import (
    "context"
    "database/sql"
    "net/http"
    "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()

    var name string
    err := db.QueryRowContext(ctx,
        "SELECT name FROM user WHERE id = ?", 42).Scan(&name)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    w.Write([]byte(name))
}

HTTP 요청 컨텍스트에 2초 타임아웃 걸고 DB 쿼리로 내려보냄. 핸들러가 타임아웃되면 DB 쿼리도 자동 취소됨 (드라이버가 지원하는 경우). 이 전파가 자동이라 너무 편함.

패턴:

  1. handler 최상단에서 r.Context()로 시작
  2. 필요시 WithTimeout, WithCancel로 감쌈
  3. 다운스트림 (DB, 외부 API) 호출에 계속 전달
  4. goroutine 띄우면 반드시 ctx.Done() 체크 루프

context.Value에 요청 ID나 로깅용 메타 넣는것도 유용. 단 context.Value에 너무 많이 의존하면 안티패턴. 진짜 "요청 전체에 흐르는 값" 만 담아야 함. 비즈니스 로직 파라미터를 context에 숨기는건 나쁨.

기존 코드 마이그레이션은 간단함:

// before
import "golang.org/x/net/context"
// after
import "context"

x/net/context와 표준 context는 type 호환됨 (전자는 후자의 type alias 수준). 기존 서드파티 라이브러리가 x/net/context 받아도 표준 context 넘기면 그대로 됨.

Go 1.5때 GC 스톱타임 줄인 것도 체감 컸는데, 1.7은 context 표준화가 가장 크다. 이거 없이 프로덕션 서버 못 쓰겠다.