Tarkibga o'tish

Performance

Gin ko‘pincha "tez framework" sifatida tanilgan. Uning routeri haqiqatan ham samarali. Lekin Framework kerakmi? darsida aytilganidek, real API’da vaqtning asosiy qismi routing’ga emas, database, tarmoq va JSON bilan ishlashga sarflanadi.

Shuning uchun bu darsda "Gin’ni qanday tezlashtirish mumkin?" degan savoldan ko‘ra kengroq masalani ko‘rib chiqamiz: Gin ilovasini production uchun qanday to‘g‘ri sozlash kerak va tezlikni qanday o‘lchash kerak. Taxmin qilish emas, o‘lchash — performance bilan ishlashning asosiy qoidasi.

1. Release rejimi

Gin standart holatda debug rejimida ishlaydi. Bu rejimda u marshrutlar ro‘yxatini va ogohlantirishlarni chiqaradi. Production’da release rejimini yoqing:

gin.SetMode(gin.ReleaseMode)

Yoki muhit o‘zgaruvchisi orqali:

export GIN_MODE=release

Release rejimi har bir so‘rovni sezilarli tezlashtirmaydi. Debug xabarlari asosan ishga tushish paytida chiqadi. Lekin loglarni ortiqcha yozuvlardan tozalaydi va production uchun to‘g‘ri holat hisoblanadi.

2. Middleware’larni ongli tanlash

gin.Default() Logger va Recovery middleware’larini avtomatik qo‘shadi. Production’da ko‘pincha gin.New() bilan boshlab, kerakli middleware’larni o‘zingiz qo‘shgan ma’qul:

r := gin.New()
r.Use(
    gin.CustomRecovery(recoverJSON), // panic → JSON 500
    requestLogger(logger),           // slog bilan tuzilgan loglar
)
  • gin.Logger() matn formatida yozadi. Loglar tizimi JSON kutsa, log/slog asosidagi o‘z middleware’ingizni yozing. Uni Logging middleware darsidagi kabi yozish mumkin, faqat Gin shaklida.
  • Har bir middleware har bir so‘rovda ishlaydi. Ular orasida sekin amallar, masalan har so‘rovda bazaga murojaat, bo‘lmasligi kerak.

3. http.Server va timeoutlar

r.Run() timeoutlarsiz server yaratadi. Production’da o‘z http.Serveringizni yarating va serverni tartib bilan to‘xtatishni qo‘shing:

main.go
package main

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

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

func main() {
    gin.SetMode(gin.ReleaseMode)

    r := gin.New()
    r.Use(gin.Recovery())
    if err := r.SetTrustedProxies([]string{"10.0.0.0/8"}); err != nil {
        log.Fatal(err)
    }

    r.GET("/healthz", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           r,
        ReadHeaderTimeout: 5 * time.Second,
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      15 * time.Second,
        IdleTimeout:       60 * time.Second,
        MaxHeaderBytes:    1 << 20,
    }

    go func() {
        log.Println("Server: http://localhost:8080")
        if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
            log.Fatalf("server: %v", err)
        }
    }()

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()
    <-ctx.Done()

    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    if err := srv.Shutdown(shutdownCtx); err != nil {
        log.Printf("to‘xtatishda xato: %v", err)
    }
    log.Println("Server to‘xtadi")
}

Bu kod http.HandleFunc va http.ListenAndServe darsidagi graceful shutdown bilan bir xil. Farqi faqat bitta: Handler sifatida Gin router’i berilgan.

4. Ishonchli proxy’lar va klient IP

Ilova odatda nginx, load balancer yoki Cloudflare ortida ishlaydi. Bunday holatda so‘rov ilovaga proxy orqali keladi va RemoteAddr klientning emas, proxy’ning manzilini ko‘rsatadi. Klientning haqiqiy IP manzili X-Forwarded-For sarlavhasida bo‘ladi.

c.ClientIP() shu sarlavhani o‘qiydi. Lekin sarlavhani istalgan klient o‘zi ham yuborishi mumkin:

curl -H "X-Forwarded-For: 1.2.3.4" https://api.example.uz/

