flock als Mutex dreier Vorgänge

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

flock als Mutex dreier Vorgänge

Beitrag von paedubucher » 05.04.2023 07:33:15

Für ein Datenbankbackup, genauer Point in Time Recovery (PostgreSQL) verwende ich Barman. Damit werden die Write-Ahead-Logs von PostgreSQL in ein lokales Verzeichnis kopiert. (Barman empfiehlt ein Setup, wo alle Datenbankserver auf einen zentralen Node sichern. Für die zentrale Sicherung verwende ich jedoch S3 und nicht den Mechanismus von Barman).

Insgesamt finden drei Vorgänge statt, die mit dem Sicherungsverzeichnis interagieren:
  • Der archive_command von PostgreSQL, welcher das WAL wegkopiert.
  • Ein Cronjob von Barman, der einmal pro Minute Aufräumarbeiten vornimmt.
  • Ein Skript, welches das Backup-Verzeichnis alle 15 Minuten auf S3 hochlädt.
Wir haben es mit drei Vorgängen zu tun, die in unterschiedlicher Frequenz ablaufen und Daten ins gleiche Verzeichnis (/var/lib/barman/pg/) schreiben bzw. daraus lesen. Während des S3-Uploads sollte auf das Verzeichnis jedoch nicht schreibend zugegriffen werden können. Allgemein gesprochen: Die drei Vorgänge sollten sich gegenseitig zeitlich ausschliessen. Hierfür verwende ich einen Mutex mit flock und der Datei /tmp/barman-cron.

Ich habe das folgendermassen implementiert:

Der archive_command ist folgendermassen formuliert:

Code: Alles auswählen

test ! -f /var/lib/barman/pg/incoming/%f && flock -n /tmp/barman-cron cp %p /var/lib/barman/pg/incoming/%f
Wenn es die von PostgreSQL erzeugte WAL-Datei noch nicht gibt, wird ein Mutex auf /tmp/barman-cron eingerichtet und die Datei an die Destination kopiert. (Mit -n wird der Vorgang abgebrochen, sofern der Mutex bereits besteht.)

Der Barman-Cronjob sieht folgendermassen aus:

Code: Alles auswählen

*/1 * * * * flock -n /tmp/barman-cron /opt/barman/bin/barman cron
D.h. der Vorgang wird nur ausgeführt, wenn ein Lock auf /tmp/barman-cron eingerichtet werden kann.

Und schliesslich der S3-Upload, den ich hier etwas vereinfacht wiedergebe:

Code: Alles auswählen

flock -F /tmp/barman-cron flock -n /tmp/some-other-mutex-for-some-reason ionice -c2 -n6 nice -n 5 /usr/local/bin/s3-backup /var/lib/barman/pg
Hier kommen zwei flock-Befehle zum Einsatz: Der vordere (flock -F /tmp/barman-cron) realisiert den Mutex zu den anderen beiden geschilderten Vorgängen. Mit dem Parameter -F hält der abgeforkte Prozess den Lock aufrecht. Der zweite (flock -n /tmp/some-other-mutex-for-some-reason) wird vom S3-Backup-Skript selber benötigt und hat nichts mit dem vorliegenden Problem zu tun.

Schliesslich wird das Skript mit ionice und nice gestartet, um das Backup herunterzupriorisieren. (Das ganze S3-Setup war schon früher da, ich habe nur den ersten Mutex hinzugefügt.)

Nun ist das S3-Skript schlau genug, um Änderungen während des Hochladevorgangs zu bemerken. Solche Änderungen passieren, wenn ein anderer Prozess schreibend auf /var/lib/barman/pg zugreift. Und leider passieren solche Änderungen doch ab und zu, wodurch ich mehrmals die Woche eine entsprechende Warnung erhalte. Der Mutex funktioniert offenbar nicht wie gewünscht.

Sieht jemand meinen Denk- bzw. Umsetzungsfehler?
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Benutzeravatar
heisenberg
Beiträge: 3567
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

bash flock simulation script

Beitrag von heisenberg » 05.04.2023 17:59:51

In Deiner Form, die lt. manpage ok ist, habe ich flock noch nicht verwendet. Ich verwende immer die 3. Form. Sieht aber grundsätzlich erst mal so aus, wie es soll.

Gedanken:
  • Mag der Parameter archive_command bei Postgres die vielen Sonderzeichen, die Du da in der Zeile hast? (Ich habe das bei mir in ein Script ausgelagert, dem ich die Parameter (%f, %p) übergebe.).
  • Testweise würde ich den zweiten flock im letzten komplexen Befehl mal weglassen und den flock als normalen flock -n (statt flock -F) laufen lassen
  • Ich würde vielleicht jeden einzelnen Befehl gegen ein anderes flock Testscript laufen lassen und schauen, ob sich das jeweils wie es soll beendet, weil es kein Lock bekommt.
Hier nochmal ein Script, dass das locking simuliert. Klappt einwandfrei.

