Skip to content
Theme

Worker pools and job queues for Go

Add jobs to a queue. A pool of workers runs them. Keep the jobs in memory, or in Redis or SQLite so they survive a restart.

go get github.com/goptics/varmq

The story as text

Order, fairness and fan-out, each in one line.

Each scene shows the order varmq runs the jobs in, with the call that makes it. Typed results and errors, job IDs and deduplication, context cancellation, metrics, a REST API and Prometheus are in the guides.

Priority queues

Give a job a number. The lowest number runs first, and equal numbers keep the order you added them.

Queues guide

Several queues, one worker

Bind many queues to one worker. A strategy decides which queue gives the next job: Priority, RoundRobin, MaxLen or MinLen.

Strategies guide

Topics

Publish once and every subscribed worker gets its own job. A queue gives each job to one worker.

Topics guide

Same worker. You choose where the jobs live.

Press Restart process. A queue in memory ends with the process. A queue in Redis or SQLite keeps the waiting jobs.

Built in. Plain queues, priority queues and topics live in your process. They have nothing to set up, and they end when the process ends.

queue := worker.BindQueue()

// or, ordered by a number:
pq := worker.BindPriorityQueue()

Use it when a lost job is fine, or when you can add it again.

The numbers, and where they do not favor VarMQ.

Each lane is one library on the same harness, same machine and same run. The longest bar is the highest value. The last column is the lane's value divided by VarMQ's.

Mean tasks per second over 15 rows: 3 task types × 5 producer shapes (1 to 1,000,000 producers), 50K workers, 1M tasks per run. Averaged over GOMAXPROCS 24, 8 and 4. Memory is bytes allocated per run of 1M tasks, from the same rows.

Throughput

tasks per second · higher is better
  1. VarMQ4.89M1.00×
  2. Buffered channel †2.73M0.56×
  3. errgroup (opens in a new tab)2.29M0.47×
  4. ants (opens in a new tab)2.22M0.45×
  5. gopool (opens in a new tab)1.92M0.39×
  6. Channel †1.90M0.39×
  7. conc (opens in a new tab)1.56M0.32×
  8. workerpool (opens in a new tab)1.35M0.28×
  9. pond (opens in a new tab)0.60M0.12×

Memory

MB allocated per 1M tasks · lower is better
  1. Channel †14.5 MB0.13×
  2. conc (opens in a new tab)15.0 MB0.13×
  3. ants (opens in a new tab)15.4 MB0.13×
  4. Buffered channel †23.2 MB0.20×
  5. workerpool (opens in a new tab)28.2 MB0.25×
  6. gopool (opens in a new tab)52.0 MB0.45×
  7. errgroup (opens in a new tab)110.7 MB0.96×
  8. VarMQ115.0 MB1.00×
  9. pond (opens in a new tab)317.8 MB2.76×

† Baselines, not libraries: N goroutines reading one channel. The buffered one has a channel that holds all 1M tasks.

What the same run says against VarMQ
  • Memory. VarMQ allocates 115 MB per 1M tasks. ants allocates 15 MB, which is 7.5× less. Add never blocks, so waiting tasks sit in an unbounded queue.
  • One worker. On the 15 one-worker Loop10 rows, a plain buffered channel is fastest in all 15.
  • 4 CPUs. The buffered channel wins 21 of 55 rows and VarMQ wins 19. At 24 CPUs VarMQ wins 39 and the channel 12.
  • Redis, one producer. rmq leads 12 of the 13 single-producer rows. VarMQ leads the enqueue-only row, and leads the 100-producer rows with one instance at 100 and 1,000 workers.

Run of 2026-10-25 · varmq v1.10.0 · Intel Core i7-13700 (24 threads), linux/amd64, Go 1.26 · Absolute numbers vary between runs, so compare ratios within one run. Figures come from the raw output in github.com/goptics/benchmarks; charts and the harness are at benchmarks.goptics.org.

Each tab is a full program.

Press Run to run it in the Go Playground, or edit it first.

A worker with the default concurrency of 1 runs two jobs, one after the other. Read it in the docs

quick-start/main.go
9 lines: package and imports
package main

import (
	"fmt"
	"time"

	"github.com/goptics/varmq"
)
func main() {
	worker := varmq.NewWorker(func(j varmq.Job[int]) {
		fmt.Printf("Processing %d\n", j.Data())
		time.Sleep(1 * time.Second)
	}) // default concurrency is 1
	defer worker.WaitUntilIdle()
	queue := worker.BindQueue()

	queue.Add(1)
	queue.Add(2)
}

Tab inserts a tab. Esc leaves the editor. Ctrl+Enter runs.

Output

Where to read next

Run your first worker.

go get github.com/goptics/varmq

VarMQ is MIT licensed. Benchmark figures come from github.com/goptics/benchmarks.