๐ŸŒ˜๋‹น์‹ ๋„ Go์˜ context๋ฅผ ์ž˜๋ชป ์“ฐ๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

sangjinsuยท2025๋…„ 6์›” 6์ผ

Many Developers Donโ€™t Understand the Use of Context in Golang. Hereโ€™s What You Need to Know.

์ •๋ง๋กœ Go์—์„œ์˜ context๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ๊ณ„์‹ ๊ฐ€์š”?

์ด ๊ธ€์—์„œ๋Š” context์˜ ํ•ต์‹ฌ ๊ฐœ๋…์„ ์‹ค์ „ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ๋ช…ํ™•ํžˆ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

ํŠนํžˆ ์š”์ฒญ ID ๋ฏธ๋“ค์›จ์–ด(Request ID Middleware)๋ฅผ ํ™œ์šฉํ•œ ํŠธ๋ ˆ์ด์‹ฑ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด, context๊ฐ€ ์‹ค์ œ ์„œ๋น„์Šค์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ ๋ณด์—ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

Go์˜ context ํŒจํ‚ค์ง€๋Š” API์™€ ๊ณ ๋ฃจํ‹ด ์ „๋ฐ˜์— ๊ฑธ์ณ ์ทจ์†Œ, ๋ฐ๋“œ๋ผ์ธ, ์š”์ฒญ ๋‹จ์œ„์˜ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ทธ ์ •ํ™•ํ•œ ์‚ฌ์šฉ๋ฒ•์ด๋‚˜, ํŠนํžˆ ์š”์ฒญ ํŠธ๋ ˆ์ด์‹ฑ๊ณผ ๊ฒฐํ•ฉํ–ˆ์„ ๋•Œ์˜ ์ง„์ •ํ•œ ์žฅ์ ์„ ์ž˜ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ๋‹ค์Œ ๋‚ด์šฉ์„ ์ค‘์‹ฌ์œผ๋กœ context.Context์˜ ๋ณธ์งˆ์„ ํŒŒํ—ค์ณ๋ด…๋‹ˆ๋‹ค:

  • context์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ๊ณผ ์‹ค์ „ ํ™œ์šฉ๋ฒ•
  • ์š”์ฒญ ID(Request ID) ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•œ ์‹ค์ œ API ์˜ˆ์ œ 2๊ฐ€์ง€
  • ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ Request ID๊ฐ€ ์™œ ์ค‘์š”ํ•œ์ง€, ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ํ†ตํ•ด ํŠธ๋ ˆ์ด์‹ฑ์ด ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ๋˜๋Š”์ง€

๐Ÿ“Œcontext.Context ๋ž€ ๋ฌด์—‡์ด๋ฉฐ ์™œ ์ค‘์š”ํ• ๊นŒ์š”?

Go์—์„œ context.Context๋Š” ์š”์ฒญ์ด๋‚˜ ์ž‘์—…์˜ ์ƒ์• ์ฃผ๊ธฐ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋ฐ๋“œ๋ผ์ธ, ์ทจ์†Œ ์‹ ํ˜ธ, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(ํ‚ค-๊ฐ’ ์Œ)๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.

์ด ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค:

  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ์„ ๋Š๊ฑฐ๋‚˜ ๋ฐ๋“œ๋ผ์ธ์ด ์ดˆ๊ณผ๋˜์—ˆ์„ ๋•Œ, ์ž‘์—…์„ ์กฐ๊ธฐ์— ์ทจ์†Œํ•  ์ˆ˜ ์žˆ์Œ
  • ์ธ์ฆ ํ† ํฐ, ํŠธ๋ ˆ์ด์‹ฑ ID ๋“ฑ๊ณผ ๊ฐ™์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ API ๊ณ„์ธต ์ „๋ฐ˜์— ์ „ํŒŒํ•  ์ˆ˜ ์žˆ์Œ
  • ๊ณ ๋ฃจํ‹ด์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์‚ด์•„์žˆ๋Š” ์ƒํ™ฉ์„ ๋ง‰๊ณ , ๋ฆฌ์†Œ์Šค ๋ˆ„์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Œ

