0Pricing
DevOps Bootcamp · Lektion

Idempotente Skripte und Wiederholungslogik mit Backoff

Entwerfen Sie Operationen, die gefahrlos erneut ausgeführt werden können, und ergänzen Sie bei unzuverlässigen externen Aufrufen einen exponentiellen Backoff.

Idempotente Skripte und Wiederholungslogik mit Backoff ist eine kostenlose DevOps Bootcamp-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des DevOps Bootcamp-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DevOps Bootcamp-Kurs umfasst insgesamt 4 Lektionen.

Was ist Idempotenz und warum ist sie wichtig

Idempotenz bedeutet, dass die mehrfache Ausführung derselben Operation dasselbe Ergebnis liefert wie ihre einmalige Ausführung. Beim Scripting mit Bash ist das entscheidend, weil Skripte abstürzen, Netzwerke ausfallen und Menschen Befehle versehentlich erneut ausführen.

  • Ein nicht idempotentes Skript, das einen Benutzer zweimal anlegt, kann fehlschlagen oder Daten duplizieren.
  • Ein idempotentes Skript prüft zuerst: Existiert das bereits?
  • Idempotente Skripte können sicher in Cron-Jobs, CI-Pipelines und Wiederholungsschleifen verwendet werden.

Die wichtigste Regel lautet: Prüfen Sie, bevor Sie handeln. Jede destruktive oder erzeugende Operation sollte durch eine Vorbedingungsprüfung abgesichert werden.

Das Erstellen von Dateien und Verzeichnissen absichern

Das häufigste Muster für Idempotenz besteht darin, vor dem Erstellen zu prüfen, ob eine Ressource bereits existiert. Bash bietet dafür prägnante Einzeiler.

  • [ -d dir ] — wahr, wenn das Verzeichnis existiert
  • [ -f file ] — wahr, wenn die reguläre Datei existiert
  • mkdir -p — erstellt das Verzeichnis nur, wenn es fehlt (integrierte Idempotenz)

Bevorzugen Sie, sofern verfügbar, integrierte Optionen wie -p und --no-clobber gegenüber manuellen Prüfungen — sie sind atomar und sicher gegenüber Race Conditions.

#!/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

Idempotente Benutzer- und Gruppenverwaltung

Aufgaben der Systemadministration wie das Hinzufügen von Benutzern oder Gruppen müssen idempotent sein — wenn Sie das Skript auf demselben Rechner erneut ausführen, darf es weder einen Fehler geben noch Duplikate anlegen.

  • id username gibt 0 zurück, wenn der Benutzer existiert
  • getent group groupname prüft, ob eine Gruppe vorhanden ist
  • Umschließen Sie jede Operation mit einer Schutzprüfung, damit das Skript sicher erneut ausgeführt werden kann

Dieses Muster bildet die Grundlage von Konfigurationsmanagement-Tools wie Ansible — jede Aufgabe ist eine abgesicherte, idempotente Operation.

#!/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

Sperrdateien verwenden, um gleichzeitige Ausführungen zu verhindern

Selbst ein idempotentes Skript kann Probleme verursachen, wenn zwei Instanzen gleichzeitig ausgeführt werden. Eine Sperrdatei stellt sicher, dass immer nur eine Instanz ausgeführt wird.

  • Erstellen Sie beim Start eine Sperrdatei und entfernen Sie sie beim Beenden.
  • Verwenden Sie einen trap, um die Sperre auch dann zu bereinigen, wenn das Skript unterbrochen wird.
  • mkdir für einen einzelnen Pfad ist auf den meisten Linux-Dateisystemen atomar — und daher zum Sperren sicherer als touch.

Ohne eine Sperre können ein langsamer Cron-Job und ein manuelles erneutes Starten kollidieren und einen gemeinsam genutzten Zustand beschädigen.

#!/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."

Abgeschlossene Schritte mit einer Zustandsdatei verfolgen

Bei mehrstufigen Skripten (Migrationen, Installationen, Bereitstellungen) können Sie mithilfe einer Zustandsdatei verfolgen, welche Schritte bereits abgeschlossen sind. Jeder Schritt prüft die Zustandsdatei vor der Ausführung und schreibt nach dem Abschluss in sie.

  • Kostengünstig und portabel — keine Datenbank erforderlich.
  • Ein fehlgeschlagenes Skript kann dort fortgesetzt werden, wo es angehalten wurde.
  • Speichern Sie den Zustand an einem vorhersehbaren Ort wie /var/lib/myapp/ oder ~/.myapp/state/.

