pgx — driver Postgres yang layak dipakai
pgx bisa dipakai sebagai driver database/sql biasa, atau langsung lewat API aslinya yang lebih cepat dan mengerti tipe Postgres. Pilihan itu menentukan seberapa banyak kode yang harus ditulis ulang nanti.
Intisari
- Dua mode: lewat
database/sql(portabel) ataupgxpoollangsung (lebih cepat, tipe Postgres asli). - Mode asli mendukung
uuid,jsonb,array, danintervaltanpa konversi manual. pgx.CollectRowsmenghapus seluruh boilerplaterows.Next()/Scan.CopyFrommemuat ribuan baris berkali-kali lipat lebih cepat daripada INSERT beruntun.- Error Postgres punya kode:
23505unique violation,23503foreign key — petakan jadi error domain, jangan cocokkan teksnya.
Dua mode
database/sql + stdlib | pgxpool langsung | |
|---|---|---|
| Import | pgx/v5/stdlib | pgx/v5/pgxpool |
| Bisa ganti database nanti | Ya | Tidak |
| Tipe Postgres asli | Terbatas | Penuh |
| Performa | Baik | Lebih baik (protokol biner) |
CopyFrom, batch, LISTEN/NOTIFY | Tidak | Ya |
| Cocok untuk | Yang mungkin pindah database | Yang sudah pasti Postgres |
Rekomendasi roadmap ini: pgxpool langsung. "Nanti mungkin pindah ke MySQL"
hampir tidak pernah terjadi, dan harganya adalah kehilangan jsonb, array, dan
CopyFrom selamanya. Kalau kamu tetap ingin abstraksi, letakkan batasnya di
interface repository milikmu sendiri (Fase 1) — bukan di driver.
Menyiapkan pool
import "github.com/jackc/pgx/v5/pgxpool"
func BukaPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
cfg, err := pgxpool.ParseConfig(dsn)
if err != nil {
return nil, fmt.Errorf("parse dsn: %w", err)
}
cfg.MaxConns = 25
cfg.MinConns = 5
cfg.MaxConnLifetime = 30 * time.Minute
cfg.MaxConnIdleTime = 5 * time.Minute
cfg.HealthCheckPeriod = time.Minute
pool, err := pgxpool.NewWithConfig(ctx, cfg)
if err != nil {
return nil, err
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, fmt.Errorf("ping: %w", err)
}
return pool, nil
}
DSN-nya bisa berupa URL atau pasangan kunci-nilai:
postgres://pengguna:sandi@host:5432/basisdata?sslmode=require&pool_max_conns=25
Di AWS, sslmode tidak boleh disable. RDS mendukung TLS secara
bawaan; require adalah minimum, dan verify-full dengan sertifikat CA RDS
adalah yang benar untuk data sensitif. Kredensialnya sendiri datang dari Secrets Manager, bukan dari
berkas (Fase 10).
Kueri dengan API asli
// Satu baris
var p Produk
err := pool.QueryRow(ctx,
`SELECT id, nama, harga FROM produk WHERE id = $1`, id,
).Scan(&p.ID, &p.Nama, &p.Harga)
if errors.Is(err, pgx.ErrNoRows) { // BUKAN sql.ErrNoRows
return Produk{}, ErrTidakDitemukan
}
// Banyak baris — tanpa loop manual sama sekali
rows, err := pool.Query(ctx,
`SELECT id, nama, harga FROM produk WHERE harga < $1`, batas)
if err != nil {
return nil, err
}
hasil, err := pgx.CollectRows(rows, pgx.RowToStructByName[Produk])
CollectRows menghapus seluruh blok defer rows.Close() /
for rows.Next() / rows.Err() — termasuk dua baris wajib yang paling
sering lupa ditulis. RowToStructByName mencocokkan nama kolom dengan tag
db:"..." pada struct. Ini salah satu alasan paling praktis memakai API asli.
Tipe Postgres yang langsung bekerja
type Produk struct {
ID uuid.UUID `db:"id"`
Nama string `db:"nama"`
Tag []string `db:"tag"` // text[]
Meta map[string]any `db:"meta"` // jsonb
Berlaku pgtype.Range[time.Time] `db:"berlaku"` // tstzrange
Dihapus *time.Time `db:"dihapus_pada"`
}
// jsonb langsung dari map — tanpa Marshal manual
_, err := pool.Exec(ctx,
`INSERT INTO produk (nama, tag, meta) VALUES ($1, $2, $3)`,
p.Nama, p.Tag, p.Meta)
Batch dan CopyFrom
// Batch: banyak kueri, SATU perjalanan jaringan
batch := &pgx.Batch{}
for _, p := range produk {
batch.Queue(`UPDATE produk SET harga = $1 WHERE id = $2`, p.Harga, p.ID)
}
hasil := pool.SendBatch(ctx, batch)
defer hasil.Close()
for range produk {
if _, err := hasil.Exec(); err != nil {
return err
}
}
// CopyFrom: jalur tercepat memuat data massal
_, err := pool.CopyFrom(ctx,
pgx.Identifier{"produk"},
[]string{"nama", "harga"},
pgx.CopyFromSlice(len(produk), func(i int) ([]any, error) {
return []any{produk[i].Nama, produk[i].Harga}, nil
}))
Untuk impor data, perbedaannya bukan puluhan persen. Sepuluh ribu INSERT
beruntun berarti sepuluh ribu perjalanan bolak-balik jaringan; CopyFrom mengirimkannya
sebagai satu aliran. Di lingkungan dengan latensi 1–2 ms per perjalanan — yaitu semua lingkungan AWS —
bedanya bisa dari menit jadi detik.
Kode error Postgres
import "github.com/jackc/pgx/v5/pgconn"
func petakanError(err error) error {
var pg *pgconn.PgError
if !errors.As(err, &pg) {
return err
}
switch pg.Code {
case "23505": // unique_violation
return fmt.Errorf("%s sudah dipakai: %w", pg.ConstraintName, ErrKonflik)
case "23503": // foreign_key_violation
return fmt.Errorf("referensi tidak valid: %w", ErrValidasiRelasi)
case "23514": // check_violation
return fmt.Errorf("melanggar aturan %s: %w", pg.ConstraintName, ErrValidasi)
case "40001", "40P01": // serialization_failure, deadlock_detected
return ErrCobaLagi
case "57014": // query_canceled
return context.DeadlineExceeded
}
return err
}
Ini pengganti cek "sudah ada?" sebelum menyimpan. Memeriksa dulu lalu menyimpan mengandung
balapan: dua permintaan bisa sama-sama lolos pemeriksaan. Biarkan unique constraint database
yang menjadi kebenaran, lalu terjemahkan kode 23505 jadi 409 Conflict. Satu kueri, tanpa
balapan.
Latihan: ganti repository dari materi database/sql ke pgxpool dan
pgx.CollectRows, lalu bandingkan jumlah barisnya. Tambahkan unique constraint pada kolom
nama, simpan dua produk bernama sama, dan petakan kode 23505 jadi
409 lewat errors.As.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.