context ์—†์ด ์ด์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด

์ทจ์†Œ ๋กœ์ง์ด ์ค‘๋ณต๋˜๊ณ  ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉฐ, ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„๋„ ์‰ฝ๊ฒŒ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

context๋Š” ์ด๋ฅผ ๊น”๋”ํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” Go์˜ ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.


๐Ÿงฉ ํŠธ๋ ˆ์ด์‹ฑ์„ ์œ„ํ•œ Request ID ๋ฏธ๋“ค์›จ์–ด ๊ตฌ์„ฑ

Request ID๋Š” ๊ฐ ์š”์ฒญ์— ๊ณ ์œ ํ•˜๊ฒŒ ๋ถ€์—ฌ๋˜๋Š” ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ(distributed tracing)์˜ ํ•ต์‹ฌ ์š”์†Œ๋กœ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค:

  • ํ•˜๋‚˜์˜ ์š”์ฒญ๊ณผ ๊ด€๋ จ๋œ ๋กœ๊ทธ์™€ ๋ฉ”ํŠธ๋ฆญ์„ ์—ฐ๊ฒฐํ•˜์—ฌ ์ถ”์  ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ
  • ๋‹ค์ˆ˜์˜ ์„œ๋น„์Šค์— ๊ฑธ์นœ ๋ณต์žกํ•œ ํ๋ฆ„์„ ๋””๋ฒ„๊น…ํ•  ๋•Œ ํ•ต์‹ฌ ๋‹จ์„œ ์ œ๊ณต
  • ์ „์ฒด ์‹œ์Šคํ…œ์—์„œ ์š”์ฒญ ์ง€์—ฐ ์‹œ๊ฐ„(latency)๊ณผ ์—๋Ÿฌ์œจ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๋ฐ ํ™œ์šฉ๋จ

๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€, Request ID๋ฅผ ๋ฏธ๋“ค์›จ์–ด์—์„œ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ƒ์†๋ฐ›์•„ context์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ด๋‹น ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ „ ์˜์—ญ์—์„œ Request ID๋ฅผ ์†์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ”ง ์˜ˆ์ œ API: Gorilla Mux, Context, UUID ๊ธฐ๋ฐ˜ Request ID ๋ฏธ๋“ค์›จ์–ด

โœ… ์‹œ๋‚˜๋ฆฌ์˜ค

๋‹ค์Œ์€ Request ID ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์ ์šฉ๋œ ์‹ค์ œ API ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค:

  • ํฌํŠธ 8081์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ฉ”์ธ ์„œ๋น„์Šค
  • ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด UUID ๊ธฐ๋ฐ˜ Request ID ๋ถ€์—ฌ
  • ์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹œ context.WithTimeout์„ ํ™œ์šฉํ•˜์—ฌ ํƒ€์ž„์•„์›ƒ ์ฒ˜๋ฆฌ

๐Ÿงฑ ์ฃผ์š” ๊ตฌ์กฐ

// Request ID๋ฅผ context์— ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ํ‚ค ์ •์˜
type key string
const requestIDKey key = "requestID"
  • context.Context์— ๊ฐ’์„ ์ €์žฅํ•  ๋•Œ๋Š” ๊ณ ์œ ํ•œ ํƒ€์ž…์˜ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฌธ์ž์—ด ๋Œ€์‹  ๋ณ„๋„์˜ ํƒ€์ž… key๋ฅผ ์„ ์–ธํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿงฉ Request ID ๋ฏธ๋“ค์›จ์–ด ๊ตฌํ˜„

