Batch-Job-Ketten
In einer Batch-Job-Kette stellt jeder Job (typischerweise als letztes Kommando) einen Folge-Job in die Warteschlange, bis eine Ende-Bedingung erreicht ist. Die in der Kette verwendeten Job-Skripte können verschieden sein; oder ein einziges Job-Skript kann sich selbst als Folge-Job aufrufen. Im ersten Fall können Jobs in Abhängigkeit von den Vorgängern gestarten werden. Der zweite Fall wird angewendet, um eine lange Rechnung in Teil-Jobs mit vertretbaren Job-Laufzeiten zu bearbeiten. Voraussetzung dafür ist, dass die Rechnung an definierten Stellen unterbochen werden kann, indem das (Simulations-) Programm seinen Zustand auf Festplatte schreibt („einen checkpoint schreibt“), der als Anfangszustand des folgende Laufs wieder eingelesen werden kann („einen restart durchführen“).
Auf dieser Seite wird zweite Fall (checkpoint/restart in einem sich selbst aufrufenden Skript) behandelt. Ein dafür geeignetes Skript benötigt die Elemente:
- Fehlerbehandlung
- Festlegung von Job-Ketten-Verzeichnis und -Dateinamen
- Job-Zähler
- Backup der restart-Dateien
- Ende-Bedingung
Beim (ersten) Testen einer Job-Kette ist wichtig:
Die folgenden Abschnitte enthalten beispielhafte Skript-Zeilen für die GNU bash, anhand derer sich Job-Ketten-Skripte erstellen lassen, die an eigene Bedürfnisse angepasst sind.
Fehlerbehandlung
Falls ein Fehler auftritt, der verhindert, dass der Folge-Job nicht
erfolgreich laufen kann, muss die Kette abbrechen (d.h. der Folge-Job
darf nicht gestartet werden). Um dies zu erreichen, kann man in der
bash
die Optionen -e und -u setzen:
set -e # Abbruch, falls ein Kommando mit Fehlerstatus endet set -u # Abbruch, falls auf eine undefinierte Variable zugegriffen wird
Durch Setzen dieser beiden Optionen führt jeder Fehlerstatus zum Abbruch. (Man erkennt hier die Wichtigkeit eines korrekten exit-Status). Ausnahmen kann man beispielsweise folgendermaßen zulassen:
Kommando-das-mit-Fehlerstatus-enden-darf || true
Falls das Kommando mit Fehlerstatus endet, wird das Kommando
ausgeführt, das auf „||“ folgt. Als Kommando wird das
Kommando true ausgeführt, das immer mit Erfolgstatus
endet.
Festlegung von Job-Ketten-Verzeichnis und -Dateinamen
Es ist nützlich mit einer klaren Verzeichnis- und Namens-Struktur zu arbeiten, beispielsweise:
# Initialisierung:
chain_name=chain1 # Name der Job-Kette
# Abgeleitete Namen:
chain_dir=$WORK/runs/$chain_name # Verzeichnis für diese Job-Kette
restart_write=$chain_dir/restart_write # Verzeichnis für restart-Dateien
restart_read=$chain_dir/restart_read # Verzeichnis für restart-Dateien
restart_old=$chain_dir/restart_old # Verzeichnis für restart-Dateien
job_file=$chain_dir/$chain_name.job # (dieses) Job-Ketten-Skript
count_file=$chain_dir/$chain_name.count # enthält den Job-Zähler
stop_file=$chain_dir/$chain_name.STOP # dient zum Anhalten der Kette
Job-Zähler
Ein Job-Zähler
# Zähler initialisieren ...
if [[ -f $count_file ]]
then
job=$(cat $count_file)
job=$(($job + 1))
else
job=1
fi
# ... und sichern
echo $job > $count_file
kann zu verschiedenen Zwecken eingesetzt werden:
# Behandlung von Sonderfällen:
if (( $job == 1 ))
then
...
fi
# Erzeugung von Dateinamen
result_file=$chain_dir/$chain_name.results.$job
# Verwendung als Ende-Bedingung:
max_jobs=100 # (steht wahrscheinlich eher im Initialisierungsteil)
if (( $job >= $max_jobs ))
then
touch $stop_file # $stop_file anlegen
fi
Backup der restart-Dateien
Wenn die restart-Information beschädigt ist, müsste die Job-Kette von vorne gerechnet werden. Um diese zu verhindern, wird eine Sicherungskopie der restart-Dateien benötigt. Man kann explizit Kopien machen oder lediglich Verzeichnisse umbenennen (was bei großen restart-Dateien keine nennenswert zusätzliche Zeit erfordern würde). Man kann beispielsweise folgendermaßen vorgehen:
- Alle restart-Dateien werden aus dem Verzeichnis
$restart_readgelesen. - Alle restart-Dateien werden in das Verzeichnis
$restart_writegeschrieben. - Die restart-Dateien des vorletzen Jobs befinden sich in
$restart_old. - Am Ende eines Jobs steht die aktuelle restart-Information in
$restart_write. Die beiden vorangegangen checkpoints sind in$restart_readund$restart_oldvorhanden. Erst im nächsten Job wird$restart_oldgelöscht. (Falls eine Job nicht erfolgreich beendet wird, müssen die passenden Verzeichnisnamen wieder hergestellt werden.) - Zu Beginn eines Jobs werden die Verzeichnisse umbenannt:
if (( $job > 1 )) then rm -rf $restart_old # (kein Fehler in Job 2 wegen -f) mv $restart_read $restart_old mv $restart_write $restart_read fi mkdir $restart_write # leeres Verzeichnis für die neuen restart-Dateien
Endebedingung
Als Ende-Bedingung wird hier eingeführt, dass die
Datei $stop_file existiert. Der Folge-Job wird nur
abgeschickt, falls $stop_file nicht existiert:
if [[ ! -f $stop_file ]]
then
submit $job_file # submit-Kommando des jeweiligen Batch-Systems
fi
$stop_file kann auf verschiedene Weisen angelegt werden:
- im Job-Skript (z.B. wenn eine vorgebene Anzahl von Jobs abgearbeitet ist, siehe Abschnitt Job-Zähler),
- vom (Simulations-) Programm (wenn es eine Ende-Bedingung feststellt),
- auf der Kommandozeile (um die Kette manuell zu stoppen).
Endlos-Job-Kette vermeiden
Insbesondere beim ersten Testen von Job-Ketten kann es passieren,
dass die Ende-Bedingung nicht funktioniert und man eine Endlos-Job-Kette
erzeugt. Dabei werden Folge-Jobs gestartet, bevor man den gerade in
Ausführung befindlichen Job löschen kann. In diesem Fall, kann man die
Kette beenden, indem man dafür sorgt, dass
das Job-Skript $job_file nicht mehr gefunden wird
(z.B. durch Umbenennung) and sich damit auch nicht erneut in die
Warteschlange stellen kann. Damit man diese Notlösung zur Verfügung
hat, sollte man Job-Ketten-Skripte nicht in einer pipe
über stdin an das submit-Kommando geben sondern immer
als explizit vorhandenes Skript, das sich im Notfall umbennen lässt!