tutorial

125 Performance Tips untuk gqlgen di Produksi

125 Performance Tips untuk gqlgen di Produksi

Banyak dari kita mengenal dan mencintai kecepatan serta tipikal schema-first dari gqlgen, salah satu GraphQL server paling populer di ekosistem Go. Namun, ketika aplikasi kita mulai dipakai oleh ribuan, bahkan jutaan user di produks,i performa sering jadi bottleneck yang mahal. Di artikel ini, saya akan mengumpulkan 125 tips performa — mulai dari fondasi hingga micro-optimisasi — untuk kamu yang serius menjalankan gqlgen di production, lengkap dengan kode, data, hingga flowchart.


Roadmap Optimasi gqlgen

Sebelum masuk ke 125 tips, mari kita lihat roadmap optimasi gqlgen secara umum.

graph TD
    subgraph Fundamental
        A[Schema Design]
        B[Efficient Go Code]
        C[Proper Database Usage]
    end
    subgraph Intermediate
        D[Batching & Caching]
        E[Middleware/Instrumentation]
        F[Concurrency]
    end
    subgraph Advanced
        G[Field-level Optimizations]
        H[Custom Extensions/Plugins]
        I[Profiling/Benchmarks]
    end

    A --> D
    D --> G
    B --> E
    C --> F
    E --> H
    F --> I

I. Schema dan Modularisasi Basics (1–20)

  1. Desain Skema Minimalis
    Hanya expose field & tipe yang benar2 dibutuhkan klien.

  2. Gunakan Scalar Types
    Hindari tipe ‘meh-n’ atau custom berlebih; default scalar Go (int, float, string, boolean) di-mapping ke backend lebih efisien.

  3. Restructure Nested Object
    Hindari nested object terlalu dalam, group field yang sering dipakai bersama.

  4. Document your Schema
    Developer baru tak perlu menebak sehingga mengurangi technical debt.

# Efficient
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
}

# Too Bloated
type User {
    id: ID!
    name: String!
    email: String
    mobilePhone: String
    address: String
    twitter: String
    facebook: String
    linkedin: String
    # ...dan seterusnya
}
  1. Group Query/Mutaion Berdasarkan Modul
    Pisahkan query user, transaksi, dsb ke file/module terpisah.

  1. Gunakan schema stitching HANYA jika perlu
    Stitching schema sangat powerful tapi juga mahal secara performa.

II. Optimisasi Resolver (21–55)

  1. Avoid N+1
    Gunakan dataloader untuk batch data fetching.
postsLoader := dataloader.NewBatchedLoader(fetchPostsBatch)
  1. Kembalikan Hanya Field yang Perlu
    Query di GQL memungkinkan klien memilih field, pastikan resolvers hanya fetch field yang diminta.

  2. Optimalisasi Resolver Chain
    Jangan lakukan pekerjaan berat di chain resolver, delegasikan ke layer lain.

  3. Gunakan Context dengan Bijak
    Pass context hanya bila memang butuh; jangan pernah bawa context.Background() by default.

  4. Parallel Resolve
    Resolver dengan operasi IO-heavy (HTTP, DB) bisa run concurrency (menggunakan goroutine & channel).

go func() {
    // long running
    result := fetchFromRemote(ctx)
    ch <- result
}()
select {
    case res := <-ch: 
        // gunakan hasil
    case <-ctx.Done(): 
        // timeout / cancell
}
  1. Gunakan ErrorList daripada Panic
    gqlgen akan mengembalikan error yang clean, memungkinkan partial success.

  1. Batasi Size Query/Mutation
    Pakai limit & offset, opsional: pagination.

  1. Caching Partial Field di Resolver
    Untuk field-field berat, gunakan cache (mis: redis) berdasarkan subquery key.

III. Optimasi Database Layer (56–80)

  1. Gunakan Query Builder
    Lihat package squirrel atau sqlc demi query yang aman dan optimal.

  2. Limit Field SQL
    Select hanya kolom yang benar2 dibutuhkan.

  3. Gunakan Index yang Tepat
    Identifikasi field filter/finding terbesar.

  4. Bulk Insert/Update
    Setiap resolver insert data masal/array lakukan bulk, bukan loop-insert.

  1. Short-Lived SQL Conn & Pool
    Set max idle/active connection jangan lebih besar dari max goroutine resolver.

  1. Optimasi Proyeksi DB Berdasarkan Field Query
    Dynamic SELECT berdasarkan field di selection set GraphQL.

  1. Prepared Statement
    Minim overhead parsing SQL: gunakan prepared/parameterized query.

IV. Concurrency & Parallelism (81–95)

  1. Resolver Pooling
    Implement worker-pool pattern jika ada resolver berat.

  2. Max Worker Limit
    Pastikan jumlah worker sebanding kapasitas CPU, hindari deadlock.

func worker(jobs <-chan Job, results chan<- Result, db *sql.DB) {
    for job := range jobs {
        res, err := processJob(job, db)
        results <- Result{res, err}
    }
}

  1. Set Connection Timeout
    Batasi waktu backend/database supaya free resource.

  1. Monitor Goroutine Leaks
    Gunakan pprof untuk profiling goroutine usage setiap deploy.

V. Caching & Batching External Calls (96–115)

  1. Dataloader Pattern
    Gunakan library github.com/graph-gophers/dataloader utk batch field.

  2. Per-Request Caching
    Cache query dalam scope satu request, terutama lookup yang berulang.

  1. Distributed Cache
    Untuk data heavy, gunakan Redis/Memcached.

  1. Cache Invalidation Policy
    Implement strategi invalidasi pada update.

  1. Cache Field-level Partial Result
    Untuk field berat, key-kan berdasarkan input dan selection set.

VI. Instrumentation & Observability (116–125)

  1. Metrics pada Resolver
    Track durasi masing2 resolver, mis: via Prometheus:
prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) {
    //store timing
})).ObserveDuration()
  1. Implement Logging Context-aware
    Logging harus bawa query id/request id.

  2. Distributed Tracing
    Gunakan OpenTelemetry agar jejak antar microservices.

  3. PPROF, Trace Routine
    Pprof diintegrasikan di /debug/pprof/ endpoint.

  1. Load Test Pra-Production
    Benchmark skenario worst-case query.

  2. Audit Schema dan Deprecated Field
    Hapus field yang sudah tak digunakan, tiap kuartal.


Simulasi: Efek Optimasi Caching & Dataloader

Berikut simulasi sederhana dampak batch resolver dengan dataloader:

Tanpa Dataloader (N+1 Problem)

Userposts countQuery ke DB
u134
u256
u312
Total12

Dengan Dataloader (Batching)

Userposts countQuery ke DB
u131
u251
u311
Total3

Contoh Setup Caching Dataloader

type loaders struct {
    UserLoader *dataloader.Loader
    PostLoader *dataloader.Loader
}

func DataloaderMiddleware(l *loaders) func(http.Handler) http.Handler {
   //inject loader ke ctx request
}

Penutup

Optimasi gqlgen bukan checklist one-off. Ini maraton iteratif yang harus diiringi monitor, benchmark, dan review terus menerus seiring pertumbuhan aplikasi. Dengan 125 tips ini, semoga kamu bisa memilih best practice yang tepat sesuai konteks production mu — dari schema, batch, concurrency, hingga observability — untuk GraphQL API yang gesit, scalable, dan maintainable.

Punya tips atau pengalaman lain? Sharing di komentar ya!

comments powered by Disqus