func CustomMiddleware() mux.MiddlewareFunc {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            reqID := r.Header.Get("X-Request-ID")
            if reqID == "" {
                reqID = uuid.New().String() // ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ
            }
            ctx := context.WithValue(r.Context(), requestIDKey, reqID)
            w.Header().Set("X-Request-ID", reqID) // ์‘๋‹ต์—๋„ ํ—ค๋” ์„ธํŒ…
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

๐Ÿ’ก Request ID๋Š”
์ง์ ‘ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ƒ์œ„ ์š”์ฒญ์—์„œ ๋ฐ›์€ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ

์ถ”์ ์„ ์œ„ํ•ด ํด๋ผ์ด์–ธํŠธ โ†’ ์„œ๋ฒ„ โ†’ ํ•˜์œ„ ์„œ๋น„์Šค๊นŒ์ง€ ์ด์–ด์ ธ์•ผ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.


๐Ÿš€ ๋ฉ”์ธ ํ•จ์ˆ˜: Router ๊ตฌ์„ฑ ๋ฐ ์„œ๋ฒ„ ์‹คํ–‰

func main() {
    r := mux.NewRouter()
    r.Use(CustomMiddleware())
    r.HandleFunc("/request", handlerRequest).Methods("GET")
    log.Println("Server running at http://localhost:8081")
    log.Fatal(http.ListenAndServe(":8081", r))
}
  • mux.Router๋ฅผ ์‚ฌ์šฉํ•ด ๋ฏธ๋“ค์›จ์–ด์™€ ๋ผ์šฐํ„ฐ๋ฅผ ์„ค์ •
  • /request ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด handlerRequest ์‹คํ–‰

๐Ÿ” ํ•ต์‹ฌ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜ (/request)

func handlerRequest(w http.ResponseWriter, r *http.Request) {
    reqID, ok := r.Context().Value(requestIDKey).(string)
    if !ok {
        http.Error(w, "Request ID not found in context", http.StatusInternalServerError)
        return
    }

    err := externalAPIHandler(r.Context())
    if err != nil {
        log.Printf("request id: %v error message: %v", reqID, err.Error())
        w.WriteHeader(http.StatusInternalServerError)
        json.NewEncoder(w).Encode(requestIDResponse{Message: "internal server error"})
        return
    }

    json.NewEncoder(w).Encode(requestIDResponse{Message: "success"})
}

๐Ÿ”ง context์—์„œ Request ID๋ฅผ ๊บผ๋‚ด ๋กœ๊น…์— ํ™œ์šฉํ•˜๊ณ , ์™ธ๋ถ€ API ํ˜ธ์ถœ์— ๊ฐ™์€ context๋ฅผ ๋„˜๊น๋‹ˆ๋‹ค.


๐ŸŒ ์™ธ๋ถ€ API ํ˜ธ์ถœ ์ฒ˜๋ฆฌ (externalAPIHandler)

func externalAPIHandler(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8082/external/user", nil)
    if err != nil {
        return err
    }

    client := http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    _, err = io.ReadAll(resp.Body)
    return err
}
  • context.WithTimeout์„ ํ™œ์šฉํ•ด 5์ดˆ ์ด์ƒ ์ง€์—ฐ๋˜๋ฉด ์š”์ฒญ์„ ์ž๋™ ์ทจ์†Œํ•˜๋„๋ก ์„ค๊ณ„
  • ์™ธ๋ถ€ ํ˜ธ์ถœ์—๋„ ๊ฐ™์€ context๋ฅผ ๋„˜๊ฒจ์„œ ์ƒ์œ„ ์š”์ฒญ ์ทจ์†Œ ์‹œ ํ•˜์œ„ ์ž‘์—…๋„ ํ•จ๊ป˜ ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค

๐Ÿง  ํ•ต์‹ฌ ํฌ์ธํŠธ ์š”์•ฝ

ํ•ญ๋ชฉ์„ค๋ช…
โœ… Request ID ์ƒ์„ฑUUID ๊ธฐ๋ฐ˜ ์ƒ์„ฑ or ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ
โœ… Context ์ €์žฅcontext.WithValue๋กœ Request ID๋ฅผ ์ „ํŒŒ
โœ… Context ํ™œ์šฉ์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹œ ํƒ€์ž„์•„์›ƒ ๋ฐ ์ทจ์†Œ ์‹ ํ˜ธ ์ „๋‹ฌ
โœ… ์ถ”์  ํŽธ์˜์„ฑ๋กœ๊ทธ๋งˆ๋‹ค Request ID๋ฅผ ๋‚จ๊ฒจ ๋””๋ฒ„๊น… ์šฉ์ด

๐Ÿ›ฐ๏ธ API 2: ์™ธ๋ถ€ ์„œ๋น„์Šค ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (์ง€์—ฐ ์‘๋‹ต)

์ด ์„œ๋น„์Šค๋Š” ํฌํŠธ 8082์—์„œ ๋™์ž‘ํ•˜๋ฉฐ, /external/user ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด 30์ดˆ๊ฐ„ ์ฒ˜๋ฆฌ ์ง€์—ฐ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค.

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
    "github.com/gorilla/mux"
)

