Library / SDK
hibiken/asynq avatar
hibiken/asynq

hibiken/asynq: a Redis-backed task queue for Go services

Simple, reliable, and efficient distributed task queue in Go

13,747 stars987 forksGoMIT

At a glance

What is it?
Asynq queues background work in Redis and runs it on Go worker pools. It fits teams already running Redis who want retries, scheduling and a web UI without adding a broker, but its v0.x API and Redis Cluster caveats are real constraints.
Who is it for?
Adopt Asynq if you are writing Go services, already operate Redis 4.0 or newer, and want retries, scheduling and a web UI without a separate broker. Do not adopt it if you need a stable v1 API, if your Redis deployment is a cluster, or if your workers are not written in Go, since the queue is a Go library and other languages would need to speak its Redis layout directly.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 99 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem Asynq solves for Go services

Any web request that sends an email, resizes an image or calls a slow third-party API is a request you would rather not hold open. Asynq exists to move that work out of the request path. The client enqueues a task, a separate server process pulls tasks off the queue and runs a handler, and the HTTP response returns immediately.

The README frames the target audience narrowly: Go developers who want a task queue they can add to an existing service. The library is a Go package, not a standalone daemon, so the unit of adoption is your own binary. The topics attached to the repository (asynchronous-tasks, background-jobs, go, golang, redis, task-queue, worker-pool) describe the same scope. If your stack is Go and you already run Redis, the marginal infrastructure cost is close to zero, which is the main reason to pick this over a general-purpose message broker.

The design assumption worth stating plainly: Asynq is not a message bus for arbitrary services. It is a work queue where each task has a type, a payload and a handler registered in your Go process.

How the client, server and Redis split the work

The README gives a three-part overview. A client puts tasks on a queue. A server pulls tasks off queues and starts a worker goroutine for each task. Tasks run concurrently across multiple workers, so a system can consist of multiple worker servers and brokers.

The repository layout matches that split. client.go enqueues, server.go runs the worker pool, servemux.go maps task types to handlers, and inspector.go exposes read and control operations over queues and tasks. Several files handle the failure paths: recoverer.go for tasks left behind when a worker crashes, janitor.go for cleanup, forwarder.go for moving scheduled tasks into the active queue, and heartbeat.go for tracking live workers. periodic_task_manager.go and scheduler.go cover recurring work. internal/proto/asynq.proto defines the task message format, and the Makefile shows it is compiled with protoc into internal/proto.

That file list is the honest architecture summary. Asynq keeps coordination state in Redis and relies on background goroutines inside your server processes rather than on a separate scheduler daemon. The trade-off is that every guarantee depends on Redis being reachable and on your worker processes staying alive long enough to run the recovery logic.

Installing Asynq and enqueueing your first task

The README requires Go (the last two Go versions are supported) and a Redis server, locally or from a Docker container, at version 4.0 or higher. Start by initializing a module and pulling the library.

bash
go mod init github.com/your/repo
go get -u github.com/hibiken/asynq

Next, define a task type and a constructor. The README's example uses a constant for the task type and JSON for the payload, and it attaches options at construction time.

go
const TypeEmailDelivery = "email:deliver"

func NewEmailDeliveryTask(userID int, tmplID string) (*asynq.Task, error) {
    payload, err := json.Marshal(EmailDeliveryPayload{UserID: userID, TemplateID: tmplID})
    if err != nil {
        return nil, err
    }
    return asynq.NewTask(TypeEmailDelivery, payload), nil
}

The handler side is a function with the asynq.HandlerFunc signature. The README's example decodes the payload and wraps a decoding failure in asynq.SkipRetry so a malformed task is not retried forever.

go
func HandleEmailDeliveryTask(ctx context.Context, t *asynq.Task) error {
    var p EmailDeliveryPayload
    if err := json.Unmarshal(t.Payload(), &p); err != nil {
        return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
    }
    log.Printf("Sending Email to User: user_id=%d, template_id=%s", p.UserID, p.TemplateID)
    return nil
}

A task can also carry per-task options. The README shows MaxRetry and Timeout passed to NewTask, and notes they can be overridden at enqueue time. What you should see after wiring the server is a worker log line per task and the task disappearing from the queue; the README does not document a rollback procedure for a task already handed to a worker, so plan for handlers that are safe to run more than once.

Monitoring, the CLI and the web UI

Two operator-facing tools ship in the repository. The tools/ directory contains a CLI, and the README links to tools/asynq/README.md, whose pause section documents stopping a queue from processing tasks. The same README lists a Web UI for inspecting and remote-controlling queues and tasks, and a Prometheus integration for collecting and visualizing queue metrics.

This is where Asynq differs from a bare Redis list. The inspector API and the UI are built on the same Redis state the workers use, so pausing a queue or inspecting a failed task does not require a side channel. The healthcheck.go file and the heartbeat mechanism give the server something to report about itself.

The limitation is that none of this is a hosted service. You run the UI, you run the CLI, and you scrape Prometheus yourself. The README does not describe retention policies for finished tasks, so how long task history stays in Redis is something you have to determine from the janitor and inspector code rather than from the documentation.