Gin standart holatda barcha proxy’larga ishonadi. Natijada hujumchi o‘z IP manzilini soxtalashtira oladi. Bu IP bo‘yicha rate limiting va brute-force himoyani chetlab o‘tish imkonini beradi.

Shuning uchun faqat o‘z proxy’laringizni ishonchli deb belgilang:

r.SetTrustedProxies([]string{"10.0.0.0/8"}) // ichki tarmoqdagi load balancer
// yoki proxy umuman bo‘lmasa:
r.SetTrustedProxies(nil)

5. JSON

Gin JSON uchun standart holatda encoding/json paketini ishlatadi. Build teglari orqali uni tezroq kutubxonaga almashtirish mumkin:

go build -tags=sonic .     # bytedance/sonic (Linux, Windows, macOS)
go build -tags=go_json .   # goccy/go-json
go build -tags=jsoniter .  # json-iterator/go

Bu o‘zgarish kodni o‘zgartirmaydi, faqat build buyrug‘iga teg qo‘shiladi. Lekin uni yoqishdan oldin ikki narsani tekshiring:

  • JSON haqiqatan ham ilovaning sekin joyi ekanini, ya’ni profiling natijalarini;
  • tanlangan kutubxona encoding/json bilan bir xil natija berishini: bo‘sh qiymatlar, omitempty va maxsus MarshalJSON metodlari bilan.

Javob hajmini kamaytirish ham tezlikka ta’sir qiladi. Ro‘yxatlarni sahifalang, keraksiz maydonlarni DTO’dan olib tashlang. Bu JSON kutubxonasini almashtirishdan ko‘pincha ko‘proq foyda beradi.

6. O‘lchash: benchmark

Handler qanchalik tez ishlashini Go’ning standart benchmark vositasi bilan o‘lchash mumkin. Benchmark haqida batafsil Benchmark darsida gaplashilgan.

main_test.go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"

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

type Book struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Author string `json:"author"`
}

func newRouter() *gin.Engine {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()

    books := make([]Book, 50)
    for i := range books {
        books[i] = Book{ID: i + 1, Title: "O‘tkan kunlar", Author: "Abdulla Qodiriy"}
    }

    r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") })
    r.GET("/books/:id", func(c *gin.Context) { c.JSON(http.StatusOK, books[0]) })
    r.GET("/books", func(c *gin.Context) { c.JSON(http.StatusOK, books) })
    return r
}

func benchmarkRoute(b *testing.B, path string) {
    r := newRouter()
    req := httptest.NewRequest(http.MethodGet, path, nil)

    b.ReportAllocs()
    for b.Loop() {
        w := httptest.NewRecorder()
        r.ServeHTTP(w, req)
    }
}

func BenchmarkPing(b *testing.B)     { benchmarkRoute(b, "/ping") }
func BenchmarkBookByID(b *testing.B) { benchmarkRoute(b, "/books/42") }
func BenchmarkBookList(b *testing.B) { benchmarkRoute(b, "/books") }
go test -bench=. -benchmem
BenchmarkPing-12              917920          1303 ns/op        1040 B/op          9 allocs/op
BenchmarkBookByID-12          591194          1833 ns/op        1121 B/op         11 allocs/op
BenchmarkBookList-12           70831         15770 ns/op        7383 B/op         11 allocs/op

Natijalar kompyuterga qarab farq qiladi. Lekin nisbatlar muhim:

  • routing va oddiy matn javobi — taxminan 1 mikrosekund. Bunga httptest.NewRecorder yaratish vaqti ham kiradi;
  • bitta obyektni JSON’ga aylantirish — bunga yana taxminan 0.5 mikrosekund qo‘shiladi;
  • 50 ta obyektli ro‘yxat — o‘n mikrosekunddan ko‘proq. Vaqtning asosiy qismi JSON’ga ketadi.

Hatto eng sekin natija ham oddiy database so‘rovidan (millisekundlar) yuz barobar tezroq. Bu Gin’ni tezlashtirishga urinishdan oldin database so‘rovlarini optimallashtirish ancha muhimroq ekanini ko‘rsatadi.