type jsonResponse struct {
    Message string `json:"message"`
}

func handlerUser(w http.ResponseWriter, r *http.Request) {
    time.Sleep(30 * time.Second) // 30์ดˆ๊ฐ„ ์ž‘์—… ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(jsonResponse{
        Message: "Process completed successfully",
    })
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/external/user", handlerUser)
    fmt.Println("Server running on http://localhost:8082")
    http.ListenAndServe(":8082", r)
}

๐ŸŽฏ ์ด API์˜ ๋ชฉ์ 

  • ์žฅ์‹œ๊ฐ„ ์ฒ˜๋ฆฌ๋˜๋Š” ์™ธ๋ถ€ ์ž‘์—… ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„
  • ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ML ์—ฐ์‚ฐ, ์™ธ๋ถ€ ์„œ๋น„์Šค ํ˜ธ์ถœ, DB ์ •ํ•ฉ์„ฑ ๊ฒ€์‚ฌ ๋“ฑ๊ณผ ์œ ์‚ฌํ•œ ํ˜•ํƒœ๋กœ ๋ฐœ์ƒ
  • API 1(Main Service)์—์„œ ์ด API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  context.WithTimeout()์„ ํ†ตํ•ด 5์ดˆ ๋‚ด ์‘๋‹ต์ด ์—†์œผ๋ฉด ์ž๋™ ์ทจ์†Œํ•˜๋„๋ก ์„ค์ •ํ•จ

๐Ÿ”„ ์ „์ฒด ํ๋ฆ„ ์š”์•ฝ (Mermaid ๋‹ค์ด์–ด๊ทธ๋žจ)

๐Ÿ“Œย 30์ดˆ ๋Œ€๊ธฐํ•˜๋Š” ์™ธ๋ถ€ API์™€ 5์ดˆ ํƒ€์ž„์•„์›ƒ ์„ค์ •์˜ ์กฐํ•ฉ

context.WithTimeout()์ด ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„๋ฅผ ๋ง‰๊ณ , ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ 


๐Ÿงช ์‹ค์ „์—์„œ ์ด๋Ÿฐ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๋ฉด?

  • ์™ธ๋ถ€ API๊ฐ€ ๋А๋ฆด ๊ฒฝ์šฐ ์ „์ฒด ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์ง€์—ฐ๋˜๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ค‘๋‹จํ•ด๋„ ์„œ๋ฒ„๋Š” ๊ณ„์† ๋Œ€๊ธฐํ•  ์ˆ˜ ์žˆ์Œ
  • ์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ context์˜ ์ทจ์†Œ ์‹œ๊ทธ๋„์ด ์—†๋‹ค๋ฉด, ๊ณ ๋ฃจํ‹ด๊ณผ ์—ฐ๊ฒฐ์ด ์‚ด์•„๋‚จ์•„ ๋ฆฌ์†Œ์Šค ๋ˆ„์ˆ˜ ๋ฐœ์ƒ

๐Ÿงช ์‹คํ—˜: ์‹ค์ œ๋กœ ์ด ์„œ๋น„์Šค๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์–ด๋–ค ์ผ์ด ๋ฒŒ์–ด์งˆ๊นŒ?

๐ŸŽฏ ์ผ€์ด์Šค 1: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ธ ๋’ค ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒฝ์šฐ

