goworker: a Go worker that reads Resque queues written by Ruby
goworker is a Go-based background worker that runs 10 to 100,000* times faster than Ruby-based workers.
At a glance
- What is it?
- It speaks Resque's wire format exactly, so a Ruby app can enqueue work and a Go process drains it. What that buys you, and what the repo's own metadata will not tell you.
- Who is it for?
- goworker solves one specific problem well: you have a Ruby application that already enqueues background work through Resque, and you want the resource-intensive jobs to run somewhere faster without rewriting the producer. Because it is compatible at the Redis protocol level rather than by wrapping a Ruby runtime, you can run Go workers and Ruby workers side by side, which the README presents as the intended operating model rather than a migration endpoint.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 27 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 20, 2026, and from our analysis. They are not legal advice.
Star history
- benmanns/goworker2,847 ★ · -1
| Repository | 2026-10-08 | 2026-10-20 |
|---|---|---|
| benmanns/goworker | 2.8k | 2.8k |
-1 Stars in the last 12 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Compatibility at the Redis level, not the Ruby level
The framing that makes goworker interesting is in its second paragraph: it is Resque-compatible, which lets you push jobs from an expressive language like Ruby while the work itself runs in Go. That is not a Ruby runtime embedded in Go. It is a Go process that speaks the same Redis key layout Resque uses, so a Ruby application enqueues with `Resque.enqueue` and a Go process pops the same jobs.
The README makes the shared-queue arrangement explicit: goworker workers can run alongside Ruby Resque clients, so you can keep all but your most resource-intensive jobs in Ruby. That is a migration you can do one queue at a time rather than all at once, which is the actual reason to reach for a project like this instead of writing your own consumer.
You can see the wire format for yourself in the testing section, which uses `redis-cli` to push work directly into a Resque queue key:
redis-cli -r 100 RPUSH resque:queue:myqueue '{"class":"MyClass","args":["hi","there"]}'That command is stated to be equivalent to a hundred iterations of `Resque.enqueue MyClass, ['hi', 'there']` in Ruby. The `resque:` prefix and the `queue:` segment are the same defaults the flags describe, so the namespace is negotiable but the shape is fixed. There is also a Go-side `goworker.Enqueue` taking a `Job` with a `Payload` of a class name and an args slice, for producers that are already in Go and do not want to hand-assemble JSON.
The function signature is the whole plugin system
A worker is any function matching `func(string, ...interface{}) error`. It receives the queue it is serving and the job arguments, and returns an error if the job failed. Registering one is a single call in an `init` function:
import "github.com/benmanns/goworker"func myFunc(queue string, args ...interface{}) error {
fmt.Printf("From %s, %v\n", queue, args)
return nil
}The `init` placement is deliberate and worth copying. Registration happens before `main` runs, so by the time the worker loop starts the class name from the job payload is already bound to a function. The class name in the Redis payload maps to the first argument you passed to `Register`, which is why the enqueue example above uses the string `MyClass`.
The `...interface{}` argument type is where the sharp edges are. Because Resque serialises to JSON, integers come back as `json.Number` rather than `int`, and the README spends a full section on the type assertions needed to convert them, including the `UseNumber` setting that changes this behaviour. The idiomatic path is to assert each argument, check the boolean, and return an error parameter if the assertion fails. It is verbose, and it is the price of a signature that can accept any Ruby caller without a schema.
For workers that need to share a database pool, the README recommends a closure: a factory function that takes a connection string, builds the resource, and returns a closure with the matching signature. That keeps one pool per process rather than one per job.
Flags, and why there is no default queue
The flag documentation is the most useful part of the README because it explains the reasoning rather than just listing switches. `-queues` is the only required one, and the reason it has no default is a failure mode worth understanding: if goworker and Resque share a queue and the Ruby side enqueues a class that has no Go counterpart, the Go worker claims that job and fails it. Rather than default to anything, the flag is mandatory, and there is no wildcard equivalent to Resque's `*`.
The README also recommends weighting, so `-queues='high=2,low=1'` checks the high-priority queue twice as often as the low one. That is the whole priority mechanism, and it is a polling frequency rather than a strict ordering guarantee.
The rest of the flags:
- `-interval=5.0`, the wait between polls when the queue came back empty - `-concurrency=25`, how many jobs run at once, documented as tunable from 1 up to 100,000 - `-connections=2`, the ceiling on Redis connections shared between the poller and all workers - `-uri=redis://localhost:6379/`, accepting both `redis://user:pass@host:port/db` and `unix://` socket forms, and readable from `$REDIS_PROVIDER` or `$REDIS_URL` - `-namespace=resque:`, the prefix for both job keys and worker stats - `-exit-on-complete=false`, which exits when the queues drain, described as useful with `time` for benchmarking configurations
The `-connections` explanation is the kind of detail that saves an incident. Two connections is near optimal, one carries a penalty, and the flag exists for managed Redis plans that cap `maxclients`.
One inconsistency to flag: the flag section tells you to set your own flags before calling `goworker.Main()`, while every code example in the same README calls `goworker.Work()`. At least one of the two names is out of date, so check the godoc before writing code against either.
Shutdown and the job that gets lost
The failure-modes section is unusually honest for a worker library, and you should read it before deploying anything on top of this.
On a clean stop, you send `QUIT`, `TERM` or `INT`. Polling stops immediately, but up to `$CONCURRENCY` jobs are already running and will be allowed to finish. That is the behaviour you want from a graceful shutdown.
The sharp edge is the unclean stop. If the process is `KILL`ed or the machine fails, one job sitting in the poller's in-memory buffer can be lost with no representation left in the queue. The consequence is stated plainly: goworker makes no guarantees about job safety across a process shutdown, and workers must be both idempotent and tolerant of job loss. In practice that means every job you enqueue must be safe to run twice and safe to never run at all.
On platforms that send `TERM` and then `KILL` after a grace period, the README notes Heroku's ten second window specifically. Ten seconds is the budget you have for in-flight jobs to complete, which is a strong argument for keeping individual jobs short or for choosing a queue with real visibility timeouts instead.
What the repository metadata does and does not tell you
This is where the project's public signals get thin, and it is worth being specific rather than vague.
The repository carries no topics at all. For a package with this many stars that is unusual, and it means there is nothing for a topic search or a topic-based recommender to match against. Combined with a name as generic as goworker, the practical effect is that the project is hard to find by search. The phrase that dominates results for the name is the ordinary English job title, not the library.
There are no releases and no tags. With 2,849 stars and 242 forks, that means there is no version history to pin against and no changelog to read before upgrading. `go get` gives you whatever the default branch holds on the day you run it. If you need a stable reference point, pin a commit hash and treat upgrades as a deliberate, tested event.
The declared Go version is `go 1.14`. For a repository whose last push was on 2026-09-26, that is a long way behind current toolchains. It also tells you something useful about maintenance: the code has kept building across many Go releases without needing the directive bumped, which is a decent signal of conservatism and a mediocre one of currency.
Licensing is the last loose end. There is a `LICENSE` file at the root of the tree, but the repository metadata reports the licence as unrecognised rather than naming a specific licence. The file's actual contents are what bind you, so read it directly rather than relying on the badge.
Where it fits
The honest case for goworker is narrow and specific. You run a Ruby application on Resque. You have identified specific jobs, usually CPU-bound ones, that are slow enough in Ruby to matter. You want those jobs to run in Go while the rest of your Ruby workers keep running unchanged. You accept that your job functions must be idempotent and loss-tolerant, because the library tells you they have to be.
The performance claim on the repository page is that goworker runs 10 to 100,000 times faster than Ruby-based workers. That range spans four orders of magnitude, which tells you the number depends entirely on the job: a Ruby process that spends its time in the VM rather than in your code will show a small difference, and a tight numeric loop will show a large one. Treat the figure as a direction, not a promise for your workload, and measure with `-exit-on-complete` under `time` on a job you already understand.
What goworker is not is a general-purpose Go job queue. It exists to consume Resque-format queues. If you are starting fresh with no Ruby, you want a queue designed for Go, and this library adds a compatibility constraint you did not otherwise have. The dependency list reinforces the vintage: alongside `redigo` for Redis and `seelog` for logging, it pulls in a pinned `vitess.io/vitess` release candidate from 2018.
Read it as an interop tool. That is what it is good at, and what it was built for.
Editorial conclusion
goworker solves one specific problem well: you have a Ruby application that already enqueues background work through Resque, and you want the resource-intensive jobs to run somewhere faster without rewriting the producer. Because it is compatible at the Redis protocol level rather than by wrapping a Ruby runtime, you can run Go workers and Ruby workers side by side, which the README presents as the intended operating model rather than a migration endpoint. The things to weigh before adopting it are unglamorous. There are no releases and no tags despite nearly 2,900 stars, the module still declares Go 1.14, and the repository carries no topics at all. None of that means the code is bad. It means version pinning and upgrade planning are your problem rather than the maintainer's.
Frequently asked questions
What is goworker and what does Resque-compatible mean?
It is a Go background worker that consumes the same Redis queue keys Resque uses, rather than embedding a Ruby runtime. A Ruby application can keep enqueuing with Resque while a separate Go process drains the jobs, and both kinds of worker can run at the same time. Compatibility happens at the Redis protocol level, not by calling into Ruby.
How do I register a job handler in goworker?
Write a function with the signature func(queue string, args ...interface{}) error and pass it to goworker.Register from an init function, using the string that matches the class name in the enqueued payload. Registering in init means the binding exists before the worker loop starts. For handlers that need shared resources such as a database pool, return a closure from a factory function with the same signature.
Why does goworker require the -queues flag with no default?
Because a shared queue is a failure source. If a goworker and a Resque worker consume the same queue and the Ruby side enqueues a class with no Go counterpart, the Go process claims it and fails it. Requiring the flag forces you to name queues deliberately, and there is no wildcard equivalent to Resque's * queue. Weights let you poll a high priority queue more often than a low one.
Are goworker jobs guaranteed to run?
No, and the documentation says so directly. A clean shutdown via QUIT, TERM or INT stops polling and lets running jobs finish, but a KILL or system failure can lose one job sitting in the poller's buffer with no trace left in the queue. Job functions must be idempotent and tolerant of loss. On platforms that send TERM then KILL, such as Heroku, you have about ten seconds for in-flight jobs.
How do I choose a concurrency setting for goworker?
The default is 25 and the README describes a usable range from 1 up to 100,000, tuned against your workflow and the availability of whatever the jobs talk to. Keep the Redis connection count separate, since -connections caps the total shared by the poller and all workers and defaults to 2. Raising concurrency far above the number of useful downstream connections mostly adds waiting.
Does goworker have versioned releases I can pin to?
The repository lists no releases and no tags, so go get returns whatever the default branch holds at the time you run it. The module also declares go 1.14 even though the last push was in September 2026. If you need a stable upgrade path, pin a commit hash and test upgrades deliberately rather than tracking a version number.
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/benmanns-goworker)Star history chart for your README
[](https://hysenlabs.com/projects/benmanns-goworker)