0Pricing
DevOps Bootcamp · Aula

Scripts idempotentes e lógica de novas tentativas com espera progressiva

Projete operações seguras para executar novamente e adicione espera progressiva exponencial para chamadas externas instáveis.

Scripts idempotentes e lógica de novas tentativas com espera progressiva é uma aula grátis de DevOps Bootcamp no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de DevOps Bootcamp, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DevOps Bootcamp inclui 4 aulas no total.

O que é idempotência e por que ela é importante

Idempotência significa que executar a mesma operação várias vezes produz o mesmo resultado que executá-la uma vez. Em scripts Bash, isso é fundamental porque os scripts falham, as redes caem e as pessoas podem repetir operações acidentalmente.

  • Um script não idempotente que cria um usuário duas vezes pode falhar ou duplicar dados.
  • Um script idempotente verifica primeiro: isso já existe?
  • Scripts idempotentes podem ser usados com segurança em tarefas do Cron, fluxos de CI e loops de repetição.

A regra de ouro é: verifique antes de agir. Toda operação destrutiva ou de criação deve ser protegida por um teste de pré-condição.

Protegendo a criação de arquivos e diretórios

O padrão mais comum de idempotência consiste em verificar se um recurso já existe antes de criá-lo. O Bash fornece comandos de uma linha concisos para isso.

  • [ -d dir ] — verdadeiro se o diretório existir
  • [ -f file ] — verdadeiro se o arquivo regular existir
  • mkdir -p — cria o diretório somente se ele não existir (idempotência integrada)

Prefira opções integradas, como -p e --no-clobber, a verificações manuais quando estiverem disponíveis — elas são atômicas e seguras contra condições de corrida.

#!/usr/bin/env bash
set -euo pipefail

CONFIG_DIR="$HOME/.myapp"
CONFIG_FILE="$CONFIG_DIR/config.ini"

# Idempotent: mkdir -p never fails if dir already exists
mkdir -p "$CONFIG_DIR"

# Idempotent: only write config if it doesn't exist yet
if [ ! -f "$CONFIG_FILE" ]; then
  echo '[defaults]' > "$CONFIG_FILE"
  echo 'timeout=30' >> "$CONFIG_FILE"
  echo "Created $CONFIG_FILE"
else
  echo "Config already exists, skipping."
fi

Gerenciamento idempotente de usuários e grupos

Tarefas de administração do sistema, como adicionar usuários ou grupos, devem ser idempotentes — executar novamente o script na mesma máquina não deve causar erros nem criar duplicatas.

  • id username retorna 0 se o usuário existir
  • getent group groupname verifica a existência de um grupo
  • Envolva cada operação em uma proteção para que o script possa ser repetido com segurança

Esse padrão é a base de ferramentas de gerenciamento de configuração, como o Ansible — cada tarefa é uma operação protegida e idempotente.

#!/usr/bin/env bash
set -euo pipefail

APP_USER="apprunner"
APP_GROUP="appgroup"

# Idempotent group creation
if ! getent group "$APP_GROUP" &>/dev/null; then
  groupadd "$APP_GROUP"
  echo "Group '$APP_GROUP' created."
else
  echo "Group '$APP_GROUP' already exists."
fi

# Idempotent user creation
if ! id "$APP_USER" &>/dev/null; then
  useradd -m -g "$APP_GROUP" -s /bin/bash "$APP_USER"
  echo "User '$APP_USER' created."
else
  echo "User '$APP_USER' already exists."
fi

Usando arquivos de bloqueio para impedir execuções simultâneas

Mesmo um script idempotente pode causar problemas se duas instâncias forem executadas simultaneamente. Um arquivo de bloqueio garante que apenas uma instância seja executada por vez.

  • Crie um arquivo de bloqueio na inicialização e remova-o ao sair.
  • Use um trap para limpar o bloqueio mesmo se o script for interrompido.
  • mkdir em um único caminho é atômico na maioria dos sistemas de arquivos Linux — mais seguro do que touch para bloqueios.

Sem um bloqueio, uma tarefa lenta do Cron e uma nova execução manual podem colidir e corromper o estado compartilhado.

#!/usr/bin/env bash
set -euo pipefail

LOCKFILE="/tmp/myapp_deploy.lock"

# Atomic lock acquisition using mkdir
if ! mkdir "$LOCKFILE" 2>/dev/null; then
  echo "ERROR: Another instance is running (lock: $LOCKFILE). Exiting." >&2
  exit 1
