Worker Pool Pattern #
Dalam dunia concurrent programming, salah satu tantangan terbesar adalah bagaimana memproses banyak pekerjaan (tasks) secara paralel tanpa membebani resource sistem secara berlebihan. Terlalu sedikit worker membuat sistem lambat, terlalu banyak justru menyebabkan resource contention.
Di sinilah Worker Pool Pattern berperan. Pattern ini sangat populer di sistem backend, job processing, message consumer, hingga data pipeline, terutama pada bahasa yang memang dirancang untuk concurrency seperti Golang.
Artikel ini akan membahas Worker Pool Pattern secara menyeluruh: konsep, tujuan, kapan digunakan, perbandingannya dengan Thread Pool, contoh implementasi di Go, serta best practice yang sering terlewat.
Apa itu Worker Pool Pattern? #
Worker Pool Pattern adalah pola concurrency di mana sejumlah worker (biasanya goroutine atau thread) dibuat terlebih dahulu untuk memproses sekumpulan job yang dikirim melalui sebuah antrian (queue).
Alih-alih membuat satu thread/goroutine untuk setiap task, sistem:
- Membuat jumlah worker yang tetap atau terkontrol
- Menyediakan job queue
- Worker mengambil job dari queue dan memprosesnya
Secara konseptual:
Jobs -> [ Job Queue ] -> Worker 1
-> Worker 2
-> Worker N
Worker akan hidup selama aplikasi berjalan atau hingga queue ditutup.
Tujuan Pattern #
Worker Pool Pattern memiliki beberapa tujuan utama:
Kontrol Concurrency Membatasi jumlah task yang berjalan bersamaan agar tidak membanjiri CPU, memory, DB connection, atau external service.
Resource Efficiency Worker digunakan ulang (reuse), menghindari overhead pembuatan thread/goroutine berulang.
Stabilitas Sistem Mencegah lonjakan beban mendadak (thundering herd problem).
Throughput yang Konsisten Sistem memproses job secara steady daripada spike ekstrem.
Kapan Cocok Digunakan? #
Worker Pool sangat cocok jika:
- Banyak task independen dan homogen
- Task bersifat I/O bound (HTTP call, DB query, disk, message queue)
- Task datang terus-menerus (stream / queue based)
- Resource eksternal memiliki limit (rate limit, connection pool)
Contoh use case nyata:
- Consumer Kafka / PubSub / SQS
- Background job (email sender, notification, image processing)
- Web crawler
- Batch processing data
- Parallel API calls dengan limit tertentu
Kurang cocok jika:
- Task sangat sedikit
- Task saling bergantung kuat
- Task membutuhkan prioritas kompleks
Contoh Implementasi (Golang) #
Golang memiliki channel dan goroutine yang membuat Worker Pool sangat natural.
Contoh Sederhana #
package main
import (
"fmt"
"sync"
"time"
)
type Job struct {
ID int
}
func worker(id int, jobs <-chan Job, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job.ID)
time.Sleep(time.Second) // simulasi kerja
}
}
func main() {
const workerCount = 3
const jobCount = 10
jobs := make(chan Job)
var wg sync.WaitGroup
// start workers
for i := 1; i <= workerCount; i++ {
wg.Add(1)
go worker(i, jobs, &wg)
}
// send jobs
for j := 1; j <= jobCount; j++ {
jobs <- Job{ID: j}
}
close(jobs)
wg.Wait()
}
Penjelasan Singkat #
jobsadalah job queue- Worker membaca dari channel sampai channel ditutup
WaitGroupmemastikan semua worker selesai- Jumlah worker mengontrol tingkat concurrency
Worker Pool vs Thread Pool #
Walaupun sering disamakan, ada perbedaan konseptual penting.
Thread Pool #
- Biasanya berbasis OS thread
- Digunakan di Java, C++, .NET
- Thread relatif berat
- Scheduling banyak bergantung pada OS
Worker Pool (di Go) #
- Biasanya berbasis goroutine
- Goroutine jauh lebih ringan
- Scheduling dikelola oleh Go runtime
- Channel menjadi komponen utama komunikasi
Ringkasannya #
| Aspek | Thread Pool | Worker Pool (Go) |
|---|---|---|
| Unit | OS Thread | Goroutine |
| Overhead | Tinggi | Rendah |
| Komunikasi | Shared memory / lock | Channel |
| Skalabilitas | Terbatas | Sangat tinggi |
Secara konsep, Worker Pool adalah general pattern, Thread Pool adalah salah satu implementasinya.
Variasi dan Pengembangan #
Beberapa variasi lanjutan:
- Dynamic Worker Pool: worker bertambah/berkurang
- Priority Queue: job dengan prioritas
- Retry & Dead Letter Queue
- Rate Limited Worker Pool
- Pipeline Worker Pool (multi-stage)
Pattern ini sering menjadi fondasi sistem job processing yang lebih kompleks.
Best Practices #
1. Tentukan Jumlah Worker dengan Benar #
- CPU bound →
runtime.NumCPU() - I/O bound → bisa lebih besar dari CPU
- Jangan asal besar
2. Gunakan Buffered Channel Jika Perlu #
Buffered channel membantu meredam lonjakan job:
jobs := make(chan Job, 100)
Namun buffer terlalu besar bisa menyembunyikan bottleneck.
3. Selalu Tangani Graceful Shutdown #
Gunakan:
context.Context- Signal (
SIGTERM,SIGINT)
Agar worker berhenti dengan rapi.
4. Tangani Panic di Worker #
defer func() {
if r := recover(); r != nil {
log.Println("worker panic", r)
}
}()
Satu panic seharusnya tidak mematikan seluruh pool.
5. Jangan Lupakan Backpressure #
Jika producer lebih cepat dari consumer:
- Batasi buffer
- Atur rate producer
- Tambahkan monitoring queue length
6. Observability Itu Wajib #
Pantau:
- Jumlah job
- Waktu proses
- Error rate
- Worker utilization
Tanpa ini, worker pool bisa menjadi silent bottleneck.
Penutup #
Worker Pool Pattern adalah salah satu concurrency pattern paling penting dan praktis di software engineering modern. Ia membantu menjaga keseimbangan antara performa, stabilitas, dan efisiensi resource.
Di Golang, Worker Pool terasa sangat natural karena dukungan goroutine dan channel. Namun, seperti semua pattern, kekuatannya ada pada penggunaan yang tepat, bukan sekadar implementasi.
Jika Anda membangun sistem yang memproses banyak pekerjaan secara paralel, memahami Worker Pool bukan lagi pilihan — tapi kebutuhan.