pulse

pulse

A reliable, Postgres-backed distributed job queue in Go — gRPC server, typed SDK, operator CLI, web dashboard.

Contention-free dispatch with FOR UPDATE SKIP LOCKED, retries with backoff and dead-letter, lease-based crash recovery, priority, scheduling, and pause/resume.

go get github.com/bete7512/pulse

A job is never lost

Submitted work is durable in Postgres before the submit returns — the queue is the database, not a process.

Retries, then dead-letter

Failures retry with attempts² backoff up to a bounded cap, then dead-letter with the last error preserved. Poison jobs can’t loop forever.

Crash recovery, automatic

Claims carry a lease; workers heartbeat to keep it; a watchdog routes lapsed leases back through the retry path. Zombie workers are fenced by attempt tokens.

Contention-free dispatch

Claims are FOR UPDATE SKIP LOCKED batches over a partial index — concurrent workers take disjoint batches, O(batch) at any backlog depth.

Priority & scheduling

Per-job priority with FIFO within a level. Schedule once, on an interval, or by cron — exactly-once per occurrence across replicas.

Operable by design

Pause dispatch durably for maintenance, inspect and requeue dead-lettered jobs, watch live stats — from the CLI or the web dashboard.

One client, both roles

The same *pulse.Client produces and consumes over a single gRPC connection. Handlers run in your process — the server only moves data.

type EmailArgs struct {
	To      string `json:"to"`
	Subject string `json:"subject"`
}

func main() {
	ctx := context.Background()

	p, err := pulse.New("localhost:50051",
		pulse.WithConcurrency(20),               // parallel handlers (default 10)
		pulse.WithUserPass("worker", "hunter2"), // when the server sets PULSE_AUTH_USERS
		pulse.WithTLS(nil),                      // system roots; pass *tls.Config to customize
	)
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	// register a handler — a plain typed function:
	pulse.Register(p, "send-email", func(ctx context.Context, a EmailArgs) error {
		return sendEmail(a.To, a.Subject)
	})

	// enqueue by name; urgent work jumps the queue:
	pulse.Enqueue(ctx, p, "send-email", EmailArgs{To: "a@b.com", Subject: "Welcome"})
	pulse.Enqueue(ctx, p, "send-email", EmailArgs{To: "vip@b.com"}, pulse.WithPriority(10))

	// schedule work — once, on an interval, or by cron:
	pulse.ScheduleJob(ctx, p, "send-email", EmailArgs{To: "digest@b.com"}, pulse.Cron("0 8 * * *"))

	p.Run(ctx) // process jobs until ctx is cancelled
}

How it works

01 · enqueue

One jobsrow holds each job's state, retry policy, and worker lease — durable before submit returns.

02 · claim

Workers stream assignments; the server claims disjoint batches with SKIP LOCKED — nothing to race, no lock queues.

03 · report

Guarded UPDATEs enforce every state transition in the WHERE clause — invariants live in the database, race-free by construction.

1,223

jobs/s end-to-end

0

claim conflicts

~1.6

DB transactions per job

2,000 jobs across 8 workers on one laptop — methodology and full numbers

Everything speaks the same API

Authored by Bete Goshme (bete7512)