curl -ik http://localhost:8081/request
  • ๋ฏธ๋“ค์›จ์–ด๊ฐ€ UUID ๊ธฐ๋ฐ˜์˜ Request ID๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ context ๋ฐ ์‘๋‹ต ํ—ค๋”์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฉ”์ธ ํ•ธ๋“ค๋Ÿฌ๋Š” /external/user API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ, context.WithTimeout()์œผ๋กœ 5์ดˆ ์ œํ•œ์„ ๋‘ก๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ์™ธ๋ถ€ API๋Š” 30์ดˆ ๋™์•ˆ ์‘๋‹ต์„ ์ง€์—ฐํ•˜๋ฏ€๋กœ, 5์ดˆ๊ฐ€ ์ง€๋‚˜๋ฉด context ํƒ€์ž„์•„์›ƒ์ด ๋ฐœ์ƒํ•˜๊ณ  ์š”์ฒญ์€ ์‹คํŒจ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ JSON ์—๋Ÿฌ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค:
HTTP/1.1 500 Internal Server Error
X-Request-ID: 6a1b1a54-6e7c-4ae0-8a2c-a7e376a73f28
Content-Type: application/json

{
  "message": "internal server error"
}

๐Ÿ“„ ๋กœ๊ทธ ์ถœ๋ ฅ ์˜ˆ์‹œ:

request id: 6a1b1a54-6e7c-4ae0-8a2c-a7e376a73f28 error message: context deadline exceeded

โœ… ์ด์ฒ˜๋Ÿผ Request ID๊ฐ€ ์žˆ๋‹ค๋ฉด,

๋ถ„์‚ฐ๋œ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์˜ ๋กœ๊ทธ๋ฅผ ํ•˜๋‚˜์˜ ํ๋ฆ„์œผ๋กœ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


โ›” ์ผ€์ด์Šค 2: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ ์ค‘๊ฐ„์— ์ง์ ‘ ์ทจ์†Œํ•˜๋Š” ๊ฒฝ์šฐ

curl -ik http://localhost:8081/request
# ์š”์ฒญ ํ›„ 5์ดˆ๊ฐ€ ๋˜๊ธฐ ์ „์— Ctrl + C๋กœ ์ค‘๋‹จ
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ์š”์ฒญ์„ ์ทจ์†Œ(Ctrl + C)ํ•˜๋ฉด, Go ์„œ๋ฒ„๋Š” context๋ฅผ ํ†ตํ•ด ์ทจ์†Œ ์‹ ํ˜ธ๋ฅผ ์ „ํŒŒ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • ์ด ์‹ ํ˜ธ๋Š” ์™ธ๋ถ€ API ํ˜ธ์ถœ์—๋„ ์ „๋‹ฌ๋˜์–ด, API ์š”์ฒญ ์ž์ฒด๊ฐ€ ์ค‘๋‹จ๋˜๊ณ  ๋ฆฌ์†Œ์Šค๋ฅผ ๋‚ญ๋น„ํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋Š” ์•„๋ฌด๋Ÿฐ ์‘๋‹ต์„ ๋ฐ›์ง€ ์•Š์ง€๋งŒ, ์„œ๋ฒ„์—์„œ๋Š” ์š”์ฒญ์„ ๊น”๋”ํžˆ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“„ ๋กœ๊ทธ ์ถœ๋ ฅ ์˜ˆ์‹œ:

request id: d3c9e401-b1aa-4410-8b65-b2eabffccf1e error message: context canceled

โš ๏ธ context deadline exceeded ์™€ context canceled์€ ์˜๋ฏธ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์ „์ž๋Š” ์‹œ์Šคํ…œ์ด ํƒ€์ž„์•„์›ƒ๋œ ๊ฒฝ์šฐ, ํ›„์ž๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ทจ์†Œํ•œ ๊ฒฝ์šฐ ๋”ฐ๋ผ์„œ ์„œ๋น„์Šค์—์„œ๋Š” ์ด ์ฐจ์ด๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ” ์™œ Request ID๊ฐ€ ํŠธ๋ ˆ์ด์‹ฑ์— ์ค‘์š”ํ•œ๊ฐ€?

