๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด/Go

[Go] Gin ํ”„๋ ˆ์ž„์›Œํฌ

by ์„œ์•„๋ž‘๐Ÿ˜ƒ 2025. 9. 20.

 

๋“ค์–ด๊ฐ€๋ฉฐ

Gin์€ Go ์–ธ์–ด ๊ธฐ๋ฐ˜์˜ ๊ณ ์„ฑ๋Šฅ ์›น ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋ณ๊ณ  ์œ ์—ฐํ•˜๋ฉฐ ๋ฏธ๋“ค์›จ์–ด, ๋ผ์šฐํŒ…, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํžˆ ๋‹จ๊ณ„๋ณ„๋กœ ์‚ดํŽด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํŠน์ง• → ์žฅ๋‹จ์  → ์‹ค๋ฌด ์‚ฌ์šฉ ์˜ˆ์ œ → ์šด์˜ ํŒ ์ˆœ์„œ๋กœ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

1) ๊ฐœ์š”

  1. Gin์€ Go๋กœ ์ž‘์„ฑ๋œ ๊ฒฝ๋Ÿ‰(ํ•˜์ง€๋งŒ ๊ธฐ๋Šฅ ํ’๋ถ€) ์›น ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์ƒ์‚ฐ์„ฑ(๊ฐ„๋‹จํ•œ API)๊ณผ ์„ฑ๋Šฅ(๋น ๋ฅธ ๋ผ์šฐํŒ…)์„ ๋™์‹œ์— ๋…ธ๋ฆฌ๋Š” ์„œ๋น„์Šค์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. (Gin Web Framework)
  2. ์ฃผ ์‚ฌ์šฉ ์‚ฌ๋ก€: REST API, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค, ๋‚ด๋ถ€ ํˆด/๊ด€๋ฆฌ ์ฝ˜์†” ๋“ฑ — JSON ๋ฐ”์ธ๋”ฉ๊ณผ ๋ฏธ๋“ค์›จ์–ด ์กฐํ•ฉ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์šด์˜ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. (Gin Web Framework)

2) ์ฃผ์š” ํŠน์ง•

  1. ๋น ๋ฅธ ๋ผ์šฐํ„ฐ(Zero-allocation / radix tree ๊ธฐ๋ฐ˜): ๋ผ์šฐํŒ…์ด ๋งค์šฐ ํšจ์œจ์ ์œผ๋กœ ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. (Gin Web Framework)
  2. ๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ ์ง€์›(๋กœ๊น…, ๋ฆฌ์ปค๋ฒ„๋ฆฌ, ์ธ์ฆ, GZIP ๋“ฑ) — ๊ธฐ๋ณธ ์ œ๊ณต ๋ฏธ๋“ค์›จ์–ด์™€ ํ™•์žฅ ์ƒํƒœ๊ณ„๊ฐ€ ํ’๋ถ€ํ•ฉ๋‹ˆ๋‹ค. (GitHub)
  3. ๋ฐ”์ธ๋”ฉ/๊ฒ€์ฆ ํ†ตํ•ฉ: JSON/XML/Form ๋ฐ”์ธ๋”ฉ๊ณผ go-playground/validator ๊ธฐ๋ฐ˜์˜ ๊ฒ€์ฆ์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (Gin Web Framework)
  4. ๋ผ์šฐํŠธ ๊ทธ๋ฃนํ™”, ์—๋Ÿฌ ๊ด€๋ฆฌ, ํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง ๋“ฑ ์‹ค์ „ ๊ธฐ๋Šฅ ๋‚ด์žฅ. (GitHub)