fi

# Guarantee lock removal on any exit
trap 'rmdir "$LOCKFILE"; echo "Lock released."' EXIT

echo "Lock acquired. Running deployment..."
sleep 2   # simulate work
echo "Deployment complete."

Acompanhando etapas concluídas com um arquivo de estado

Em scripts com várias etapas (migrações, instalações, provisionamento), você pode acompanhar quais etapas já foram concluídas usando um arquivo de estado. Cada etapa verifica o arquivo de estado antes de ser executada e grava nele quando termina.

  • Barato e portátil — não requer banco de dados.
  • Permite que um script com falha retome a execução do ponto em que parou.
  • Armazene o estado em um local previsível, como /var/lib/myapp/ ou ~/.myapp/state/.

Esse padrão é usado por ferramentas importantes, como apt, cloud-init e estruturas de migração de bancos de dados.

#!/usr/bin/env bash
set -euo pipefail

STATE_DIR="/tmp/myapp_state"
mkdir -p "$STATE_DIR"

run_step() {
  local step_name="$1"
  local step_cmd="$2"
  local marker="$STATE_DIR/${step_name}.done"

  if [ -f "$marker" ]; then
    echo "[SKIP] $step_name already completed."
    return 0
  fi

  echo "[RUN]  $step_name ..."
  eval "$step_cmd"
  touch "$marker"
  echo "[DONE] $step_name"
}

run_step "install_deps"   "echo 'Installing dependencies...'"
run_step "configure_db"   "echo 'Configuring database...'"
run_step "start_service"  "echo 'Starting service...'"

Introdução à lógica de repetição

Chamadas externas — solicitações HTTP, consultas DNS e chamadas a APIs de nuvem — são inerentemente não confiáveis. Uma única falha não deve interromper todo o seu script. A lógica de repetição tenta executar novamente um comando que falhou de forma automática.

  • Repetição ingênua: repete N vezes até o comando ser bem-sucedido.
  • Sempre defina uma quantidade máxima de repetições para evitar loops infinitos.
  • Registre cada tentativa para que as falhas possam ser diagnosticadas.

A função de repetição mais simples envolve qualquer comando e tenta executá-lo novamente até um número fixo de vezes, com um atraso constante. Isso é suficiente para muitos casos de uso, mas apresenta uma falha crítica sob carga — abordada a seguir.

#!/usr/bin/env bash
set -euo pipefail

# Simple fixed-delay retry (3 attempts, 2s apart)
retry() {
  local max_attempts=3
  local delay=2
  local attempt=1

  until "$@"; do
    if (( attempt >= max_attempts )); then
      echo "ERROR: Command failed after $max_attempts attempts: $*" >&2
      return 1
    fi
    echo "Attempt $attempt failed. Retrying in ${delay}s..." >&2
    sleep "$delay"
    (( attempt++ ))
  done
}

# Example: retry a curl call
retry curl --silent --fail --max-time 5 https://httpbin.org/get -o /dev/null
echo "Request succeeded."

O problema da debandada

Quando muitos clientes repetem uma solicitação simultaneamente após uma falha, eles criam uma debandada — todos repetem no mesmo intervalo fixo, sobrecarregando o servidor exatamente no mesmo momento e impossibilitando a recuperação.

  • 100 scripts repetem a cada 5 segundos → 100 solicitações simultâneas a cada 5 segundos.
  • O servidor já está enfrentando dificuldades; a carga sincronizada piora a situação.
  • A solução é o recuo exponencial: dobrar o tempo de espera após cada falha.
  • Adicione variação aleatória para dessincronizar as repetições entre os clientes.

O recuo exponencial com variação aleatória é o padrão do setor, usado pelos SDKs da AWS, pelos clientes do Google Cloud e por todos os principais sistemas distribuídos.

Implementação do recuo exponencial

O recuo exponencial aumenta exponencialmente o tempo de espera após cada falha: 1s, 2s, 4s, 8s, 16s... Isso dá tempo para o sistema remoto se recuperar e reduz a carga total.

A fórmula: delay = base * (2 ^ attempt)

  • Defina um limite (atraso máximo) para que as esperas não cresçam indefinidamente.
  • Parâmetros a ajustar: base_delay, max_delay, max_attempts.

Esta função é reutilizável — passe qualquer comando para ela como argumento.

#!/usr/bin/env bash
set -euo pipefail