ํ˜„๋Œ€์˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์‹œ์Šคํ…œ์—์„œ๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ์ด ์—ฌ๋Ÿฌ ๊ณ„์ธต๊ณผ ์„œ๋น„์Šค๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ Request ID๊ฐ€ ์—†๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค:

  • ๋กœ๊ทธ์™€ ๋ฉ”ํŠธ๋ฆญ์ด ์„œ๋กœ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•„ ํ๋ฆ„ ์ถ”์ ์ด ์–ด๋ ค์›€
  • ๋ณต์žกํ•œ ์žฅ์•  ์›์ธ ๋ถ„์„์ด๋‚˜ ์ง€์—ฐ ๋ถ„์„์ด ์‚ฌ์‹ค์ƒ ๋ถˆ๊ฐ€๋Šฅ
  • ์‚ฌ์šฉ์ž ์š”์ฒญ์— ๋Œ€ํ•œ ์ „์ฒด ํ๋ฆ„์„ ์‹ ๋ขฐ์„ฑ ์žˆ๊ฒŒ ๊ด€์ฐฐ(Observability) ํ•  ์ˆ˜ ์—†์Œ

๐Ÿ’ก ๋ฏธ๋“ค์›จ์–ด์—์„œ Request ID๋ฅผ ์ƒ์„ฑ/์ „ํŒŒํ•˜๊ณ  ์ด๋ฅผ context.Context์— ์ €์žฅํ•จ์œผ๋กœ์จ, ์„œ๋น„์Šค ์ „๋ฐ˜์— ๊ฑธ์ณ ์ผ๊ด€๋œ ํŠธ๋ ˆ์ด์‹ฑ ๊ธฐ๋ฐ˜์„ ๋งˆ๋ จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


โœ… ์ •๋ฆฌํ•˜๋ฉฐ: ์‹ค์ „์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ํŒจํ„ด๋“ค

์ด ๊ธ€์—์„œ ๋‹ค๋ฃฌ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ๋‚ด์šฉ์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค:

  • context.WithTimeout()์„ ํ™œ์šฉํ•ด ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒํ•˜๋Š” ๋ฒ•
  • UUID ๊ธฐ๋ฐ˜ Request ID๋ฅผ ๋ฏธ๋“ค์›จ์–ด์—์„œ ์ƒ์„ฑํ•˜๊ณ  ์ „ํŒŒํ•˜๋Š” ๊ตฌ์กฐ
  • ํด๋ผ์ด์–ธํŠธ์˜ ์ทจ์†Œ(Ctrl + C)๋ฅผ ์„œ๋ฒ„๊นŒ์ง€ ์ „๋‹ฌํ•˜๊ณ  ๋ฆฌ์†Œ์Šค๋ฅผ ์•„๋ผ๋Š” ์ฒ˜๋ฆฌ ๋ฐฉ์‹

์ด๋Ÿฌํ•œ ํŒจํ„ด์„ ์ตํžˆ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์ ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค:

  • ๐Ÿ“ฆ ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ
  • ๐Ÿ” ์„œ๋น„์Šค ๊ฐ€์‹œ์„ฑ(Observability) ํ™•๋ณด
  • ๐Ÿงฉ ์—๋Ÿฌ ๋””๋ฒ„๊น… ๋ฐ ํŠธ๋ ˆ์ด์‹ฑ์˜ ์ผ๊ด€์„ฑ

๐Ÿ’ฌ ๋งˆ๋ฌด๋ฆฌ

์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ context์™€ Request ID๋ฅผ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์€

ํ”„๋กœ๋•์…˜๊ธ‰ Go ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“œ๋Š” ํ•ต์‹ฌ ์—ญ๋Ÿ‰ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.


๐Ÿ”Žย ์ถ”๊ฐ€ ์ธ์‚ฌ์ดํŠธ