Dieses Muster wird von wichtigen Tools wie apt, cloud-init und Frameworks für Datenbankmigrationen verwendet.

#!/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...'"

Einführung in die Wiederholungslogik

Externe Aufrufe — HTTP-Anfragen, DNS-Abfragen und Cloud-API-Aufrufe — sind grundsätzlich unzuverlässig. Ein einzelner Fehler sollte nicht Ihr gesamtes Skript abbrechen. Eine Wiederholungslogik versucht einen fehlgeschlagenen Befehl automatisch erneut.

  • Naive Wiederholung: N-mal durchlaufen, bis der Befehl erfolgreich ist.
  • Legen Sie immer eine maximale Anzahl von Wiederholungen fest, um Endlosschleifen zu vermeiden.
  • Protokollieren Sie jeden Versuch, damit Fehler diagnostizierbar sind.

Die einfachste Wiederholungsfunktion kapselt einen beliebigen Befehl und versucht ihn mit einer konstanten Verzögerung bis zu einer festgelegten Anzahl von Malen erneut. Für viele Anwendungsfälle reicht das aus, unter Last hat dieser Ansatz jedoch einen entscheidenden Fehler — der im nächsten Abschnitt behandelt wird.

#!/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."

Das Thundering-Herd-Problem

Wenn viele Clients nach einem Fehler gleichzeitig neue Versuche starten, entsteht ein Thundering Herd — alle wiederholen den Vorgang im selben festen Intervall, überlasten den Server exakt im gleichen Moment und machen eine Erholung unmöglich.

  • 100 Skripte wiederholen den Vorgang alle 5 Sekunden → alle 5 Sekunden 100 gleichzeitige Anfragen.
  • Der Server ist bereits überlastet; die synchronisierte Last verschärft die Situation.
  • Die Lösung ist exponentielles Backoff: Verdoppeln Sie die Wartezeit nach jedem Fehler.
  • Fügen Sie Jitter (zufällige Abweichungen) hinzu, um die Wiederholungen der Clients zu entzerren.

Exponentielles Backoff mit Jitter ist der Industriestandard, den AWS SDKs, Google-Cloud-Clients und alle wichtigen verteilten Systeme verwenden.

Exponentielles Backoff implementieren

Exponentielles Backoff verlängert die Wartezeit nach jedem Fehler exponentiell: 1 s, 2 s, 4 s, 8 s, 16 s ... So erhält das entfernte System Zeit zur Erholung, während die Gesamtlast sinkt.

Die Formel: delay = base * (2 ^ attempt)

  • Legen Sie ein Limit (maximale Verzögerung) fest, damit die Wartezeiten nicht unbegrenzt anwachsen.
  • Diese Parameter können Sie anpassen: base_delay, max_delay, max_attempts.

Diese Funktion ist wiederverwendbar – übergeben Sie ihr beliebige Befehle als Argumente.

#!/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"

Jitter zum Backoff hinzufügen

Jitter fügt der Backoff-Verzögerung eine Zufallskomponente hinzu. Selbst bei exponentiellem Backoff würden alle Clients weiterhin synchron erneut versuchen, wenn sie gleichzeitig starten. Jitter durchbricht diese Synchronisierung.

Zwei gängige Jitter-Strategien:

  • Full Jitter: sleep random(0, cap) – maximale Verteilung und geringste Spitzenlast.
  • Equal Jitter: sleep cap/2 + random(0, cap/2) – garantiert eine Mindestwartezeit und verhindert sofortige Überlastung.

Verwenden Sie in Bash $RANDOM (0–32767), um Zufallszahlen zu erzeugen. Skalieren Sie sie mithilfe einer Modulo-Rechnung auf Ihren Verzögerungsbereich.

#!/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."

Idempotenz und Wiederholungsversuche in einem realen Workflow kombinieren

In Produktionsskripten arbeiten Idempotenz und Wiederholungslogik zusammen. Ein typischer Deployment-Workflow könnte folgendermaßen aussehen:

  1. Eine Sperre erwerben (gleichzeitige Ausführungen verhindern)
  2. Statusdateien prüfen (abgeschlossene Schritte überspringen)
  3. Für externe Aufrufe (Download, API, DNS) Wiederholungsversuche mit Backoff verwenden
  4. Schritte erst nach bestätigtem Erfolg als abgeschlossen markieren
  5. Die Sperre über trap freigeben

Durch diese Kombination können Skripte jederzeit sicher erneut ausgeführt werden – nach einem Absturz, einem Timeout oder einem manuellen Abbruch –, ohne das System in einem fehlerhaften Zustand zu hinterlassen.