retry_with_backoff() {
  local max_attempts="${RETRY_MAX_ATTEMPTS:-5}"
  local base_delay="${RETRY_BASE_DELAY:-1}"
  local max_delay="${RETRY_MAX_DELAY:-30}"
  local attempt=0
  local delay="$base_delay"

  until "$@"; do
    (( attempt++ ))
    if (( attempt >= max_attempts )); then
      echo "ERROR: '$*' failed after $max_attempts attempts." >&2
      return 1
    fi
    echo "Attempt $attempt failed. Backing off for ${delay}s..." >&2
    sleep "$delay"
    # Double the delay, but cap it
    delay=$(( delay * 2 ))
    (( delay > max_delay )) && delay=$max_delay
  done

  echo "Command succeeded on attempt $(( attempt + 1 ))."
}

retry_with_backoff echo "Simulated success"

Adição de variação ao recuo

A variação adiciona aleatoriedade ao atraso do recuo. Mesmo com o recuo exponencial, se todos os clientes começarem ao mesmo tempo, eles ainda tentarão novamente em sincronia. A variação desfaz essa sincronização.

Duas estratégias comuns de variação:

  • Variação completa: sleep random(0, cap) — distribuição máxima, menor carga de pico.
  • Variação uniforme: sleep cap/2 + random(0, cap/2) — garante uma espera mínima e evita sobrecarregar o sistema imediatamente.

No Bash, use $RANDOM (0–32767) para gerar números aleatórios. Ajuste-os ao intervalo do atraso usando aritmética modular.

#!/usr/bin/env bash
set -euo pipefail

retry_with_jitter() {
  local max_attempts="${1:-5}"; shift
  local base_delay=1
  local max_delay=32
  local attempt=0
  local cap=$base_delay

  until "$@"; do
    (( attempt++ ))
    if (( attempt >= max_attempts )); then
      echo "ERROR: Giving up after $max_attempts attempts." >&2
      return 1
    fi

    # Full jitter: random value in [0, cap]
    local jitter=$(( RANDOM % (cap + 1) ))
    echo "Attempt $attempt failed. Sleeping ${jitter}s (cap=${cap}s)..." >&2
    sleep "$jitter"

    # Grow cap exponentially, bounded by max_delay
    cap=$(( cap * 2 ))
    (( cap > max_delay )) && cap=$max_delay
  done
}

retry_with_jitter 4 curl --silent --fail --max-time 3 https://httpbin.org/get -o /dev/null
echo "Done."

Combinação de idempotência e novas tentativas em um fluxo de trabalho real

Em scripts de produção, a idempotência e a lógica de novas tentativas trabalham em conjunto. Um fluxo de implantação típico pode:

  1. Adquirir um bloqueio (impedir execuções simultâneas)
  2. Verificar os arquivos de estado (ignorar etapas concluídas)
  3. Usar novas tentativas com recuo para chamadas externas (download, API, DNS)
  4. Marcar as etapas como concluídas somente após confirmar o sucesso
  5. Liberar o bloqueio por meio de trap

Essa combinação torna os scripts seguros para serem executados novamente a qualquer momento — após uma falha, um tempo limite ou um cancelamento manual — sem deixar o sistema em um estado inconsistente.

#!/usr/bin/env bash
set -euo pipefail

STATE_DIR="/tmp/deploy_state" && mkdir -p "$STATE_DIR"
LOCK="/tmp/deploy.lock"

mkdir "$LOCK" 2>/dev/null || { echo "Already running."; exit 1; }
trap 'rmdir "$LOCK"' EXIT

step_done() { [ -f "$STATE_DIR/$1.done" ]; }
mark_done() { touch "$STATE_DIR/$1.done"; }

retry_backoff() {
  local attempt=0 delay=1
  until "${@:2}"; do
    (( ++attempt >= $1 )) && { echo "Failed after $1 attempts."; return 1; }
    echo "Retry $attempt in ${delay}s..."; sleep $delay; delay=$(( delay * 2 ))
  done
}

if ! step_done "download_artifact"; then
  retry_backoff 4 curl -fsSL https://httpbin.org/get -o /tmp/artifact.json
  mark_done "download_artifact"
  echo "[DONE] download_artifact"
else
  echo "[SKIP] download_artifact"
fi

echo "Deployment finished successfully."

Tratamento de erros que não permitem novas tentativas

