Tarefa periódica
Quando usar: manutenção que precisa acontecer de tempos em tempos — reconciliar o que ficou pela metade, expurgar o que passou da retenção, sincronizar um catálogo.
Declarar
Um método de bean, anotado, com o Ctx da rodada (ou sem parâmetro nenhum):
@Component
@Profile("serve")
static class Periodic {
private final SampleService service;
private final ActionReaper reaper;
Periodic(SampleService service, ActionReaper reaper) {
this.service = service;
this.reaper = reaper;
}
/**
* `scan-reconcile`: varredura cuja Action terminou sem sucesso (esgotada, aposentada) deixaria o
* item "pendente" para sempre; esta tarefa fecha a conta.
*/
@NnsPeriodic(name = "scan-reconcile", every = "1m")
void scanReconcile(Ctx ctx) {
service.reconcileScans(ctx, Duration.ofMinutes(2));
}
/** `action-reaper`: fecha as Actions cuja release saiu do ar. */
@NnsPeriodic(name = "action-reaper", every = "1m")
void reap(Ctx ctx) {
reaper.reap(ctx);
}
}
O nome é único no serviço e aparece na métrica (nns.periodic.runs{task,outcome}) e no log. O
every aceita a sintaxe de duração da plataforma (30s, 1m, 1h30m) e um placeholder da
configuração declarada: every = "${catalog.sync-every:1h}".
O que o SDK garante
- Só a release ativa dispara. O papel é relido a cada rodada — a standby não roda, e quando a release vira ativa, ela começa a rodar sem restart.
- Primeira espera aleatória em
[0, every/10]: réplicas da mesma release não saem juntas. - O período é contado do fim da rodada: uma rodada que demora não empilha a próxima.
- O prazo da rodada é o próprio
every. - A exceção é contida: um erro numa rodada não mata o agendador e sai no log com
error_class. - Desligamento: nada de rodada nova a partir do início do desligamento; a rodada em curso é
interrompida pelo cancelamento do
Ctx.
Uma rodada longa (laço infinito) não é tarefa periódica: para isso existe o laço de fundo
(NnsBackground.loop, como o ActionRunner deste módulo).
Como testar
O teste roda a tarefa cadastrada, pelo nome, sem esperar o relógio. Duas linhas de configuração:
nns.periodic.schedule=false registra as tarefas sem agendar (recusado em produção), e
PeriodicTasks.runOnce("<nome>") roda uma agora:
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
ConsoleSdkAutoConfiguration.class, NnsLifecycleAutoConfiguration.class))
.withInitializer(NnsApplication.contextInitializer(NnsCommand.SERVE, config, module))
.withPropertyValues(
"nns.periodic.schedule=false",
"spring.profiles.active=serve",
"spring.autoconfigure.exclude="
+ "br.com.nnumbers.console.sdk.spring.data.NnsDataAutoConfiguration,"
+ "br.com.nnumbers.console.sdk.spring.web.NnsWebAutoConfiguration")
.withBean(CountingService.class, () -> service)
.withBean(ActionReaper.class, () -> reaper)
.withBean(SampleConfiguration.Periodic.class, () -> periodic)
.run(
context -> {
PeriodicTasks tasks = context.getBean(PeriodicTasks.class);
// As duas declarações do módulo estão registradas.
assertThat(tasks.names()).containsExactlyInAnyOrder("scan-reconcile", "action-reaper");
assertThat(tasks.runOnce("scan-reconcile")).isTrue();
assertThat(service.reconciled).hasValue(1);
// Nome que não existe é ERRO de programação, e o gancho diz isso.
assertThatThrownBy(() -> tasks.runOnce("nao-existe"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("nao-existe");
});
O que esse teste pega e uma chamada direta ao método não pegaria: nome errado na anotação, tarefa não registrada, bean ausente.
O serviço de contagem é um dublê que registra a chamada, para o teste não precisar de banco:
static final class CountingService extends SampleService {
final AtomicInteger reconciled = new AtomicInteger();
@Override
public int reconcileScans(Ctx ctx, Duration minAge) {
reconciled.incrementAndGet();
return 0;
}
}
As tarefas que já vêm prontas
Com banco, migrador e as Sources do SDK, duas tarefas são registradas sozinhas — não escreva as suas:
| Tarefa | O que faz |
|---|---|
audit-outbox-purge | Expurga do outbox os eventos já entregues, depois da retenção. |
idempotency-purge | Expurga as respostas guardadas de idempotência, depois da retenção. |
Erros comuns
@Scheduleddo Spring — não conhece o papel: a standby também dispara e as duas releases escrevem no mesmo lugar. Use@NnsPeriodic.- Trabalho longo numa tarefa periódica — o prazo da rodada é o
every; se ela pode passar disso, o lugar é um laço de fundo comNnsBackground.sleep. - Fazer a rodada "pegar tudo" — duas releases da mesma ativa... e uma rodada atrasada depois de um
restart: limite o lote (
LIMIT, idade mínima) e seja idempotente. - Contar com intervalo exato — o primeiro disparo é aleatório dentro de
every/10e o período começa no fim da rodada. Tarefa de minuto em minuto não é relógio de parede. - Deixar exceção subir esperando retry — a rodada falhou e o log registrou; o retry é a próxima rodada. Se precisa de mais, trate dentro da tarefa.
- Esquecer o
@Profile("serve")— a tarefa passa a existir também nomigrate; o Job não deve agendar nada. - Rodar a manutenção na réplica "líder" — a plataforma não tem líder: a tarefa roda em toda réplica da release ativa, então ela precisa ser segura com N réplicas concorrentes.
Esta página ajudou?
Reportar um problema nesta páginaNão envie senhas, chaves, tokens ou dados de clientes.