โœ… 1. ์ปจํ…์ŠคํŠธ ์ทจ์†Œ ๊ฐ์ง€ ๋ฐ ์ •๋ฆฌ ์ฝ”๋“œ ๊ตฌํ˜„

์™œ ์ค‘์š”ํ•œ๊ฐ€?

context๋Š” ๋‹จ์ˆœํžˆ ํƒ€์ž„์•„์›ƒ/์ทจ์†Œ ์‹œ๊ทธ๋„๋งŒ ์ „๋‹ฌํ•˜๋Š” ๋„๊ตฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

์ง„์งœ ์ค‘์š”ํ•œ ๊ฑด, ๊ณ ๋ฃจํ‹ด ๋˜๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์ ๊ทน์ ์œผ๋กœ ์ •๋ฆฌ(cleanup) ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

select {
case <-ctx.Done():
    log.Println("context canceled:", ctx.Err())
    // ์˜ˆ: ์—ฐ๊ฒฐ ์ข…๋ฃŒ, ์ฑ„๋„ ๋‹ซ๊ธฐ, ์บ์‹œ ๋กค๋ฐฑ ๋“ฑ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ
    return ctx.Err()
case result := <-someProcess:
    return result
}

๐ŸŽฏย ์ •๋ฆฌ ๋กœ์ง์ด ์—†๋‹ค๋ฉด context๋Š” ๋ฌด์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.


โœ… 2. Request ID๋ฅผ ๋กœ๊ทธ์— ์ž๋™ ํฌํ•จํ•˜๋Š” Logger Wrapping

์™œ ์ค‘์š”ํ•œ๊ฐ€?

ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ ๋งค๋ฒˆ ctx.Value()๋กœ Request ID๋ฅผ ๊บผ๋‚ด๋Š” ๊ฑด ๋ฒˆ๊ฑฐ๋กญ๊ณ  ์‹ค์ˆ˜ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋กœ๊ทธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ์‹ธ์„œ context์—์„œ ์ž๋™์œผ๋กœ Request ID๋ฅผ ๊บผ๋‚ด ๋กœ๊น…์— ํฌํ•จ

func LogWithContext(ctx context.Context, msg string) {
    reqID, _ := ctx.Value(requestIDKey).(string)
    log.Printf("[reqID:%s] %s", reqID, msg)
}

๐Ÿงฉ ํŒ€์—์„œ ๊ณตํ†ต Logger ์œ ํ‹ธ๋กœ ๋งŒ๋“ค์–ด ๋‘๋ฉด ๋งค์šฐ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


โœ… 3.Request ID๋ฅผ ์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹œ ์ „ํŒŒ

์™œ ์ค‘์š”ํ•œ๊ฐ€?

๋‹จ์ผ ์„œ๋น„์Šค ๋‚ด์—์„œ๋งŒ Request ID๋ฅผ ์“ฐ๋Š” ๊ฑด ๋ฐ˜์ชฝ์งœ๋ฆฌ์ž…๋‹ˆ๋‹ค.

์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹œ์—๋„ ๋ฐ˜๋“œ์‹œ X-Request-ID๋ฅผ ํ—ค๋”๋กœ ์ถ”๊ฐ€ํ•ด ์ฃผ๋Š” ๊ฒƒ์ด ๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

req.Header.Set("X-Request-ID", reqID)

๊ทธ๋ฆฌ๊ณ  ํ˜ธ์ถœ๋ฐ›๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค๋„ ๊ทธ ๊ฐ’์„ context์— ์ €์žฅํ•ด์„œ ๋‹ค์‹œ DB, ๋กœ๊ทธ, ๋‹ค๋ฅธ API์— ์ „ํŒŒํ•ด์•ผ ํŠธ๋ ˆ์ด์‹ฑ์˜ ์—ฐ์†์„ฑ์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.


profile
๊ฐœ๋ฐœ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋Š”, ์ด์Šˆ๋ฅผ ์ค„์ด๋Š” ํ™˜๊ฒฝ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€