Nem todos os erros devem ser tentados novamente. Tentar novamente após um erro 404 Not Found ou 403 Forbidden é desperdício — eles nunca serão resolvidos sem intervenção humana. Sua lógica de novas tentativas deve distinguir entre:

  • Erros transitórios — tempo limite da rede, 503 Service Unavailable, falha de DNS → tentar novamente
  • Erros permanentes — 401 Unauthorized, 404 Not Found, entrada inválida → falhar imediatamente

Com curl, verifique o código de status HTTP e ignore novas tentativas para respostas 4xx. Use --write-out '%{http_code}' para capturar o status separadamente do corpo.

#!/usr/bin/env bash
set -euo pipefail

fetch_with_retry() {
  local url="$1"
  local max_attempts=4
  local delay=1
  local attempt=0
  local http_code

  until http_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url"); do
    : # curl itself failed (network error)
    (( ++attempt >= max_attempts )) && { echo "Network error, giving up."; return 1; }
    sleep $delay; delay=$(( delay * 2 ))
  done

  # Permanent client errors — do not retry
  if [[ "$http_code" =~ ^4 ]]; then
    echo "ERROR: HTTP $http_code for $url — not retrying." >&2
    return 1
  fi

  # Transient server errors — retry
  if [[ "$http_code" =~ ^5 ]]; then
    (( ++attempt >= max_attempts )) && { echo "Server error, giving up."; return 1; }
    echo "HTTP $http_code — backing off ${delay}s..."
    sleep $delay; delay=$(( delay * 2 ))
    fetch_with_retry "$url"
    return
  fi

  echo "HTTP $http_code — success."
}

fetch_with_retry "https://httpbin.org/status/200"

Verificação de conhecimento: escolha da estratégia de recuo

Um script de implantação baixa um artefato de versão de um bucket do S3. Durante um incidente recente, 80 instâncias da canalização falharam simultaneamente devido a uma breve interrupção do S3. Quando o S3 se recuperou após 10 segundos, as 80 instâncias tentaram novamente no mesmo momento, causando outra sobrecarga e prolongando a interrupção por mais 3 minutos.

Qual estratégia de novas tentativas evitaria melhor essa cascata de efeito manada em incidentes futuros?

Recapitulação: idempotência e novas tentativas com recuo

Nesta lição, você aprendeu a projetar scripts Bash que são seguros para serem executados novamente e resilientes a falhas transitórias.

Padrões de idempotência:

  • Proteja cada operação com uma verificação de existência ([ -f ], [ -d ], id, getent).
  • Use mkdir -p e outras opções idempotentes integradas quando disponíveis.
  • Use arquivos de bloqueio (mkdir atômico) para impedir execuções simultâneas.
  • Use arquivos de estado (arquivos marcadores para cada etapa) para permitir a retomada após uma falha.

Padrões de novas tentativas com recuo:

  • Sempre defina uma quantidade máxima de tentativas — nunca tente novamente para sempre.
  • Use recuo exponencial: dobre o atraso após cada falha.
  • Adicione variação (aleatoriedade) para impedir a sincronização de efeito manada.
  • Diferencie erros transitórios (tentar novamente) de erros permanentes (falhar rapidamente).

A combinação desses padrões produz scripts prontos para produção: seguros, observáveis e capazes de se recuperar automaticamente.

Perguntas Frequentes

A aula “Scripts idempotentes e lógica de novas tentativas com espera progressiva” é grátis?

Sim — o texto completo de “Scripts idempotentes e lógica de novas tentativas com espera progressiva” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de DevOps Bootcamp, atualize para CoddyKit PRO. O curso de DevOps Bootcamp inclui 4 aulas no total.

O que vou aprender em “Scripts idempotentes e lógica de novas tentativas com espera progressiva”?

Projete operações seguras para executar novamente e adicione espera progressiva exponencial para chamadas externas instáveis. Você pratica DevOps Bootcamp com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar DevOps Bootcamp?

Nenhuma experiência prévia é necessária. DevOps Bootcamp no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.

Quanto tempo leva a aula “Scripts idempotentes e lógica de novas tentativas com espera progressiva”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de DevOps Bootcamp?

Sim. Cada aula de DevOps Bootcamp inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Modo estrito com set -euo pipefail
  2. Manipuladores trap para limpeza e sinais
  3. Arquivos temporários seguros e diretórios de bloqueio
  4. Scripts idempotentes e lógica de novas tentativas com espera progressiva
← Voltar para DevOps Bootcamp