3) ์žฅ์ 

  • ์žฅ์ 
    1. ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ — ์ง๊ด€์ ์ธ API์™€ ๋ฌธ์„œ, ์˜ˆ์ œ๊ฐ€ ๋งŽ์•„ ์ดˆ๊ธฐ ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ๋น ๋ฆ…๋‹ˆ๋‹ค. (Gin Web Framework)
    2. ์„ฑ๋Šฅ์ด ์šฐ์ˆ˜ — net/http ๊ธฐ๋ฐ˜ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ๋น ๋ฅธ ํŽธ์ด๋ฉฐ ์‹ค ์„œ๋น„์Šค์—์„œ ์ถฉ๋ถ„ํ•œ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. (Gin Web Framework)
    3. ์ž…๋ ฅ ๋ฐ”์ธ๋”ฉ๊ณผ ๊ฒ€์ฆ์ด ๊ฐ„ํŽธ → DTO/Struct์— ํƒœ๊ทธ๋งŒ ๋‹ฌ์•„๋‘๋ฉด ์ž๋™ ๋ฐ”์ธ๋”ฉ/๊ฒ€์ฆ์ด ๋ฉ๋‹ˆ๋‹ค. (Gin Web Framework)
    4. ํ’๋ถ€ํ•œ ๋ฏธ๋“ค์›จ์–ด·์ปค๋ฎค๋‹ˆํ‹ฐ → ๋กœ๊น…·๋ฆฌ์ปค๋ฒ„๋ฆฌ ๋“ฑ ๊ธฐ๋ณธ ๊ตฌ์„ฑ์œผ๋กœ ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ. (GitHub)
  • ๋‹จ์  / ๊ณ ๋ ค์‚ฌํ•ญ
    1. ๊ทนํ•œ์˜ ๋ฒค์น˜๋งˆํฌ ํ™˜๊ฒฝ์—์„œ๋Š” fasthttp ๊ธฐ๋ฐ˜ ํ”„๋ ˆ์ž„์›Œํฌ(Fiber ๋“ฑ)์— ์šฐ์œ„๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค — ์‹ค ํŠธ๋ž˜ํ”ฝ/IO๊ฐ€ ๋งŽ์€ ์„œ๋น„์Šค์—์„œ ๋ฏธ์„ธ ์ฐจ์ด๋ฅผ ๊ฒ€ํ† ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๊ทธ๋Ÿฌ๋‚˜ ๋Œ€๋ถ€๋ถ„์˜ ํ˜„์‹ค์  ์„œ๋น„์Šค์—์„œ๋Š” ์ฐจ์ด๊ฐ€ ํฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค). (Medium)
    2. WebSocket/ํ•˜์ด์žฌํ‚น๋œ ์—ฐ๊ฒฐ์€ graceful shutdown์—์„œ ์ถ”๊ฐ€ ๊ด€๋ฆฌ ํ•„์š” — http.Server.Shutdown()์€ hijacked ์—ฐ๊ฒฐ(์˜ˆ: WebSocket)์„ ์ž๋™์œผ๋กœ ์ •๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ณ„๋„ ์ข…๋ฃŒ ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. (VictoriaMetrics)
    3. ๋””๋ฒ„๊ทธ/๋ฆด๋ฆฌ์Šค ๋ชจ๋“œ ๊ด€๋ฆฌ ํ•„์š” — ๊ฐœ๋ฐœ ์ค‘ ๋””๋ฒ„๊ทธ ๋กœ๊ทธ๊ฐ€ ๋งŽ์ด ์ถœ๋ ฅ๋  ์ˆ˜ ์žˆ์–ด, ์šด์˜ํ™˜๊ฒฝ์—์„œ๋Š” gin.SetMode(gin.ReleaseMode) ์„ค์ •์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. (Go Packages)

4) ๋‹จ๊ณ„๋ณ„ ์‚ฌ์šฉ ์˜ˆ์‹œ (์‹ค์ „ ์ฝ”๋“œ · ์„ค๋ช…)

์•„๋ž˜ ์˜ˆ์ œ๋“ค์€ ์‹ค์ œ๋กœ ๋ฐ”๋กœ ๋ถ™์—ฌ ์จ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

์ค€๋น„(ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”)

mkdir mygin && cd mygin
go mod init example.com/mygin
go get github.com/gin-gonic/gin

(๊ณต์‹ Quickstart ์ฐธ์กฐ). (Gin Web Framework)


1. Hello world (๊ธฐ๋ณธ ๋ผ์šฐํŒ…)

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default() // Logger + Recovery ํฌํ•จ
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })
    r.Run(":8080")
}
  • gin.Default()๋Š” Logger/Recovery ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์–ด ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์ด ๋†’์Œ. (GitHub)

2. JSON ๋ฐ”์ธ๋”ฉ + ๊ฒ€์ฆ ์˜ˆ์ œ

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

