Running it
What to look at when something is wrong, and what gotick leaves behind in Redis.
The inspector
A built-in web UI: which calls are running, which step each one sits on, how long each took, what the error was, and how much of a sleep is left. It's a plain http.Handler that reads Redis directly, so it never has to reach a running worker — and because it lives in its own sub-package, never importing it costs your binary nothing at all. It can see every metadata value you passed in, so put authentication in front of it.
Folds into a service you already ship, and reuses the auth middleware you already have. Least work.
h, _ := ui.NewHandler(ui.Options{ RootPath: "/_gotick", Store: store.NewRedisStore(rdb), Auth: myAuthMiddleware,})mux.Handle("/_gotick/", h)For a process with no HTTP server of its own, like net/http/pprof. Binding anywhere but loopback without credentials is refused rather than warned about.
go ui.ListenAndServe("127.0.0.1:6060", ui.Options{ Store: store.NewRedisStore(rdb),})Production is misbehaving: change no code, deploy nothing, point the CLI at Redis and look.
go run github.com/zbysir/gotick/cmd/gotick@latest ui \ -redis redis://localhost:6379/0From the command line
gotick inspect prints one call's steps — status, retries, next run time, last error — and the metadata. It reads Redis too, so a stuck flow can be diagnosed without touching the running service. There's also a published container image if you'd rather run the inspector as its own service.
$ gotick inspect -redis redis://localhost:6379/0 9f2ac41b8e7d callId 9f2ac41b8e7dtasks 3 TASK STATUS RETRY RUN AT LAST ERRORsend-email retry 1 2026-08-18T09:12:06Z smtp timeoutstart done 0 - -wait sleep 0 2026-08-18T09:12:03Z - metadata order_id = ORD-1What is stored, and for how long
Everything lives in the Redis you pointed gotick at. Cleanup is automatic; the numbers below are the defaults.
| Key | What's in it | Lifetime |
|---|---|---|
| <callId>_status | One field per step: status, retries, timings, last error | 7 days after the call reaches a terminal state |
| <callId>_meta | Your metadata, memoised values, delivered signals | 7 days after the call reaches a terminal state |
| gotick:run:<callId> | The run summary the inspector lists | 7 day TTL |
| gotick:runs, gotick:flow:<id>:runs | Sorted sets indexing runs by start time | Trimmed past the retention window, at most once every 10 minutes |
| gotick:flows | The list of flow ids that have ever run | Kept — its size is bounded by how many flows you have |
| gotick:keys:<flowId> | Your business key to callId, one hash per flow | Each entry removed when its call finishes |
| asynq:* | asynq's own queues and stats | Successful tasks aren't kept; ones that exhaust retries are archived 90 days |
Configuration
| Field | What it does |
|---|---|
| RedisURL / RedisClient | Where Redis is. Pass a client of your own to reuse an existing pool. |
| Queue | Queue namespace, default gotick. Change it to isolate one group of flows from another. |
| Concurrency | How many tasks one worker runs at once. Default 10. |
| DelayedTaskCheckInterval | How often scheduled work is promoted to runnable — the real precision of every Sleep and every retry backoff. Default 500ms. Raising it saves Redis polling at the cost of looser wake-ups. |
| TaskCheckInterval | How often an idle queue is checked for new work. Default 100ms. |
| ConsumeLegacyPerFlowQueues | Upgrading from the old queue-per-flow layout: turn it on so workers also drain the old queues, then turn it off once they're empty. |