b.Loop() Go 1.24 da qo‘shilgan. Eski versiyalarda for i := 0; i < b.N; i++ ko‘rinishidagi sikl ishlatiladi.

7. Yuklama testi

Benchmark bitta handlerni alohida o‘lchaydi. Butun servisni real sharoitga yaqin holatda sinash uchun yuklama testi (load testing) vositalari ishlatiladi: hey, wrk, oha, k6.

go install github.com/rakyll/hey@latest
hey -n 20000 -c 50 http://localhost:8080/healthz

-n — jami so‘rovlar soni, -c — bir vaqtda yuboriladigan so‘rovlar soni. Natijada o‘rtacha javob vaqti, percentillar (p50, p95, p99) va sekundiga so‘rovlar soni chiqadi.

Warning

Yuklama testini faqat o‘zingizga tegishli serverlarga qarshi o‘tkazing. Birovning serveriga ruxsatsiz yuklama berish xizmat ko‘rsatishni rad etish hujumi (DoS) hisoblanadi.

O‘rtacha qiymatga emas, p95 va p99 ga e’tibor bering. O‘rtacha javob vaqti 20 ms bo‘lsa ham, har yuzinchi foydalanuvchi 2 soniya kutayotgan bo‘lishi mumkin.

8. Profiling: pprof

Qaysi funksiya ko‘p vaqt olayotganini aniqlash uchun Go’ning pprof vositasi ishlatiladi. Uni Gin ilovasiga alohida, faqat ichki tarmoqdan kirish mumkin bo‘lgan portda ulash xavfsizroq:

import _ "net/http/pprof"

go func() {
    // pprof faqat localhost'da, asosiy API portidan alohida
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()

net/http/pprof paketi import qilinganda o‘z handlerlarini http.DefaultServeMuxga qo‘shadi. Gin router’i esa alohida portda ishlaydi, shuning uchun profiler tashqi foydalanuvchilarga ochilmaydi.

30 soniyalik CPU profilini yig‘ish:

go tool pprof -http=:9090 http://localhost:6060/debug/pprof/profile?seconds=30

Brauzerda interaktiv grafik ochiladi. Unda eng ko‘p vaqt olgan funksiyalar ko‘rinadi.

Danger

pprof endpointlarini hech qachon ommaviy portda ochmang. Ular dastur ichki tuzilishi haqida ma’lumot beradi va resurs talab qiladigan amallarni ishga tushirish imkonini beradi.

Tekshiruv ro‘yxati

# Sozlama Nima uchun
1 gin.SetMode(gin.ReleaseMode) Debug chiqishini o‘chiradi
2 gin.New() va kerakli middleware’lar Ortiqcha ishni kamaytiradi
3 http.Server timeoutlar bilan Osilib qolgan ulanishlardan himoya
4 Graceful shutdown Deploy paytida so‘rovlar uzilmaydi
5 SetTrustedProxies IP manzilni soxtalashtirishning oldini oladi
6 So‘rov tanasi chegarasi Katta tanalar bilan hujumning oldini oladi
7 Sahifalash va DTO Javob hajmini kamaytiradi
8 Benchmark, yuklama testi, pprof Taxmin emas, o‘lchov

Xulosa

  • Production’da release rejimini yoqing va middleware’larni ongli tanlang.
  • r.Run() o‘rniga timeoutlar bilan sozlangan http.Server va graceful shutdown ishlating.
  • SetTrustedProxies bilan faqat o‘z proxy’laringizga ishoning, aks holda ClientIP()ni soxtalashtirish mumkin.
  • JSON kutubxonasini almashtirishdan oldin o‘lchang. Javob hajmini kamaytirish ko‘pincha ko‘proq foyda beradi.
  • Benchmark, yuklama testi va pprof — tezlik haqidagi savollarga javob beradigan vositalar.
  • Routing odatda sekin joy emas. Database va tashqi chaqiruvlarga e’tibor bering.

Shu bilan Gin bo‘limi tugadi. Keyingi bo‘limda API’ni HTTPS orqali xavfsiz ishlatishni o‘rganamiz.