Code: Alles auswählen

#!/bin/bash

declare -rx      SELF="$(basename $0)"
declare -rx      BASE="$HOME/flock_test"
declare -rx LOCK_FILE="$BASE/tmp/pg_archive.lock"
declare -rx  LOG_FILE="$BASE/flock.log"

#colors
declare -rx       END="\033[0m"
declare -rx       RED="\033[0;31m"
declare -rx     GREEN="\033[0;32m"

red()    { echo -e "${RED}${1}${END}";   }
green()  { echo -e "${GREEN}${1}${END}"; }

mylog()  { echo "$(date) : $SELF : $*" >>$LOG_FILE; }

myinit() {

        if ! [ -d "$BASE" ]; then
                mkdir -p "$BASE" "$BASE/tmp" "$BASE/bin"
                ln $SELF "$BASE/bin/upload"
                ln $SELF "$BASE/bin/cron"
                ln $SELF "$BASE/bin/archive"
                chmod u+rx "$BASE/bin/upload" "$BASE/bin/cron" "$BASE/bin/archive"
        fi
}

main() {

        myinit
        while :;do

                mylog "starting"
                mylog "Waiting for lock"
                exec 200>"$LOCK_FILE"
                time_waited=0

                while : ; do
                        if flock -n 200 ; then
                                mylog "$(red "acquired lock after $time_waited seconds")"

				# do some work here - exclusive lock needed from here
                                sleep $(( $RANDOM % 15 + 1 ))
                                # work is done now - exclusive lock no longer needed from here

                                flock -u 200
                                mylog "$(green "released lock")"
                                break
                        fi
                        sleep 1
                        ((time_waited=$time_waited+1))
                done
                mylog "finished"

                sleep $(( $RANDOM % 15 + 1 ))

        done
}

main
Bild
Zuletzt geändert von heisenberg am 06.04.2023 18:20:03, insgesamt 1-mal geändert.
Jede Rohheit hat ihren Ursprung in einer Schwäche.

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: bash flock simulation script

Beitrag von paedubucher » 06.04.2023 08:35:17

heisenberg hat geschrieben: ↑ zum Beitrag ↑
05.04.2023 17:59:51
In Deiner Form, die lt. manpage ok ist, habe ich flock noch nicht verwendet. Ich verwende immer die 3. Form. Sieht aber grundsätzlich erst mal so aus, wie es soll.
Du verwendest also jeweils flock -F, verstehe ich das richtig?
heisenberg hat geschrieben: Gedanken:
  • Mag der Parameter archive_command bei Postgres die vielen Sonderzeichen, die Du da in der Zeile hast? (Ich habe das bei mir in ein Script ausgelagert, dem ich die Parameter (%f, %p) übergebe.).
  • Testweise würde ich den zweiten flock im letzten komplexen Befehl mal weglassen und den flock als normalen flock -n (statt flock -F) laufen lassen
  • Ich würde vielleicht jeden einzelnen Befehl gegen ein anderes flock Testscript laufen lassen und schauen, ob sich das jeweils wie es soll beendet, weil es kein Lock bekommt.
Der archive_command funktioniert einwandfrei. Ich habe schon erfolgreiche Restore-Tests basierend darauf gemacht.

Den zweiten flock-Aufruf konnte ich auf deine Anregung hin sogar ganz eliminieren, indem ich jetzt dafür die gleiche Datei verwende. (Diese Datei wird noch von einem Verifizierungsskript einmal die Woche verwendet.) Der Befehl sieht jetzt so aus:

Code: Alles auswählen

flock -F /tmp/barman-cron ionice -c2 -n6 nice -n 5 /usr/local/bin/s3-backup /var/lib/barman/pg
Danke auch für das Testskript! Ich werde gleich schauen, wie ich das in meinem Setup integrieren kann.

Eine Vermutung wäre noch, dass ich überall -F statt -n verwenden sollte, da sonst der Lock zu früh weggeht.

Nachtrag: Ich glaube, ich habe das Problem jetzt erkannt! Mit flock funktioniert alles, wie es sollte. Das Problem ist der Befehl barman cron, bzw. dass er nicht nur als Cronjob vom PostgreSQL-Benutzer ausgeführt wird, sondern auch von root. Das habe ich gerade per Zufall auf dem Server entdeckt.

Wie konnte es soweit kommen? Nun, ich verwende Puppet als Konfigurationsmanagementsystem. Zuerst habe ich den Cronjob nur für root laufen lassen. Ich fand es dann aber sinnvoller, den mit dem Benutzer postgres laufen zu lassen, da dessen Rechte dafür ausreichen. Das ganze habe ich im Konfigurationsmanagementsystem entsprechend gesetzt. Dummerweise wurde dadurch der alte Cronjob von root nicht entfernt. :facepalm:

D.h. nicht der "geflockte" Cronjob war das Problem, sondern ein anderer.

