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
kann zu verschiedenen Zwecken eingesetzt werden:# 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
# 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_read
gelesen. - Alle restart-Dateien werden in das Verzeichnis
$restart_write
geschrieben. - 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_read
und$restart_old
vorhanden. Erst im nächsten Job wird$restart_old
gelö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!