Contexto
O #1017 (fecha #1015 e #1016) tornou a reconciliação de liveness do scaler
autoritativa e bidirecional: classifica réplicas conhecidas com autoridade por
host (Missing vs Unknown), poda Missing/Stopped-pós-graça por uma rotina de
limpeza única, e readota um container reiniciado externamente após readiness (com
janela de graça que evita spawn duplicado). O caminho de recuperação é o reconcile
periódico do scaler (tick de ~10 s).
As duas otimizações marcadas como opcionais nos issues #1015 e #1016 ficaram
deliberadamente fora do #1017 para manter a fatia mergeável. Este issue as
rastreia. Ambas apenas encurtam a janela de recuperação; nenhuma substitui o
reconcile periódico, que continua sendo o fallback.
Fatia A — recuperação reativa no caminho do proxy
Hoje, entre o docker rm -f/docker restart e o próximo tick, uma requisição
roteada para a réplica agora inalcançável recebe 502 upstream error. O reconcile
periódico limpa em ≤10 s, mas o usuário vê o 502 nesse intervalo.
Proposta: em falha de transporte (erro de conexão HTTP, ou falha ao abrir o
WebSocket upstream) contra a réplica selecionada:
- Confirmar o estado real no backend via
ContainerBackend::replica_liveness
(apenas para a réplica em questão).
- Se o backend confirmar
Stopped/Missing (autoritativo), executar a mesma
cleanup_replica_state do scaler e invalidar o vínculo sticky (expirar o cookie
__ruscker_session_{spec}).
- Disparar o spawn coalescido para a spec (a infra
state.spawn_locks já existe).
- Replay apenas de requisições seguramente reproduzíveis (
GET/HEAD, sem
corpo já enviado). Para métodos não idempotentes: não repetir cegamente —
limpar a réplica para que a próxima chamada se recupere e devolver um erro
explícito.
Não podar por um 5xx da aplicação nem por um erro transitório do daemon
Docker — a poda reativa exige confirmação autoritativa Stopped/Missing
(reusar exatamente a distinção Missing vs Unknown do #1017; um host Unknown
nunca poda).
Cuidados: é o hot path — a confirmação no backend deve ser barata (uma réplica),
fora de qualquer lock de registro, e o replay deve ter limite (uma tentativa) para
não amplificar carga. WebSocket já conecta o upstream antes do 101
(ws::connect), então a falha de conexão WS é o ponto natural de recuperação.
Critérios de aceite (A)
Fatia B — listener de eventos Docker (recuperação quase imediata)
Proposta: assinar o stream de eventos do daemon (die/destroy/stop/start),
filtrado pelos labels do Ruscker, e disparar a mesma reconciliação/limpeza sem
esperar o tick:
die/stop/restart → tirar a réplica do roteamento (equivalente a
mark_restarting).
start → reinspecionar binding + readiness + readotar ao pool.
destroy → poda definitiva + limpeza de sessões.
- O listener deve reconectar ao stream em caso de queda; o reconcile periódico
permanece como fallback para eventos perdidos durante desconexões/restarts do
Ruscker.
Escopo: implementar primeiro no LocalDockerBackend (um daemon). Multi-host = um
stream por host, com um host indisponível apenas degradando para o fallback
periódico daquele host (nunca podando por ausência de eventos). Pode ficar atrás de
um flag de config inicialmente.
Critérios de aceite (B)
Notas
- Fatias independentes e mergeáveis separadamente (A entrega o ganho visível ao
usuário; B reduce a latência de fundo). Sugиро A primeiro.
- Testes: unit para a decisão de recuperação reativa (transport-fail +
Missing
confirmado → limpa sticky/registro + inicia recuperação; 5xx da app → nada);
docker-it para o listener de eventos (spawn, docker rm -f, asserir limpeza em
~1 s).
Contexto
O #1017 (fecha #1015 e #1016) tornou a reconciliação de liveness do scaler
autoritativa e bidirecional: classifica réplicas conhecidas com autoridade por
host (
MissingvsUnknown), podaMissing/Stopped-pós-graça por uma rotina delimpeza única, e readota um container reiniciado externamente após readiness (com
janela de graça que evita spawn duplicado). O caminho de recuperação é o reconcile
periódico do scaler (tick de ~10 s).
As duas otimizações marcadas como opcionais nos issues #1015 e #1016 ficaram
deliberadamente fora do #1017 para manter a fatia mergeável. Este issue as
rastreia. Ambas apenas encurtam a janela de recuperação; nenhuma substitui o
reconcile periódico, que continua sendo o fallback.
Fatia A — recuperação reativa no caminho do proxy
Hoje, entre o
docker rm -f/docker restarte o próximo tick, uma requisiçãoroteada para a réplica agora inalcançável recebe
502 upstream error. O reconcileperiódico limpa em ≤10 s, mas o usuário vê o 502 nesse intervalo.
Proposta: em falha de transporte (erro de conexão HTTP, ou falha ao abrir o
WebSocket upstream) contra a réplica selecionada:
ContainerBackend::replica_liveness(apenas para a réplica em questão).
Stopped/Missing(autoritativo), executar a mesmacleanup_replica_statedo scaler e invalidar o vínculo sticky (expirar o cookie__ruscker_session_{spec}).state.spawn_locksjá existe).GET/HEAD, semcorpo já enviado). Para métodos não idempotentes: não repetir cegamente —
limpar a réplica para que a próxima chamada se recupere e devolver um erro
explícito.
Não podar por um
5xxda aplicação nem por um erro transitório do daemonDocker — a poda reativa exige confirmação autoritativa
Stopped/Missing(reusar exatamente a distinção
MissingvsUnknowndo #1017; um hostUnknownnunca poda).
Cuidados: é o hot path — a confirmação no backend deve ser barata (uma réplica),
fora de qualquer lock de registro, e o replay deve ter limite (uma tentativa) para
não amplificar carga. WebSocket já conecta o upstream antes do 101
(
ws::connect), então a falha de conexão WS é o ponto natural de recuperação.Critérios de aceite (A)
GETpara uma spec cuja réplica sofreudocker rm -frecupera na mesmarequisição (sem esperar o tick), sem 502 persistente.
após limpar a réplica.
5xxda aplicação ou um erro transitório do daemon não poda a réplica.Unknown(timeout) na confirmação reativa não poda.Fatia B — listener de eventos Docker (recuperação quase imediata)
Proposta: assinar o stream de eventos do daemon (
die/destroy/stop/start),filtrado pelos labels do Ruscker, e disparar a mesma reconciliação/limpeza sem
esperar o tick:
die/stop/restart→ tirar a réplica do roteamento (equivalente amark_restarting).start→ reinspecionar binding + readiness + readotar ao pool.destroy→ poda definitiva + limpeza de sessões.permanece como fallback para eventos perdidos durante desconexões/restarts do
Ruscker.
Escopo: implementar primeiro no
LocalDockerBackend(um daemon). Multi-host = umstream por host, com um host indisponível apenas degradando para o fallback
periódico daquele host (nunca podando por ausência de eventos). Pode ficar atrás de
um flag de config inicialmente.
Critérios de aceite (B)
~1 s (não ~10 s), sem interface.
"silêncio" de eventos.
periódico, sem poda nem duplicação.
Notas
usuário; B reduce a latência de fundo). Sugиро A primeiro.
Missingconfirmado → limpa sticky/registro + inicia recuperação;
5xxda app → nada);docker-itpara o listener de eventos (spawn,docker rm -f, asserir limpeza em~1 s).