Where Asynq is the wrong tool

The README states the current major version is zero (v0.x.x) to accommodate rapid development, and that the public API could change without a major version update before v1.0.0. It also describes the library as undergoing moderate development with less frequent breaking API changes. That is a candid warning, and it means an upgrade can require code edits even on a minor version bump. The release history supports the caution: v0.25.0 to v0.25.1 to v0.26.0, with a long gap between v0.25.1 in December 2024 and v0.26.0 in February 2026.

The second constraint is Redis Cluster. The README says some of the Lua scripts in the library may not be compatible with Redis Cluster. Sentinel is supported for automatic failover, so high availability is possible, but a sharded cluster is not a configuration the documentation endorses.

The third is delivery semantics. Asynq guarantees at least one execution of a task. A handler that charges a card or sends a non-idempotent write must be written defensively, because a crash between execution and acknowledgement can cause a repeat. If you need exactly-once behaviour, this queue does not promise it.

Finally, the library is Go-only. There is no official client for other languages in the repository, so a Python or Node service cannot enqueue tasks through a supported API.

Asynq against RabbitMQ, Kafka and River

The difference against RabbitMQ and Kafka is where the state lives. Both are separate brokers with their own protocols, operational tooling and clustering models. Asynq stores tasks in Redis, which most Go services already run for caching or sessions, so adoption is a dependency and a schema rather than a new deployment. The cost is that you inherit Redis's durability characteristics and its failure modes for your queue.

Against River, another Go job queue, the split is the backing store rather than the language. River targets PostgreSQL, so a team that already runs Postgres and wants job state in the same database and the same transactions gets a different set of guarantees from Asynq's Redis-backed model. Which is better depends on whether your operational centre of gravity is Redis or Postgres, not on the queue API.

Against Machinery, the comparison people search for most often, the practical difference is scope. Asynq is a library plus a CLI and UI with a documented pause command and a Prometheus integration; Machinery is a broader task framework. The README does not compare itself to any of these projects, so treat the choice as an infrastructure decision first.

Maintenance, upgrades and the MIT licence

The repository is not archived, and the last push was on 2026-06-22. The most recent release is v0.26.0 from 2026-02-03. go.mod declares Go 1.24.0 and pins github.com/redis/go-redis/v9 v9.14.1, github.com/robfig/cron/v3 v3.0.1 and github.com/google/uuid v1.6.0, among others. Those pins are the upgrade surface: a Redis client major version or a Go toolchain bump can force changes in your own module.

The upgrade cost is dominated by the v0.x API policy. Because breaking changes can land without a major version increment, pinning a specific version in go.mod and reading CHANGELOG.md before bumping is the realistic practice. The Makefile shows the maintenance workflow is small: a proto target that regenerates internal/proto/asynq.proto with protoc, and a lint target that runs golangci-lint.

The licence is MIT, which permits commercial and closed-source use with the licence and copyright notice retained. That is a permissive arrangement, but it is not legal advice; check how your organisation handles third-party notices.

Editorial conclusion

Adopt Asynq if you are writing Go services, already operate Redis 4.0 or newer, and want retries, scheduling and a web UI without a separate broker. Do not adopt it if you need a stable v1 API, if your Redis deployment is a cluster, or if your workers are not written in Go, since the queue is a Go library and other languages would need to speak its Redis layout directly. Before committing, verify three things in your own environment: that your Redis topology is a single instance or Sentinel (the README warns some Lua scripts may not work with Redis Cluster), that your team accepts a v0.x public API that can change without a major version bump, and that at-least-once delivery is acceptable for your handlers, because the README promises at least one execution of a task, not exactly one.

Frequently asked questions

What is an async queue, and what does Asynq do?

Asynq is a Go library for queueing tasks and processing them asynchronously with workers, backed by Redis. The README describes it as designed to be scalable yet easy to get started, with a client that enqueues tasks and a server that runs a worker goroutine per task.

How does asynq compare with Machinery?

The README does not compare the two. From the repository alone, Asynq is a Go library backed by Redis that ships a CLI and a web UI, while no equivalent description of Machinery appears here, so the decision has to rest on your own evaluation of both.

How does asynq compare with Temporal?

The repository does not mention Temporal, so no comparison can be drawn from it. What Asynq does document is a Redis-backed task queue with at-least-once execution, retries, scheduling and periodic tasks.

How does asynq compare with BullMQ?

The repository does not mention BullMQ, so no comparison can be drawn from it. Asynq's own documentation describes a Go library backed by Redis with a client, a worker server, a CLI and a web UI.

How does asynq compare with Kafka?

The repository does not mention Kafka. What can be said from the documentation is that Asynq stores tasks in Redis and runs them through Go worker pools, whereas Kafka is a separate broker that Asynq's README does not discuss.

How does asynq compare with NATS?

The repository does not mention NATS, so no comparison can be drawn from it. The README only describes Asynq's own model: a Redis-backed queue with at-least-once execution and configurable retries.

Official sources

  1. hibiken/asynq on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hibiken-asynq.svg)](https://hysenlabs.com/projects/hibiken-asynq)