{"id":1713,"date":"2026-09-23T12:34:18","date_gmt":"2026-09-23T15:34:18","guid":{"rendered":"https:\/\/www.linuxpro.com.br\/?p=1713"},"modified":"2026-09-23T12:34:18","modified_gmt":"2026-09-23T15:34:18","slug":"asynq-filas-de-tarefas-em-go-com-redis-e-asynqmon","status":"publish","type":"post","link":"https:\/\/www.linuxpro.com.br\/en\/2026\/09\/asynq-filas-de-tarefas-em-go-com-redis-e-asynqmon\/","title":{"rendered":"Asynq: task queues in Go with Redis and Asynqmon"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/asynq-filas-de-tarefas-go-v1.webp\" alt=\"Mascote LinuxPro coloca um envelope numa esteira que sai de um cilindro vermelho do Redis sob o logo do Asynq, enquanto o Gopher do Go recebe as tarefas e o cachorro caramelo observa\" width=\"1486\" height=\"856\" \/><\/p>\n<p>Todo sistema web chega ao ponto em que o usu\u00e1rio n\u00e3o pode esperar: o e-mail de boas-vindas, a miniatura da foto, o relat\u00f3rio que leva dois minutos para gerar. Se isso roda dentro da requisi\u00e7\u00e3o HTTP, a p\u00e1gina trava, o timeout estoura e uma falha no SMTP vira erro 500 na cara do cliente. A sa\u00edda \u00e9 uma fila de tarefas: a API registra o trabalho e responde na hora, e processos separados executam o servi\u00e7o em segundo plano, com nova tentativa quando algo falha. No mundo Go, a biblioteca mais usada para isso \u00e9 o <strong>Asynq<\/strong>, que usa o Redis como broker e vem com um painel web, o <strong>Asynqmon<\/strong>. Este guia monta um exemplo completo, testado de verdade contra Redis e Valkey em cont\u00eaineres, e termina com deploy em systemd e m\u00e9tricas no Prometheus.<\/p>\n<h2>O que \u00e9 o Asynq<\/h2>\n<p>O <a href=\"https:\/\/github.com\/hibiken\/asynq\">Asynq<\/a> \u00e9 uma biblioteca Go, licen\u00e7a MIT, criada por Ken Hibino. O modelo \u00e9 simples:<\/p>\n<ul>\n<li>o <strong>cliente<\/strong> (<code>asynq.Client<\/code>) grava a tarefa numa fila no Redis;<\/li>\n<li>o <strong>servidor<\/strong> (<code>asynq.Server<\/code>) puxa tarefas das filas e abre uma goroutine por tarefa, at\u00e9 o limite de <code>Concurrency<\/code>;<\/li>\n<li>um <strong>ServeMux<\/strong> roteia cada tarefa pelo tipo (<code>email:boas-vindas<\/code>, <code>imagem:miniatura<\/code>), do mesmo jeito que o <code>net\/http<\/code> roteia URLs.<\/li>\n<\/ul>\n<p>Uma tarefa \u00e9 s\u00f3 um tipo (string) mais um payload em bytes, normalmente JSON. Como o estado inteiro fica no Redis, voc\u00ea escala rodando mais c\u00f3pias do worker em outras m\u00e1quinas, sem coordena\u00e7\u00e3o extra. Se voc\u00ea est\u00e1 chegando agora na linguagem, vale ler <a href=\"\/2026\/09\/a-historia-da-linguagem-go\/\">a hist\u00f3ria da linguagem Go<\/a> e o guia de <a href=\"\/2017\/05\/instalando-golang-no-linux\/\">instala\u00e7\u00e3o do Go no Linux<\/a>.<\/p>\n<h2>Arquitetura e recursos<\/h2>\n<p>O que o Asynq entrega pronto, com o nome da op\u00e7\u00e3o na API:<\/p>\n<ul>\n<li><strong>Entrega pelo menos uma vez<\/strong> (<em>at-least-once<\/em>): se o worker morre no meio, a tarefa volta para a fila. Por isso o handler precisa ser idempotente.<\/li>\n<li><strong>Filas com prioridade<\/strong>: <code>Queues: map[string]int{\"critical\": 6, \"default\": 3, \"low\": 1}<\/code> divide o tempo em 60\/30\/10% quando todas t\u00eam trabalho (<a href=\"https:\/\/github.com\/hibiken\/asynq\/wiki\/Queue-Priority\">prioridade ponderada<\/a>). Com <code>StrictPriority: true<\/code>, a fila de menor prioridade s\u00f3 anda quando as de cima est\u00e3o vazias.<\/li>\n<li><strong>Retries com backoff<\/strong>: o padr\u00e3o \u00e9 <code>MaxRetry<\/code> 25 e atraso exponencial (a f\u00f3rmula do Sidekiq, n<sup>4<\/sup> + 15 s + um valor aleat\u00f3rio). Tudo ajust\u00e1vel por tarefa e por <code>RetryDelayFunc<\/code> (<a href=\"https:\/\/github.com\/hibiken\/asynq\/wiki\/Task-Retry\">wiki: Task Retry<\/a>). Um erro embrulhado com <code>asynq.SkipRetry<\/code> pula as tentativas.<\/li>\n<li><strong>Tarefas agendadas<\/strong>: <code>ProcessIn(30*time.Second)<\/code> ou <code>ProcessAt(t)<\/code> deixam a tarefa no estado <code>scheduled<\/code> at\u00e9 a hora.<\/li>\n<li><strong>Tarefas peri\u00f3dicas<\/strong>: o <code>asynq.Scheduler<\/code> enfileira tarefas por express\u00e3o cron ou <code>@every<\/code> (<a href=\"https:\/\/github.com\/hibiken\/asynq\/wiki\/Periodic-Tasks\">wiki: Periodic Tasks<\/a>).<\/li>\n<li><strong>Deduplica\u00e7\u00e3o<\/strong>: <code>Unique(ttl)<\/code> recusa uma segunda tarefa com o mesmo tipo, payload e fila enquanto a primeira n\u00e3o for processada com sucesso ou o TTL n\u00e3o expirar, e devolve <code>ErrDuplicateTask<\/code>. <code>TaskID(\"...\")<\/code> d\u00e1 um ID fixo \u00e0 tarefa e recusa repeti\u00e7\u00e3o com <code>ErrTaskIDConflict<\/code> (<a href=\"https:\/\/github.com\/hibiken\/asynq\/wiki\/Unique-Tasks\">wiki: Unique Tasks<\/a>).<\/li>\n<li><strong>Agrega\u00e7\u00e3o em grupos<\/strong>: tarefas enfileiradas com <code>Group(\"nome\")<\/code> ficam em <code>aggregating<\/code> e um <code>GroupAggregator<\/code> as junta numa s\u00f3, controlado por <code>GroupGracePeriod<\/code>, <code>GroupMaxDelay<\/code> e <code>GroupMaxSize<\/code>. Serve, por exemplo, para mandar um \u00fanico e-mail com dez notifica\u00e7\u00f5es (<a href=\"https:\/\/github.com\/hibiken\/asynq\/wiki\/Task-aggregation\">wiki: Task aggregation<\/a>).<\/li>\n<li><strong>Timeout e deadline<\/strong>: <code>Timeout(d)<\/code> (padr\u00e3o de 30 minutos) e <code>Deadline(t)<\/code> cancelam o <code>context.Context<\/code> do handler (<a href=\"https:\/\/github.com\/hibiken\/asynq\/wiki\/Task-Timeout-and-Cancelation\">wiki: Timeout and Cancelation<\/a>).<\/li>\n<li><strong>Tarefas arquivadas<\/strong>: quem esgota as tentativas ou devolve <code>SkipRetry<\/code> vai para <code>archived<\/code>, onde fica dispon\u00edvel para inspe\u00e7\u00e3o e reprocessamento manual pela CLI ou pelo painel.<\/li>\n<li><strong>Reten\u00e7\u00e3o<\/strong>: <code>Retention(24*time.Hour)<\/code> guarda a tarefa conclu\u00edda como <code>completed<\/code>, \u00fatil para auditoria.<\/li>\n<\/ul>\n<p>O caminho de uma tarefa, do produtor at\u00e9 o painel:<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/asynq-fluxo-tarefas.webp\" alt=\"Diagrama do fluxo do Asynq: produtor e agendador enfileiram no Redis\/Valkey, workers consomem as filas por prioridade, falhas voltam como retry, Asynqmon e CLI inspecionam e o Prometheus raspa as m\u00e9tricas\" width=\"1200\" height=\"700\" \/><\/figure>\n<h2>Situa\u00e7\u00e3o atual dos projetos<\/h2>\n<p>Antes de colocar uma depend\u00eancia em produ\u00e7\u00e3o, olhe para ela. Conferido no GitHub em 23 de setembro de 2026:<\/p>\n<ul>\n<li><strong>Asynq<\/strong>: \u00faltima vers\u00e3o <a href=\"https:\/\/github.com\/hibiken\/asynq\/releases\/tag\/v0.26.0\">v0.26.0<\/a>, de 3 de fevereiro de 2026. Ela subiu o m\u00ednimo para <strong>Go 1.24<\/strong> e trouxe headers nas tarefas, <code>--tls<\/code> no <code>asynq dash<\/code>, usu\u00e1rio de ACL do Redis na CLI (<code>--username<\/code>) e <code>UpdateTaskPayload<\/code> no Inspector. O branch <code>master<\/code> tem 25 commits al\u00e9m da tag, com o \u00faltimo em 12 de junho de 2026 (entre eles um <code>BatchEnqueue<\/code> ainda sem release). O reposit\u00f3rio tem cerca de 13,7 mil estrelas e mais de 290 issues abertas. O README diz que o projeto \u00e9 &#8220;relativamente est\u00e1vel&#8221; e segue em vers\u00e3o <code>v0.x<\/code>: a API p\u00fablica ainda pode quebrar entre vers\u00f5es menores.<\/li>\n<li><strong>Go<\/strong>: o README promete suporte \u00e0s duas \u00faltimas vers\u00f5es do Go, e a CI testa 1.24.x e 1.25.x contra <code>redis:7<\/code>. Eu compilei com Go 1.27.1 sem ajuste nenhum.<\/li>\n<li><strong>Redis<\/strong>: o README exige Redis 4.0 ou superior e avisa que alguns scripts Lua podem n\u00e3o ser compat\u00edveis com <strong>Redis Cluster<\/strong>. Redis Sentinel \u00e9 suportado.<\/li>\n<li><strong>Valkey<\/strong>: n\u00e3o h\u00e1 declara\u00e7\u00e3o oficial de suporte. A <a href=\"https:\/\/github.com\/hibiken\/asynq\/issues\/981\">issue #981<\/a> re\u00fane relatos de uso em produ\u00e7\u00e3o com Valkey (e com Dragonfly, usando <code>--default_lua_flags=allow-undeclared-keys<\/code>), e o pedido de documenta\u00e7\u00e3o (#985) segue aberto. No meu teste abaixo, o exemplo inteiro rodou igual no Valkey 8.1.<\/li>\n<li><strong>Asynqmon<\/strong>: aqui a situa\u00e7\u00e3o \u00e9 pior. A \u00faltima release \u00e9 a <a href=\"https:\/\/github.com\/hibiken\/asynqmon\/releases\/tag\/v0.7.1\">v0.7.1<\/a>, de maio de 2022. O \u00faltimo commit \u00e9 de julho de 2023, e a imagem <code>hibiken\/asynqmon:0.7.2<\/code> (tamb\u00e9m <code>latest<\/code>) no Docker Hub foi publicada no mesmo dia, sem release correspondente no GitHub. O c\u00f3digo depende do Asynq v0.24.1, e a tabela de compatibilidade do README para em &#8220;Asynq 0.23.x \u2194 Asynqmon 0.7.x&#8221;. Na pr\u00e1tica, ele leu e administrou sem erro as filas criadas pelo Asynq v0.26.0 no meu teste, mas trate-o como ferramenta parada no tempo, n\u00e3o como produto mantido.<\/li>\n<\/ul>\n<h2>Laborat\u00f3rio: Redis e Valkey em cont\u00eainer<\/h2>\n<p>Para o teste, subi um Redis 8 e um Valkey 8 numa rede Docker pr\u00f3pria, mais o Asynqmon apontando para o Redis. Se o Docker ainda \u00e9 novidade, comece pela <a href=\"\/2026\/09\/a-historia-do-docker\/\">hist\u00f3ria do Docker<\/a>.<\/p>\n<pre><code class=\"language-bash\">docker network create filas\ndocker run -d --name redis  --network filas -p 127.0.0.1:6379:6379 redis:8-alpine\ndocker run -d --name valkey --network filas -p 127.0.0.1:6380:6379 valkey\/valkey:8-alpine\n\ndocker exec redis redis-server --version\ndocker exec valkey valkey-server --version<\/code><\/pre>\n<pre><code class=\"language-bash\">Redis server v=8.10.2 sha=00000000:1 malloc=jemalloc-5.3.0 bits=64 build=6583f6419e33bdeb\nValkey server v=8.1.8 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=288c3332651d2a23<\/code><\/pre>\n<p>As portas ficam presas em <code>127.0.0.1<\/code>: um Redis sem senha exposto na internet \u00e9 invas\u00e3o garantida.<\/p>\n<h2>O exemplo: m\u00f3dulo Go com produtor, worker e agendador<\/h2>\n<p>O m\u00f3dulo tem tr\u00eas bin\u00e1rios e um pacote compartilhado:<\/p>\n<pre><code class=\"language-bash\">filas\/\n\u251c\u2500\u2500 go.mod\n\u251c\u2500\u2500 tarefas\/tarefas.go      # tipos, payloads e handlers\n\u2514\u2500\u2500 cmd\/\n    \u251c\u2500\u2500 produtor\/main.go    # enfileira as tarefas\n    \u251c\u2500\u2500 worker\/main.go      # asynq.Server + ServeMux\n    \u2514\u2500\u2500 agendador\/main.go   # tarefas peri\u00f3dicas<\/code><\/pre>\n<pre><code class=\"language-bash\">mkdir filas &amp;&amp; cd filas\ngo mod init exemplo.com\/filas\ngo get github.com\/hibiken\/asynq@v0.26.0<\/code><\/pre>\n<h3>Tipos de tarefa e handlers<\/h3>\n<p>Cada tipo ganha uma fun\u00e7\u00e3o que cria a tarefa e um handler que a executa. O handler da miniatura simula um storage inst\u00e1vel: falha nas duas primeiras tentativas e s\u00f3 funciona na terceira. Um arquivo <code>.bmp<\/code> \u00e9 erro permanente e vai direto para o arquivo, sem retry.<\/p>\n<pre><code class=\"language-go\">\/\/ Package tarefas define os tipos de tarefa, os payloads e os handlers.\npackage tarefas\n\nimport (\n\t\"context\"\n\t\"encoding\/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com\/hibiken\/asynq\"\n)\n\n\/\/ Tipos de tarefa: o ServeMux roteia pelo prefixo, como rotas HTTP.\nconst (\n\tTipoEmailBoasVindas = \"email:boas-vindas\"\n\tTipoMiniatura       = \"imagem:miniatura\"\n\tTipoRelatorio       = \"relatorio:diario\"\n)\n\n\/\/ RedisAddr l\u00ea o endere\u00e7o do Redis\/Valkey do ambiente.\nfunc RedisAddr() string {\n\tif a := os.Getenv(\"REDIS_ADDR\"); a != \"\" {\n\t\treturn a\n\t}\n\treturn \"127.0.0.1:6379\"\n}\n\ntype EmailPayload struct {\n\tUsuarioID int    `json:\"usuario_id\"`\n\tEmail     string `json:\"email\"`\n}\n\ntype MiniaturaPayload struct {\n\tOrigem  string `json:\"origem\"`\n\tLargura int    `json:\"largura\"`\n}\n\nfunc NovaTarefaEmail(id int, email string) (*asynq.Task, error) {\n\tp, err := json.Marshal(EmailPayload{UsuarioID: id, Email: email})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t\/\/ Op\u00e7\u00f5es padr\u00e3o da tarefa; podem ser sobrescritas no Enqueue.\n\treturn asynq.NewTask(TipoEmailBoasVindas, p,\n\t\tasynq.Queue(\"critical\"), asynq.MaxRetry(5), asynq.Timeout(30*time.Second)), nil\n}\n\nfunc NovaTarefaMiniatura(origem string, largura int) (*asynq.Task, error) {\n\tp, err := json.Marshal(MiniaturaPayload{Origem: origem, Largura: largura})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn asynq.NewTask(TipoMiniatura, p, asynq.MaxRetry(3), asynq.Timeout(2*time.Minute)), nil\n}\n\nfunc HandleEmail(ctx context.Context, t *asynq.Task) error {\n\tvar p EmailPayload\n\tif err := json.Unmarshal(t.Payload(), &amp;p); err != nil {\n\t\t\/\/ Payload inv\u00e1lido nunca vai dar certo: n\u00e3o adianta tentar de novo.\n\t\treturn fmt.Errorf(\"payload inv\u00e1lido: %v: %w\", err, asynq.SkipRetry)\n\t}\n\tid, _ := asynq.GetTaskID(ctx)\n\tlog.Printf(\"e-mail de boas-vindas para %s (usu\u00e1rio %d, tarefa %s)\", p.Email, p.UsuarioID, id)\n\treturn nil\n}\n\nfunc HandleMiniatura(ctx context.Context, t *asynq.Task) error {\n\tvar p MiniaturaPayload\n\tif err := json.Unmarshal(t.Payload(), &amp;p); err != nil {\n\t\treturn fmt.Errorf(\"payload inv\u00e1lido: %v: %w\", err, asynq.SkipRetry)\n\t}\n\tif strings.HasSuffix(p.Origem, \".bmp\") {\n\t\t\/\/ Erro permanente: vai direto para \"archived\", sem retry.\n\t\treturn fmt.Errorf(\"formato n\u00e3o suportado: %s: %w\", p.Origem, asynq.SkipRetry)\n\t}\n\ttentativa, _ := asynq.GetRetryCount(ctx)\n\tif tentativa &lt; 2 {\n\t\t\/\/ Simula falha transit\u00f3ria (storage fora do ar) nas duas primeiras tentativas.\n\t\treturn fmt.Errorf(\"storage indispon\u00edvel ao ler %s (tentativa %d)\", p.Origem, tentativa+1)\n\t}\n\tselect {\n\tcase &lt;-time.After(3 * time.Second): \/\/ \"trabalho\" pesado\n\tcase &lt;-ctx.Done(): \/\/ timeout, deadline ou desligamento\n\t\treturn ctx.Err()\n\t}\n\tlog.Printf(\"miniatura %dpx gerada para %s na tentativa %d\", p.Largura, p.Origem, tentativa+1)\n\treturn nil\n}\n\nfunc HandleRelatorio(ctx context.Context, t *asynq.Task) error {\n\tlog.Printf(\"relat\u00f3rio di\u00e1rio gerado \u00e0s %s\", time.Now().Format(\"15:04:05\"))\n\treturn nil\n}<\/code><\/pre>\n<h3>Produtor: enfileirar, deduplicar e agendar<\/h3>\n<p>O produtor faz o papel da sua API: grava quatro tarefas. O e-mail \u00e9 enfileirado duas vezes com <code>Unique<\/code> para mostrar a deduplica\u00e7\u00e3o. O banner vai agendado para 30 segundos depois, com ID pr\u00f3prio e reten\u00e7\u00e3o de 24 horas.<\/p>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"time\"\n\n\t\"exemplo.com\/filas\/tarefas\"\n\t\"github.com\/hibiken\/asynq\"\n)\n\nfunc main() {\n\tclient := asynq.NewClient(asynq.RedisClientOpt{Addr: tarefas.RedisAddr()})\n\tdefer client.Close()\n\n\t\/\/ 1) E-mail imediato na fila \"critical\". Unique evita duplicar o envio\n\t\/\/    se o cadastro for reprocessado dentro de 1 hora.\n\tfor i := 0; i &lt; 2; i++ {\n\t\tt, _ := tarefas.NovaTarefaEmail(42, \"ana@exemplo.com.br\")\n\t\tinfo, err := client.Enqueue(t, asynq.Unique(time.Hour))\n\t\tif errors.Is(err, asynq.ErrDuplicateTask) {\n\t\t\tlog.Printf(\"duplicada, ignorada: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Printf(\"enfileirada: id=%s fila=%s tipo=%s\", info.ID, info.Queue, info.Type)\n\t}\n\n\t\/\/ 2) Miniatura na fila \"default\" (falha 2x e \u00e9 reprocessada).\n\tt, _ := tarefas.NovaTarefaMiniatura(\"uploads\/foto-42.jpg\", 320)\n\tinfo, err := client.Enqueue(t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Printf(\"enfileirada: id=%s fila=%s tipo=%s\", info.ID, info.Queue, info.Type)\n\n\t\/\/ 3) Miniatura agendada para daqui a 30 s, na fila \"low\", com ID pr\u00f3prio.\n\tt, _ = tarefas.NovaTarefaMiniatura(\"uploads\/banner.png\", 1200)\n\tinfo, err = client.Enqueue(t, asynq.ProcessIn(30*time.Second),\n\t\tasynq.Queue(\"low\"), asynq.TaskID(\"miniatura-banner\"), asynq.Retention(24*time.Hour))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Printf(\"agendada: id=%s fila=%s estado=%s para=%s\",\n\t\tinfo.ID, info.Queue, info.State, info.NextProcessAt.Format(\"15:04:05\"))\n\n\t\/\/ 4) Erro permanente: termina em \"archived\" sem retry.\n\tt, _ = tarefas.NovaTarefaMiniatura(\"uploads\/antiga.bmp\", 320)\n\tinfo, err = client.Enqueue(t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Printf(\"enfileirada: id=%s fila=%s tipo=%s\", info.ID, info.Queue, info.Type)\n}<\/code><\/pre>\n<h3>Worker com ServeMux, retries e desligamento gracioso<\/h3>\n<p>O <code>RetryDelayFunc<\/code> aqui \u00e9 curto de prop\u00f3sito, para a demonstra\u00e7\u00e3o caber em um minuto. Em produ\u00e7\u00e3o, omita o campo e fique com o backoff exponencial padr\u00e3o. O <code>srv.Run<\/code> trata sozinho os sinais: <code>SIGTERM<\/code> ou <code>SIGINT<\/code> encerram com calma, e <code>SIGTSTP<\/code> para de puxar tarefas novas sem derrubar o processo.<\/p>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"exemplo.com\/filas\/tarefas\"\n\t\"github.com\/hibiken\/asynq\"\n)\n\nfunc main() {\n\tsrv := asynq.NewServer(\n\t\tasynq.RedisClientOpt{Addr: tarefas.RedisAddr()},\n\t\tasynq.Config{\n\t\t\tConcurrency: 10,\n\t\t\t\/\/ Prioridade ponderada: 60% critical, 30% default, 10% low.\n\t\t\tQueues: map[string]int{\"critical\": 6, \"default\": 3, \"low\": 1},\n\t\t\t\/\/ Backoff curto s\u00f3 para a demonstra\u00e7\u00e3o: 5 s, 10 s, 15 s...\n\t\t\t\/\/ Em produ\u00e7\u00e3o, omita e use o exponencial padr\u00e3o.\n\t\t\tRetryDelayFunc: func(n int, err error, t *asynq.Task) time.Duration {\n\t\t\t\treturn time.Duration(n+1) * 5 * time.Second\n\t\t\t},\n\t\t\tErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, t *asynq.Task, err error) {\n\t\t\t\tn, _ := asynq.GetRetryCount(ctx)\n\t\t\t\tmax, _ := asynq.GetMaxRetry(ctx)\n\t\t\t\tlog.Printf(\"ERRO %s (retry %d\/%d): %v\", t.Type(), n, max, err)\n\t\t\t}),\n\t\t\t\/\/ No SIGTERM, espera at\u00e9 20 s as tarefas em andamento terminarem.\n\t\t\tShutdownTimeout: 20 * time.Second,\n\t\t},\n\t)\n\n\tmux := asynq.NewServeMux()\n\tmux.HandleFunc(tarefas.TipoEmailBoasVindas, tarefas.HandleEmail)\n\tmux.HandleFunc(tarefas.TipoMiniatura, tarefas.HandleMiniatura)\n\tmux.HandleFunc(tarefas.TipoRelatorio, tarefas.HandleRelatorio)\n\n\t\/\/ Run bloqueia at\u00e9 receber SIGTERM\/SIGINT e ent\u00e3o desliga com calma.\n\tif err := srv.Run(mux); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}<\/code><\/pre>\n<h3>Agendador de tarefas peri\u00f3dicas<\/h3>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"exemplo.com\/filas\/tarefas\"\n\t\"github.com\/hibiken\/asynq\"\n)\n\nfunc main() {\n\t\/\/ Sem Location, o Scheduler interpreta o cron em UTC.\n\tfuso, err := time.LoadLocation(\"America\/Sao_Paulo\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ts := asynq.NewScheduler(asynq.RedisClientOpt{Addr: tarefas.RedisAddr()}, &amp;asynq.SchedulerOpts{\n\t\tLocation: fuso,\n\t\tPostEnqueueFunc: func(info *asynq.TaskInfo, err error) {\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"falha ao enfileirar peri\u00f3dica: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Printf(\"peri\u00f3dica enfileirada: %s id=%s\", info.Type, info.ID)\n\t\t},\n\t})\n\n\t\/\/ Sintaxe cron (robfig\/cron): todo dia \u00e0s 03:00...\n\tif _, err := s.Register(\"0 3 * * *\", asynq.NewTask(tarefas.TipoRelatorio, nil), asynq.Queue(\"low\")); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t\/\/ ...ou intervalo fixo, \u00fatil para testar.\n\tid, err := s.Register(\"@every 20s\", asynq.NewTask(tarefas.TipoRelatorio, []byte(`{\"teste\":true}`)), asynq.Queue(\"low\"))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Printf(\"entrada registrada: %s\", id)\n\n\tif err := s.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}<\/code><\/pre>\n<p>Aten\u00e7\u00e3o ao fuso: sem <code>Location<\/code>, o <code>Scheduler<\/code> interpreta o cron em <strong>UTC<\/strong>. No primeiro teste, sem essa op\u00e7\u00e3o, o <code>0 3 * * *<\/code> estava marcado para 11h35 dali em diante, ou seja, meia-noite no hor\u00e1rio de Bras\u00edlia. Com <code>America\/Sao_Paulo<\/code>, o log confirma:<\/p>\n<pre><code class=\"language-bash\">2026\/09\/23 12:24:53 entrada registrada: 4e28eaa1-ddd4-40d5-9dc6-2e96f770e600\nasynq: pid=416391 2026\/09\/23 15:24:53.136229 INFO: Scheduler starting\nasynq: pid=416391 2026\/09\/23 15:24:53.136232 INFO: Scheduler timezone is set to America\/Sao_Paulo\nasynq: pid=416391 2026\/09\/23 15:24:53.136241 INFO: Send signal TERM or INT to stop the scheduler\n2026\/09\/23 12:25:13 peri\u00f3dica enfileirada: relatorio:diario id=217bb335-888a-4148-bdb2-121c19f6d8a8\n2026\/09\/23 12:25:33 peri\u00f3dica enfileirada: relatorio:diario id=9d80f7ea-26c1-4e08-b0ee-124579c81e88\n2026\/09\/23 12:25:53 peri\u00f3dica enfileirada: relatorio:diario id=1e43a446-4fc8-41c7-ab44-6285a8e53203\n2026\/09\/23 12:26:13 peri\u00f3dica enfileirada: relatorio:diario id=399dc3e0-5d5d-4407-b10c-f14ba55c9eb3\nasynq: pid=416391 2026\/09\/23 15:26:17.022259 INFO: Scheduler shutting down\nasynq: pid=416391 2026\/09\/23 15:26:17.022798 INFO: Scheduler stopped<\/code><\/pre>\n<p>S\u00f3 rode <strong>um<\/strong> agendador por ambiente: duas c\u00f3pias enfileiram cada tarefa peri\u00f3dica duas vezes. Os workers, por outro lado, voc\u00ea replica \u00e0 vontade.<\/p>\n<h2>Rodando o exemplo<\/h2>\n<p>Compile, suba o worker e o agendador, pause a fila <code>default<\/code> pela CLI (para ver as tarefas paradas no painel) e rode o produtor:<\/p>\n<pre><code class=\"language-bash\">go build -o bin\/ .\/cmd\/...\nexport REDIS_ADDR=127.0.0.1:6379\n\nbin\/worker &amp;\nbin\/agendador &amp;\nasynq queue pause default\nbin\/produtor<\/code><\/pre>\n<pre><code class=\"language-bash\">2026\/09\/23 12:24:55 enfileirada: id=542783fc-53f6-48c4-bff3-b3ded2e72681 fila=critical tipo=email:boas-vindas\n2026\/09\/23 12:24:55 duplicada, ignorada: task already exists\n2026\/09\/23 12:24:55 enfileirada: id=3b3008d8-9889-497a-aca8-3dd2cab5f84c fila=default tipo=imagem:miniatura\n2026\/09\/23 12:24:55 agendada: id=miniatura-banner fila=low estado=scheduled para=12:25:25\n2026\/09\/23 12:24:55 enfileirada: id=16d93751-6b19-4ca7-84fa-71cc2a20c306 fila=default tipo=imagem:miniatura<\/code><\/pre>\n<p>A segunda chamada com <code>Unique<\/code> voltou <code>task already exists<\/code>, e o banner ficou em <code>scheduled<\/code>. Depois de <code>asynq queue unpause default<\/code>, o log do worker conta a hist\u00f3ria toda:<\/p>\n<pre><code class=\"language-bash\">asynq: pid=416390 2026\/09\/23 15:24:53.136156 INFO: Starting processing\nasynq: pid=416390 2026\/09\/23 15:24:53.136186 INFO: Send signal TSTP to stop processing new tasks\nasynq: pid=416390 2026\/09\/23 15:24:53.136187 INFO: Send signal TERM or INT to terminate the process\n2026\/09\/23 12:24:55 e-mail de boas-vindas para ana@exemplo.com.br (usu\u00e1rio 42, tarefa 542783fc-53f6-48c4-bff3-b3ded2e72681)\n2026\/09\/23 12:25:14 ERRO imagem:miniatura (retry 0\/3): storage indispon\u00edvel ao ler uploads\/foto-42.jpg (tentativa 1)\n2026\/09\/23 12:25:14 ERRO imagem:miniatura (retry 0\/3): formato n\u00e3o suportado: uploads\/antiga.bmp: skip retry for the task\nasynq: pid=416390 2026\/09\/23 15:25:14.093162 WARN: Retry exhausted for task id=16d93751-6b19-4ca7-84fa-71cc2a20c306\n2026\/09\/23 12:25:14 relat\u00f3rio di\u00e1rio gerado \u00e0s 12:25:14\n2026\/09\/23 12:25:23 ERRO imagem:miniatura (retry 1\/3): storage indispon\u00edvel ao ler uploads\/foto-42.jpg (tentativa 2)\n2026\/09\/23 12:25:28 ERRO imagem:miniatura (retry 0\/3): storage indispon\u00edvel ao ler uploads\/banner.png (tentativa 1)\n2026\/09\/23 12:25:33 relat\u00f3rio di\u00e1rio gerado \u00e0s 12:25:33\n2026\/09\/23 12:25:33 ERRO imagem:miniatura (retry 1\/3): storage indispon\u00edvel ao ler uploads\/banner.png (tentativa 2)\n2026\/09\/23 12:25:36 miniatura 320px gerada para uploads\/foto-42.jpg na tentativa 3\n2026\/09\/23 12:25:47 miniatura 1200px gerada para uploads\/banner.png na tentativa 3\n2026\/09\/23 12:25:53 relat\u00f3rio di\u00e1rio gerado \u00e0s 12:25:53\n2026\/09\/23 12:26:11 ERRO imagem:miniatura (retry 0\/3): formato n\u00e3o suportado: uploads\/antiga.bmp: skip retry for the task\nasynq: pid=416390 2026\/09\/23 15:26:11.619533 WARN: Retry exhausted for task id=16d93751-6b19-4ca7-84fa-71cc2a20c306\n2026\/09\/23 12:26:13 relat\u00f3rio di\u00e1rio gerado \u00e0s 12:26:13\nasynq: pid=416390 2026\/09\/23 15:26:14.020184 INFO: Stopping processor\nasynq: pid=416390 2026\/09\/23 15:26:14.449245 INFO: Processor stopped\nasynq: pid=416390 2026\/09\/23 15:26:14.449276 INFO: Starting graceful shutdown\nasynq: pid=416390 2026\/09\/23 15:26:14.449296 INFO: Waiting for all workers to finish...\nasynq: pid=416390 2026\/09\/23 15:26:14.449300 INFO: All workers have finished\nasynq: pid=416390 2026\/09\/23 15:26:14.449797 INFO: Exiting<\/code><\/pre>\n<p>Leia na ordem:<\/p>\n<ul>\n<li>o e-mail saiu na hora, pela fila <code>critical<\/code>, que n\u00e3o estava pausada;<\/li>\n<li>a <code>foto-42.jpg<\/code> falhou duas vezes e deu certo na terceira, com 9 e 13 segundos entre as tentativas (atraso de 5 s e 10 s mais a checagem peri\u00f3dica do Asynq);<\/li>\n<li>a <code>antiga.bmp<\/code> foi para <code>archived<\/code> na primeira falha, por causa do <code>SkipRetry<\/code>. O aviso <code>Retry exhausted<\/code> \u00e9 a mensagem que o pr\u00f3prio Asynq usa nesse caso;<\/li>\n<li>o banner agendado para 12:25:25 come\u00e7ou \u00e0s 12:25:28: o Asynq verifica as tarefas agendadas a cada 5 segundos, ent\u00e3o n\u00e3o conte com precis\u00e3o de segundo;<\/li>\n<li>\u00e0s 12:26:11, reprocessei a tarefa arquivada com <code>asynq task run<\/code>, e ela voltou ao arquivo, como esperado;<\/li>\n<li>no <code>SIGTERM<\/code>, o servidor parou de puxar tarefas, esperou os workers e saiu. Se uma tarefa passar do <code>ShutdownTimeout<\/code>, ela volta para o Redis e roda de novo em outro worker, e da\u00ed vem a exig\u00eancia de idempot\u00eancia.<\/li>\n<\/ul>\n<p>Os hor\u00e1rios com prefixo <code>asynq:<\/code> est\u00e3o em UTC (logger interno da biblioteca), e os outros no hor\u00e1rio local.<\/p>\n<h3>O mesmo teste no Valkey<\/h3>\n<p>Troquei s\u00f3 a vari\u00e1vel: <code>REDIS_ADDR=127.0.0.1:6380<\/code>. Deduplica\u00e7\u00e3o, retries, agendamento, arquivamento e peri\u00f3dicas se comportaram igual. A tarefa em <code>retry<\/code> no resumo abaixo \u00e9 o banner: encerrei o worker antes da terceira tentativa. A CLI mostra a vers\u00e3o <code>7.2.4<\/code> porque o Valkey se apresenta com essa vers\u00e3o de compatibilidade no <code>INFO<\/code>:<\/p>\n<pre><code class=\"language-bash\">Task Count by State\nactive     pending    aggregating  scheduled  retry      archived   completed\n---------  ---------  ---------    ---------  ---------  ---------  ---------\n0          0          0            0          1          1          0\n\nTask Count by Queue\ncritical  default   low\n--------  --------  --------\n0         1         1\n\nDaily Stats 2026-09-23 UTC\nprocessed  failed  error rate\n---------  ------  ----------\n9          5       55.56%\n\nRedis Info\nversion  uptime  connections  memory usage  peak memory usage\n-------  ------  -----------  ------------  -----------------\n7.2.4    0 days  1            1.70MB        1.71MB<\/code><\/pre>\n<h2>A CLI asynq<\/h2>\n<p>A ferramenta de linha de comando fica num m\u00f3dulo separado do reposit\u00f3rio:<\/p>\n<pre><code class=\"language-bash\">go install github.com\/hibiken\/asynq\/tools\/asynq@latest<\/code><\/pre>\n<p>Um detalhe: o m\u00f3dulo <code>tools<\/code> n\u00e3o tem tag pr\u00f3pria, ent\u00e3o o <code>@latest<\/code> resolve para um commit do <code>master<\/code> (no meu caso, de 12 de junho de 2026) que ainda declara depend\u00eancia do Asynq v0.25.0. Funcionou sem problema contra filas da v0.26.0. Os comandos que mais uso:<\/p>\n<pre><code class=\"language-bash\">asynq stats                                   # vis\u00e3o geral por estado e por fila\nasynq queue ls                                # lista as filas\nasynq queue inspect default                   # detalhes de uma fila\nasynq queue pause default                     # para de entregar tarefas da fila\nasynq task ls --queue=default --state=archived\nasynq task run --queue=default --id=&lt;ID&gt;     # reprocessa uma arquivada\nasynq server ls                               # workers conectados\nasynq cron ls                                 # entradas do Scheduler\nasynq dash                                    # painel no terminal (TUI)<\/code><\/pre>\n<p>Para outro servidor, use <code>--uri host:porta<\/code>, <code>--password<\/code>, <code>--username<\/code> e <code>--tls<\/code>. Sa\u00eddas reais do laborat\u00f3rio:<\/p>\n<pre><code class=\"language-bash\">$ asynq stats\nTask Count by State\nactive     pending    aggregating  scheduled  retry      archived   completed\n---------  ---------  ---------    ---------  ---------  ---------  ---------\n0          0          0            0          0          1          1\n\nTask Count by Queue\ncritical  default   low\n--------  --------  --------\n0         1         1\n\nDaily Stats 2026-09-23 UTC\nprocessed  failed  error rate\n---------  ------  ----------\n13         6       46.15%\n\nRedis Info\nversion  uptime  connections  memory usage  peak memory usage\n-------  ------  -----------  ------------  -----------------\n8.10.2   0 days  4            2.32MB        2.38MB\n\n\n$ asynq task ls --queue=default --state=archived\nID                                    Type              Payload                                        Last Failed                   Last Error\n--                                    ----              -------                                        -----------                   ----------\n16d93751-6b19-4ca7-84fa-71cc2a20c306  imagem:miniatura  {\"origem\":\"uploads\/antiga.bmp\",\"largura\":320}  Wed Sep 23 12:26:11 -03 2026  formato n\u00e3o suportado: uploads\/antiga.bmp: skip retry for the task<\/code><\/pre>\n<h2>Asynqmon: o painel web<\/h2>\n<p>O <a href=\"https:\/\/github.com\/hibiken\/asynqmon\">Asynqmon<\/a> roda como bin\u00e1rio, cont\u00eainer ou biblioteca embutida na sua aplica\u00e7\u00e3o (<code>asynqmon.New(...)<\/code> devolve um <code>http.Handler<\/code>). O jeito mais r\u00e1pido \u00e9 a imagem Docker, na mesma rede do Redis:<\/p>\n<pre><code class=\"language-bash\">docker run -d --name asynqmon --network filas \\\n  -p 127.0.0.1:8080:8080 \\\n  hibiken\/asynqmon:0.7.2 \\\n  --redis-addr=redis:6379 \\\n  --enable-metrics-exporter<\/code><\/pre>\n<p>Abra <code>http:\/\/127.0.0.1:8080<\/code>. A aba <strong>Queues<\/strong> mostra tamanho, mem\u00f3ria, lat\u00eancia, processadas e taxa de erro por fila. No print abaixo, a <code>default<\/code> aparece pausada, com as duas miniaturas pendentes:<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/asynqmon-painel-filas.webp\" alt=\"Painel Queues do Asynqmon com as filas critical, default (pausada, 2 tarefas pendentes) e low\" width=\"1200\" height=\"677\" \/><\/figure>\n<p>Clicando numa fila, voc\u00ea v\u00ea as tarefas por estado (active, pending, aggregating, scheduled, retry, archived, completed), com payload e \u00faltimo erro. Dali d\u00e1 para reexecutar, apagar ou arquivar em lote:<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/asynqmon-tarefas-arquivadas.webp\" alt=\"Asynqmon mostrando a tarefa imagem:miniatura arquivada na fila default com o payload e o \u00faltimo erro\" width=\"1200\" height=\"488\" \/><\/figure>\n<p>A aba <strong>Schedulers<\/strong> lista as entradas peri\u00f3dicas registradas, com o pr\u00f3ximo e o \u00faltimo enfileiramento:<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/asynqmon-agendadores.webp\" alt=\"Aba Schedulers do Asynqmon com as entradas peri\u00f3dicas 0 3 * * * e @every 20s da tarefa relatorio:diario\" width=\"1200\" height=\"428\" \/><\/figure>\n<p>O painel n\u00e3o tem autentica\u00e7\u00e3o nenhuma: quem acessa pode apagar filas inteiras. Deixe-o preso em <code>127.0.0.1<\/code> e publique por tr\u00e1s de um proxy reverso com login, VPN ou t\u00fanel SSH. Para quem s\u00f3 precisa olhar, existe <code>--read-only<\/code>.<\/p>\n<h2>M\u00e9tricas no Prometheus<\/h2>\n<p>H\u00e1 dois caminhos, ambos conferidos no c\u00f3digo:<\/p>\n<ul>\n<li><strong>Pelo Asynqmon<\/strong>: com <code>--enable-metrics-exporter<\/code>, ele exp\u00f5e <code>\/metrics<\/code> com o estado das filas. Com <code>--prometheus-addr=http:\/\/prometheus:9090<\/code>, ele tamb\u00e9m consulta o Prometheus e liga a aba de gr\u00e1ficos hist\u00f3ricos.<\/li>\n<li><strong>Dentro da sua aplica\u00e7\u00e3o<\/strong>: o pacote <a href=\"https:\/\/github.com\/hibiken\/asynq\/tree\/master\/x\/metrics\"><code>github.com\/hibiken\/asynq\/x\/metrics<\/code><\/a> tem um <code>NewQueueMetricsCollector(inspector)<\/code> que voc\u00ea registra no seu pr\u00f3prio <code>prometheus.Registry<\/code>, sem depender do Asynqmon.<\/li>\n<\/ul>\n<p>A sa\u00edda real do exportador do Asynqmon no laborat\u00f3rio:<\/p>\n<pre><code class=\"language-bash\">curl -s 127.0.0.1:8080\/metrics | grep '^asynq_' | grep default<\/code><\/pre>\n<pre><code class=\"language-bash\">asynq_queue_latency_seconds{queue=\"default\"} 0\nasynq_queue_memory_usage_approx_bytes{queue=\"default\"} 547\nasynq_queue_paused_total{queue=\"default\"} 0\nasynq_queue_size{queue=\"default\"} 1\nasynq_tasks_enqueued_total{queue=\"default\",state=\"active\"} 0\nasynq_tasks_enqueued_total{queue=\"default\",state=\"archived\"} 1\nasynq_tasks_enqueued_total{queue=\"default\",state=\"completed\"} 0\nasynq_tasks_enqueued_total{queue=\"default\",state=\"pending\"} 0\nasynq_tasks_enqueued_total{queue=\"default\",state=\"retry\"} 0\nasynq_tasks_enqueued_total{queue=\"default\",state=\"scheduled\"} 0\nasynq_tasks_failed_total{queue=\"default\"} 4\nasynq_tasks_processed_total{queue=\"default\"} 5<\/code><\/pre>\n<p>E o trecho do <code>prometheus.yml<\/code>:<\/p>\n<pre><code class=\"language-yaml\">scrape_configs:\n  - job_name: asynq\n    static_configs:\n      - targets: [\"127.0.0.1:8080\"]<\/code><\/pre>\n<p>Os alertas que valem a pena: <code>asynq_queue_size<\/code> crescendo sem parar (workers insuficientes ou parados), <code>asynq_queue_latency_seconds<\/code> alto na fila <code>critical<\/code> e qualquer aumento de <code>asynq_tasks_enqueued_total{state=\"archived\"}<\/code>. Se voc\u00ea ainda n\u00e3o tem Prometheus, o guia <a href=\"\/2026\/09\/monitorando-servidores-linux-com-prometheus\/\">Monitorando servidores Linux com Prometheus e Node Exporter<\/a> monta a base.<\/p>\n<h2>Deploy: o worker como servi\u00e7o systemd<\/h2>\n<p>O worker \u00e9 um bin\u00e1rio est\u00e1tico, sem runtime, e roda bem com um usu\u00e1rio sem privil\u00e9gios. O ponto central \u00e9 o <code>TimeoutStopSec<\/code>, que precisa ser maior que o <code>ShutdownTimeout<\/code> do c\u00f3digo (20 s). Sem isso, o systemd manda <code>SIGKILL<\/code> antes de o Asynq devolver as tarefas em andamento.<\/p>\n<pre><code class=\"language-ini\"># \/etc\/systemd\/system\/filas-worker.service\n[Unit]\nDescription=Worker Asynq (filas)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=filas\nGroup=filas\nEnvironment=REDIS_ADDR=127.0.0.1:6379\nExecStart=\/usr\/local\/bin\/filas-worker\nKillSignal=SIGTERM\nTimeoutStopSec=30\nRestart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<pre><code class=\"language-bash\">sudo useradd --system --no-create-home --shell \/usr\/sbin\/nologin filas\nsudo install -m 755 bin\/worker \/usr\/local\/bin\/filas-worker\nsudo systemctl daemon-reload\nsudo systemctl enable --now filas-worker\njournalctl -u filas-worker -f<\/code><\/pre>\n<p>O agendador ganha uma unit igual, com <code>ExecStart=\/usr\/local\/bin\/filas-agendador<\/code>, em <strong>uma<\/strong> m\u00e1quina s\u00f3. Para mais comandos do dia a dia, veja <a href=\"\/2026\/09\/dominando-o-systemd-comandos-essenciais\/\">Dominando o systemd<\/a>. Se o seu produtor \u00e9 uma API Go, a s\u00e9rie <a href=\"\/2026\/09\/vue-go-echo-v5-parte-1-ambiente-e-primeira-api\/\">Vue.js + Go com Echo v5<\/a> mostra como mont\u00e1-la, e a <a href=\"\/2026\/09\/vue-go-echo-v5-parte-5-embed-binario-unico-e-docker\/\">parte 5<\/a> cobre bin\u00e1rio \u00fanico, Docker e systemd, um encaixe natural para chamar <code>client.Enqueue<\/code> dentro dos handlers.<\/p>\n<h2>Boas pr\u00e1ticas<\/h2>\n<ul>\n<li><strong>Idempot\u00eancia sempre<\/strong>: a entrega \u00e9 &#8220;pelo menos uma vez&#8221;. Grave no banco que o e-mail do usu\u00e1rio 42 j\u00e1 foi enviado e confira antes de enviar de novo. <code>Unique<\/code> e <code>TaskID<\/code> evitam duplicar a tarefa na fila, mas n\u00e3o protegem contra reexecu\u00e7\u00e3o depois de um crash.<\/li>\n<li><strong>Payload pequeno<\/strong>: mande IDs e caminhos, n\u00e3o o arquivo. A imagem fica no storage, e a tarefa leva s\u00f3 <code>uploads\/foto-42.jpg<\/code>. Payload grande pesa na mem\u00f3ria do Redis e em cada leitura.<\/li>\n<li><strong>Filas separadas por perfil<\/strong>: e-mail transacional na <code>critical<\/code>, processamento pesado na <code>low<\/code>. Se o volume justificar, rode workers dedicados s\u00f3 para a fila pesada (<code>Queues: {\"low\": 1}<\/code>) em outra m\u00e1quina.<\/li>\n<li><strong>Erro permanente sem retry<\/strong>: payload inv\u00e1lido ou formato n\u00e3o suportado nunca v\u00e3o dar certo. Use <code>SkipRetry<\/code> e deixe a tarefa no arquivo para an\u00e1lise.<\/li>\n<li><strong>Respeite o contexto<\/strong>: passe o <code>ctx<\/code> do handler para HTTP, banco e SMTP. \u00c9 por ele que chegam timeout, deadline e cancelamento.<\/li>\n<li><strong>Redis com persist\u00eancia e senha<\/strong>: a fila mora no Redis. Ative AOF ou RDB, configure <code>requirepass<\/code> ou ACL e n\u00e3o o exponha fora da rede interna. N\u00e3o use a mesma inst\u00e2ncia como cache com <code>maxmemory-policy<\/code> de despejo, ou o Redis pode apagar tarefas.<\/li>\n<li><strong>Fixe vers\u00f5es<\/strong>: com a API ainda em <code>v0.x<\/code>, leia as notas antes de cada atualiza\u00e7\u00e3o do Asynq.<\/li>\n<\/ul>\n<h2>Conclus\u00e3o<\/h2>\n<p>O Asynq resolve bem o problema cl\u00e1ssico de tirar trabalho da requisi\u00e7\u00e3o: filas com prioridade, retry com backoff, agendamento, cron, deduplica\u00e7\u00e3o e desligamento limpo, tudo com um Redis ou Valkey que voc\u00ea provavelmente j\u00e1 tem. A biblioteca segue viva, com release em 2026 e commits recentes. O Asynqmon funciona, mas est\u00e1 parado desde 2023, ent\u00e3o use-o como painel interno e confie no Prometheus para alertas. Se um dia o Asynqmon quebrar numa vers\u00e3o nova do Asynq, a CLI e o <code>x\/metrics<\/code> cobrem o essencial.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Practical guide to Asynq, the task queue in Go on Redis: priorities, retries with backoff, scheduling, cron, deduplication, graceful shutdown, CLI, Asynqmon panel, Prometheus, and systemd, tested with Redis 8 and Valkey 8.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[45,46,2,111,120],"tags":[494,495,498,58,31,125,496,123,497],"class_list":["post-1713","post","type-post","status-publish","format-standard","hentry","category-desenv","category-devops","category-linux","category-opensource","category-servidores","tag-asynq","tag-asynqmon","tag-fila-de-tarefas","tag-go","tag-golang","tag-prometheus","tag-redis","tag-systemd","tag-valkey"],"_links":{"self":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/1713","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/comments?post=1713"}],"version-history":[{"count":1,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/1713\/revisions"}],"predecessor-version":[{"id":1715,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/1713\/revisions\/1715"}],"wp:attachment":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/media?parent=1713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/categories?post=1713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/tags?post=1713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}