Fireworq: A Single-Binary Job Queue Built on MySQL
Fireworq is a lightweight, high-performance, language-independent job queue system.
At a glance
- What is it?
- Fireworq gives any language HTTP access to a MySQL-backed job queue, with one dispatcher per queue, delayed jobs, retries and primary/backup nodes.
- Who is it for?
- Fireworq makes a small set of decisions that keep the whole system understandable: HTTP as the only integration surface, MySQL as the system of record, and exactly one dispatcher per queue. A PHP billing service and a Go image processor can push work into the same `foo` category without sharing a client library, and an operator can rebuild the deployment from the Dockerfile alone.
- Can I use it commercially?
- Yes. Apache-2.0 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 19 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 October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Where Fireworq sits in the queue landscape
Fireworq describes itself as a lightweight, high-performance job queue that is language independent, and the README is specific about what that means. It works with a single binary and no external dependencies, it is built on top of an RDBMS so jobs survive a process crash, it supports primary and backup nodes, and it runs one dispatcher per queue that fans work out to workers over HTTP.
Those five claims, portability, reliability, availability, scalability and flexibility, read like a checklist rather than a feature list, and the project layout backs each one up. `dispatcher/` is a separate package from `jobqueue/` and `repository/`, `web/` holds the API surface, and `service/` sits between them. The repository is Go with a `go 1.17` directive, and the dependency list is short enough to read in one pass, with the MySQL driver, `gorilla/mux` for routing and `zerolog` for structured output.
The worker contract is the whole integration surface
Because Fireworq is language independent by design, the only thing your code has to agree on is HTTP. A worker is any web server that accepts a `POST` with a body, typically a JSON value, and answers with a JSON result. The README walks through the shape of both sides:
POST /work HTTP/1.1
Host: localhost:3000
{"id":12345}HTTP/1.1 200 OK
{"status":"success","message":"It's working!"}The `status` field carries the decision that matters, and it has exactly three meaningful values. `"success"` means the job finished, `"failure"` means it failed and may be retried, and `"permanent-failure"` means it failed and must not be retried. Any other value counts as a failure, which is a useful default, since a worker that returns a malformed body stops the retry loop rather than silently marking work done. The HTTP status code is ignored entirely, which removes a familiar source of disagreement about whether a 500 with a success body counts as a success.
Enqueuing a job and reading the result
Pushing work is a single request. You name a worker URL and a payload, and Fireworq takes responsibility for holding the job until a dispatcher can send it:
$ curl -XPOST -d '{"url":"http://172.17.0.1:3000/work","payload":{"id":12345}}' http://localhost:8080/job/fooThe path segment after `/job/` is the category, which is how routing works. A category mapped by the routing API goes to its own queue, and anything unmapped goes to the queue named by `FIREWORQ_QUEUE_DEFAULT`, or fails if no default is configured. Once the worker replies, the daemon logs a single structured line that carries almost every field an operator would want:
{"level":"info","time":1507128673123,"tag":"fireworq.dev","action":"complete","queue":"default","category":"foo","id":2,"status":"completed","created_at":1507128673025,"elapsed":98,"url":"http://172.17.0.1:3000/work","payload":"{\"id\":12345}","next_try":1507128673025,"retry_count":0,"retry_delay":0,"fail_count":0,"timeout":0,"message":"It's working!"}The `elapsed` figure next to `created_at` is the clearest signal about how much queue latency your architecture adds, and `retry_count` and `fail_count` sitting beside it tell you whether a slow worker or a broken one is the problem.
Storage, drivers and what happens when a node dies
The demo path is a single container with an in-memory driver, and the README says plainly that it is not for production use. It exists so you can play with the system without standing up a database first:
$ docker run -p 8080:8080 fireworq/fireworq --driver=in-memory --queue-default=defaultFor anything real, the DSN is the mandatory piece of configuration. `FIREWORQ_MYSQL_DSN` takes a `user:password@tcp(mysql_host:mysql_port)/database?options` string and covers both the job queue database and the repository database. Because the durability story is delegated to MySQL, the reliability guidance is ordinary database advice: use replication, and Fireworq inherits whatever your failover setup gives you.
Availability is handled a level up. Nodes run as one primary with the rest as backups, and a backup takes over automatically when the primary stops. The practical consequence is that running more than one Fireworq instance is a normal production topology rather than a scaling trick, since dispatch is coordinated rather than duplicated.
Queues, delayed jobs and configuration
Queues are the unit of operational separation. The README suggests the shape directly: one low priority queue feeding a small number of high latency workers, and one high priority queue in front of many fast ones. Since a job's queue comes from its category, that split is configuration work rather than code work, and it can change after jobs are already in flight.
Configuration arrives as environment variables set when the daemon starts. Beyond the DSN and the default queue name, `FIREWORQ_QUEUE_DEFAULT_POLLING_INTERVAL` controls how often a dispatcher looks for work, which is the knob you reach for when you are trading latency against database load. Delays and retry limits are set per job at enqueue time, so a batch import can ask for a backoff while an interactive request runs immediately. The README is careful to point at a generated configuration document for the full list rather than pretend the highlights are complete, and that document is regenerated as part of the build.
${GO} run script/gendoc/gendoc.go config > doc/config.mdBuild, packaging and the operational surface
The Makefile shows how a release is produced, and the fact that the release target sets `CGO_ENABLED=0` explains the portability claim: the result is a static binary with no shared library expectations. The `release` target depends on `clean credits generate`, so generated assets and the credits file are rebuilt from source rather than committed as opaque blobs.
The Dockerfile is two stages, compiling on `golang:1.17.8` and shipping on `alpine:3.15.4`, and it sets `FIREWORQ_BIND 0.0.0.0:8080` before exposing 8080. That is the entire deployment story for a container: one process, one port, one DSN.
Inspection is where the project draws a deliberate line. Fireworq ships inspection endpoints, but the README is explicit that they suit machine monitoring rather than humans, and points to Fireworqonsole for queue statistics, running and failed job inspection, and queue and routing definitions. The last commit recorded on the repository is dated September 18, 2026, so the tree is still moving.
Editorial conclusion
Fireworq makes a small set of decisions that keep the whole system understandable: HTTP as the only integration surface, MySQL as the system of record, and exactly one dispatcher per queue. A PHP billing service and a Go image processor can push work into the same `foo` category without sharing a client library, and an operator can rebuild the deployment from the Dockerfile alone. The cost of that simplicity is a specific set of tradeoffs worth knowing before you commit: the worker contract puts retry semantics in the payload response rather than in a header, storage drivers other than MySQL exist but the DSN is mandatory for a manual setup, and capacity planning stops at the queue boundary because worker scaling is left to a load balancer.
Frequently asked questions
Does Fireworq need a message broker like Redis or RabbitMQ?
No. Fireworq stores jobs in MySQL through a DSN you provide, and the shipped binary has no external dependencies, so the database is the only infrastructure component you manage yourself.
How does a worker tell Fireworq whether to retry a job?
The worker returns a JSON body with a status field set to success, failure or permanent-failure. Failure allows a retry and permanent-failure stops it, and any other value is treated as a failure.
Can a Python service push jobs even though Fireworq is written in Go?
Yes. Workers and producers only exchange HTTP, so any language with an HTTP client can enqueue a job by posting a worker URL and a payload, and the in-memory driver lets you try that locally with one container.
What happens if the Fireworq process dies while jobs are queued?
With the MySQL driver, queued jobs live in the database rather than in process memory, so they outlive a crash. Node availability is handled separately, with backup nodes taking over when the primary stops.
Official sources
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.
[](https://hysenlabs.com/projects/fireworq-fireworq)