Ich prüfe das noch im Detail; aber es wäre schon einmal eine Erklärung, warum ich das Problem nur auf den produktiven Servern und nicht lokal nachvollziehen konnte, da ich die lokalen Server immer wieder von Grund auf neu provisioniere.
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Benutzeravatar
heisenberg
Beiträge: 3567
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: flock als Mutex dreier Vorgänge

Beitrag von heisenberg » 06.04.2023 09:28:17

Den Schalter -F bei flock habe ich noch nicht verwendet. Ich nutze immer nur flock -n.
Jede Rohheit hat ihren Ursprung in einer Schwäche.

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: flock als Mutex dreier Vorgänge

Beitrag von paedubucher » 06.04.2023 11:40:03

heisenberg hat geschrieben: ↑ zum Beitrag ↑
06.04.2023 09:28:17
Den Schalter -F bei flock habe ich noch nicht verwendet. Ich nutze immer nur flock -n.
Der sollte für meine Fälle auch genügen. Ich starte jetzt barman cron nach einem sleep 1, damit das seltener laufende Backup zuerst den Lock erhält. Ersterer Befehl wird dann halt nicht immer ausgeführt, dafür läuft das Backup ohne konkurrierende Änderungen durch.

Das Problem scheint wirklich der zweite Cronjob gewesen zu sein, den ich manuell rauslöschen musste.
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

DaCoda
Beiträge: 172
Registriert: 09.07.2019 21:58:10

Re: flock als Mutex dreier Vorgänge

Beitrag von DaCoda » 06.04.2023 18:05:06

Also ein Fehler ist, dass du wahrscheinlich -n nicht verstanden hast. Das sorgt lediglich dafür, dass flock 1 als exit code zurückgibt, falls der Lock nicht frei ist. Du musst also den exit code testen und bei 1 das Skript beenden.

Ich bin nicht am PC um es zu testen, aber wahrscheinlich brauchst du außerdem --exclusive.

Benutzeravatar
heisenberg
Beiträge: 3567
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: flock als Mutex dreier Vorgänge

Beitrag von heisenberg » 06.04.2023 18:15:34

DaCoda hat geschrieben: ↑ zum Beitrag ↑
06.04.2023 18:05:06
Also ein Fehler ist, dass du wahrscheinlich -n nicht verstanden hast. Das sorgt lediglich dafür, dass flock 1 als exit code zurückgibt. Du musst also den exit code testen und bei 1 das Skript beenden.

Ich bin nicht am PC um es zu testen, aber wahrscheinlich brauchst du außerdem --exclusive.
-n steht für Non-Blocking. Das habe ich bewusst gewählt.
--exclusive ist default

Mein Script ist dafür da, dass es Scripte, die mittels flock gesperrte Dateien verwenden und deren Interaktion in einer Schleife mit mehreren gleichzeitig laufenden Instanzen simuliert. D. h. es werden mehrfach gesperrte Teile des Programms aufgerufen, die dann immer wieder warten, bis die exklusive Sperre verfügbar ist, "arbeiten" und die Sperre dann wieder freigeben. Dabei verwende ich Non-Blocking Locks, warte und prüfe das wiederkehrend mit Sleep-Pausen. Das kann man natürlich auch "Blocking" machen, dann ist das nur ein Befehl, statt einer Schleife. Ich habe mich in dem Fall zu letzterem entschieden. Der Vorteil einer Non-Blocking-Operation ist, dass die Kontrolle dann beim Programm bleibt und man in diesem entscheiden kann, wie man auf ein nicht-erhalten der Sperre reagieren kann.

Das Script ist explizit kein Beispiel für eine normale Verwendung.
Jede Rohheit hat ihren Ursprung in einer Schwäche.

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: flock als Mutex dreier Vorgänge

Beitrag von paedubucher » 11.04.2023 09:23:12

DaCoda hat geschrieben: ↑ zum Beitrag ↑
06.04.2023 18:05:06
Also ein Fehler ist, dass du wahrscheinlich -n nicht verstanden hast. Das sorgt lediglich dafür, dass flock 1 als exit code zurückgibt, falls der Lock nicht frei ist. Du musst also den exit code testen und bei 1 das Skript beenden.
Wenn ich meinen Befehl als Parameter von flock mitgebe, und die Sperre nicht aufgebaut werden kann, wird auch der Befehl nicht ausgeführt. Als Demonstration führe ich folgenden Befehl in einer Shell aus:

Code: Alles auswählen

$ flock -n flock.txt sleep 100
In einer zweiten Shell führe ich nun folgendes aus:

Code: Alles auswählen

$ flock -n flock.txt echo 'foo'
$ echo $?
-1
Und nach einer Wartezeit der angebebenen 100 Sekunden noch einmal:

Code: Alles auswählen

$ flock -n flock.txt echo 'foo'
foo
echo $?
0
Damit sollte also alles in Ordnung sein.
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Antworten