#!/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."

Nicht wiederholbare Fehler behandeln

Nicht alle Fehler sollten zu einem erneuten Versuch führen. Einen Fehler 404 Not Found oder 403 Forbidden erneut zu versuchen, ist unnötig – ohne manuelles Eingreifen wird der Vorgang niemals erfolgreich sein. Ihre Wiederholungslogik sollte unterscheiden zwischen:

  • Vorübergehenden Fehlern – Netzwerk-Timeout, 503 Service Unavailable, DNS-Fehler → erneut versuchen
  • Dauerhaften Fehlern – 401 Unauthorized, 404 Not Found, ungültige Eingabe → sofort fehlschlagen

Prüfen Sie mit curl den HTTP-Statuscode und überspringen Sie Wiederholungsversuche bei 4xx-Antworten. Verwenden Sie --write-out '%{http_code}', um den Status getrennt vom Antworttext zu erfassen.

#!/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"

Wissensüberprüfung: Auswahl einer Backoff-Strategie

Ein Deployment-Skript lädt ein Release-Artefakt aus einem S3-Bucket herunter. Während eines kürzlichen Vorfalls schlugen 80 Pipeline-Instanzen aufgrund eines kurzen S3-Ausfalls gleichzeitig fehl. Als S3 sich nach 10 Sekunden erholte, wiederholten alle 80 Instanzen den Vorgang im selben Moment. Dadurch kam es zu einer erneuten Überlastung, und der Ausfall verlängerte sich um 3 Minuten.

Welche Strategie für Wiederholungsversuche würde eine solche Kaskade durch eine gleichzeitig anstürmende Client-Gruppe bei zukünftigen Vorfällen am besten verhindern?

Zusammenfassung: Idempotenz und Wiederholungsversuche mit Backoff

In dieser Lektion haben Sie gelernt, Bash-Skripte zu entwerfen, die sicher erneut ausgeführt werden können und gegenüber vorübergehenden Fehlern robust sind.

Muster für Idempotenz:

  • Sichern Sie jede Operation mit einer Existenzprüfung ab ([ -f ], [ -d ], id, getent).
  • Verwenden Sie mkdir -p und andere verfügbare integrierte idempotente Optionen.
  • Verwenden Sie Sperrdateien (atomisches mkdir), um gleichzeitige Ausführungen zu verhindern.
  • Verwenden Sie Statusdateien (Markierungsdateien pro Schritt), damit ein Vorgang nach einem Fehler fortgesetzt werden kann.

Muster für Wiederholungsversuche mit Backoff:

  • Legen Sie immer eine maximale Anzahl von Versuchen fest – wiederholen Sie einen Vorgang niemals unbegrenzt.
  • Verwenden Sie exponentielles Backoff: Verdoppeln Sie die Verzögerung nach jedem Fehler.
  • Fügen Sie Jitter (Zufälligkeit) hinzu, um eine Synchronisierung gleichzeitig anstürmender Clients zu verhindern.
  • Unterscheiden Sie zwischen vorübergehenden (erneut versuchen) und dauerhaften (sofort fehlschlagen) Fehlern.

Durch die Kombination dieser Muster entstehen produktionsreife Skripte: sicher, gut beobachtbar und selbstheilend.

Häufig gestellte Fragen

Ist die Lektion „Idempotente Skripte und Wiederholungslogik mit Backoff“ kostenlos?

Ja — der vollständige Text von „Idempotente Skripte und Wiederholungslogik mit Backoff“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des DevOps Bootcamp-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DevOps Bootcamp-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Idempotente Skripte und Wiederholungslogik mit Backoff“?

Entwerfen Sie Operationen, die gefahrlos erneut ausgeführt werden können, und ergänzen Sie bei unzuverlässigen externen Aufrufen einen exponentiellen Backoff. Du übst DevOps Bootcamp mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um DevOps Bootcamp zu starten?

Keine Vorkenntnisse erforderlich. DevOps Bootcamp auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Idempotente Skripte und Wiederholungslogik mit Backoff“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser DevOps Bootcamp-Lektion Code schreiben und ausführen?

Ja. Jede DevOps Bootcamp-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Strict Mode mit set -euo pipefail
  2. Trap-Handler für Bereinigung und Signale
  3. Sichere temporäre Dateien und Sperrverzeichnisse
  4. Idempotente Skripte und Wiederholungslogik mit Backoff
← Zurück zu DevOps Bootcamp