type CreateUserReq struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"gte=0,lte=130"`
}

func main() {
    r := gin.Default()

    r.POST("/users", func(c *gin.Context) {
        var req CreateUserReq
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // ๊ฒ€์ฆ ํ†ต๊ณผ
        c.JSON(http.StatusCreated, gin.H{"user": req})
    })

    r.Run(":8080")
}
  • Gin์€ go-playground/validator๋ฅผ ์‚ฌ์šฉํ•ด binding ํƒœ๊ทธ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. (Gin Web Framework)

3. ๋ฏธ๋“ค์›จ์–ด·๋ผ์šฐํŠธ ๊ทธ๋ฃน ํ™œ์šฉ

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("X-Api-Key")
        if token != "secret" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error":"unauthorized"})
            return
        }
        c.Next()
    }
}

func main() {
    r := gin.New()
    r.Use(gin.Logger(), gin.Recovery())

    api := r.Group("/api")
    {
        v1 := api.Group("/v1")
        v1.Use(authMiddleware())
        v1.GET("/profile", func(c *gin.Context) {
            c.JSON(200, gin.H{"name":"alice"})
        })
    }

    r.Run(":8080")
}
  • Group์œผ๋กœ ๊ณตํ†ต ๊ฒฝ๋กœ·๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ฌถ์–ด ๊ด€๋ฆฌ → ๋Œ€๊ทœ๋ชจ API ๊ตฌ์กฐํ™”์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. (GitHub)

4. Graceful shutdown ์˜ˆ์ œ

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    gin.SetMode(gin.ReleaseMode) // ์šด์˜ ์‹œ ๊ถŒ์žฅ
    r := gin.New()
    r.Use(gin.Logger(), gin.Recovery())
    r.GET("/ping", func(c *gin.Context){ c.String(200, "pong") })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()
    // ์‹œ๊ทธ๋„ ๋Œ€๊ธฐ (Ctrl+C, SIGTERM)
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    log.Println("Shutdown Server ...")

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }
    log.Println("Server exiting")
}
  • http.Server.Shutdown() ํŒจํ„ด์œผ๋กœ ๊ธฐ์กด ์š”์ฒญ์„ ๋งˆ์นœ ๋’ค ์ข…๋ฃŒ. ๋‹ค๋งŒ WebSocket ๋“ฑ ํ•˜์ด์žฌํ‚น๋œ ์—ฐ๊ฒฐ์€ ๋ณ„๋„ ์ •๋ฆฌ ๋กœ์ง ํ•„์š”. (Gin Web Framework)

5) ์šด์˜ ํŒ / ๊ด€๋ จ ๊ฐœ๋…

  1. ๋ฆด๋ฆฌ์Šค ๋ชจ๋“œ: ์šด์˜์—์„œ ๋กœ๊ทธ ์žก์Œ์„ ์ œ๊ฑฐํ•˜๋ ค๋ฉด gin.SetMode(gin.ReleaseMode) ์‚ฌ์šฉ. (Go Packages)
  2. ํ”„๋กœํŒŒ์ผ๋ง: net/http/pprof๋กœ CPU/๋ฉ”๋ชจ๋ฆฌ ๋ถ„์„ — ๋ณ‘๋ชฉ ์›์ธ์„ ๋จผ์ € ์ฐพ๊ณ  ์ตœ์ ํ™”ํ•˜์„ธ์š”.
  3. ๋ฏธ๋“ค์›จ์–ด ๋น„์šฉ ๊ด€๋ฆฌ: ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก per-request ์˜ค๋ฒ„ํ—ค๋“œ ์ฆ๊ฐ€ → ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ ์šฉํ•˜๊ณ , ํ•ซ์ŠคํŒŸ์€ ํ”„๋กœํŒŒ์ผ๋ง ํ›„ ์ตœ์ ํ™”.
  4. ์„ฑ๋Šฅ ์ตœ์ ํ™” ์„ ํƒ์ง€: ์ ˆ๋Œ€ ์ตœ๋Œ€ RPS/์ดˆ์ €์ง€์—ฐ์ด ๋ชฉํ‘œ๋ผ๋ฉด fasthttp ๊ธฐ๋ฐ˜ ํ”„๋ ˆ์ž„์›Œํฌ(Fiber ๋“ฑ)๋ฅผ ๊ฒ€ํ† ํ•˜๋˜, ์ƒํƒœ๊ณ„·๊ฐœ๋ฐœ ํŽธ์˜์„ฑ๊ณผ trade-off๋ฅผ ๋”ฐ์ ธ ๊ฒฐ์ •ํ•˜์„ธ์š”. (Medium)
  5. ์ปจํ…Œ์ด๋„ˆ·K8s ๋ฐฐํฌ: readiness/liveness probe, SIGTERM ์ฒ˜๋ฆฌ(Graceful shutdown) ํ•„์ˆ˜. (Medium)

6) ๊ฒฐ๋ก 

  • ๋น ๋ฅด๊ฒŒ API๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ์šด์˜๊นŒ์ง€ ๊ณ ๋ คํ•œ ์•ˆ์ •์„ฑ์„ ์›ํ•˜๋ฉด Gin์ด ์ข‹์€ ์„ ํƒ์ž…๋‹ˆ๋‹ค(๋ฐ”์ธ๋”ฉ·๋ฏธ๋“ค์›จ์–ด·๋ฌธ์„œ·์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ์žฅ์ ). (GitHub)
  • ๊ทน๋‹จ์  ์„ฑ๋Šฅ(๋ฒค์น˜๋งˆํฌ ์šฐ์œ„)๊ฐ€ ์ตœ์šฐ์„ ์ด๋ผ๋ฉด fasthttp ๊ณ„์—ด์„ ๊ฒ€ํ† ํ•˜๋˜, ์‹ค์ œ ์„œ๋น„์Šค์—์„œ IO/DB์— ๋ฌถ์ด๋Š” ๊ฒฝ์šฐ ์ฐจ์ด๋Š” ์ž‘์Šต๋‹ˆ๋‹ค. (Medium)

๋Œ“๊ธ€