parallel-fs-ops.template.yml|Anhang (2,8 KB)
Lassen Sie uns über ein Skalierungsproblem sprechen, das schmerzhaft offensichtlich wird, sobald eine Discourse-Seite eine große Upload-Bibliothek ansammelt.
Was früher Minuten dauerte, bis der chown-Befehl über ein riesiges Upload-Verzeichnis lief, dauert jetzt Sekunden!
Hintergrund
Discourse-Rebuilds können rekursive Operationen ausführen, wie z. B.:
chown -R ...
chmod -R ...
Konkreter gesagt, diese Zeile in templates/web.template.yml:
- chown -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp
Diese Befehle durchlaufen das Dateisystem seriell, eines nach dem anderen.
Das ist für eine kleine Installation völlig in Ordnung. Aber wenn shared/uploads Hunderttausende – oder Millionen – von Dateien enthält, können rekursive Ownership- und Berechtigungsoperationen einen Deploy dominieren. CPU-, Speicher- und Netzwerkressourcen mögen verfügbar sein, doch ein Prozess durchläuft den gesamten Baum Inode für Inode.
Für Upload-lastige Communities kann das folgende Ergebnisse haben:
- Extrem lange Rebuilds
- Längere Wartungsfenster
- Verzögerte Deploys und Sicherheitsupdates
- Schlechte Auslastung schneller oder verteilter Speicher
- Ein Deploy, das festzuhängen scheint, während es einen enormen Dateibaum verarbeitet
- Besonders schmerzhafte Leistung auf NFS, JuiceFS, CephFS und anderen Remote-Dateisystemen
Der frustrierende Teil ist, dass viele dieser Dateien unabhängig voneinander sind. Ihre Berechtigungen könnten parallel verarbeitet werden.
Die Lösung: Parallel Filesystem Operations Template
Ich habe ein pups-Template erstellt, das rekursive chmod- und chown-Operationen transparent durch parallele find- und xargs-Pipelines ersetzt.
Die Wrapper melden sich jedes Mal, wenn sie eine rekursive Operation abfangen:
echo "[parallel-fs-ops] chmod -R override active: $*" >&2
und:
echo "[parallel-fs-ops] chown -R override active: $*" >&2
Dieses eckige Klammer-Präfix macht die Optimierung in einem Deploy-Log leicht erkennbar.
Wie ein Deploy aussieht
Am Anfang des Deploys bestätigt das Template, welche Binärdateien die späteren Dateisystemoperationen übernehmen werden:
[parallel-fs-ops] chmod -> /usr/local/bin/chmod
[parallel-fs-ops] chown -> /usr/local/bin/chown
Wenn ein Upstream-Template später eine rekursive Berechtigungsänderung ausführt, enthält die Deploy-Ausgabe eine Zeile wie diese:
[parallel-fs-ops] chmod -R override active: -R 0755 /var/www/discourse/public
Eine rekursive Ownership-Änderung erzeugt:
[parallel-fs-ops] chown -R override active: -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp
Bei einer upload-lastigen Installation könnten Sie etwas Ähnliches wie Folgendes sehen:
[parallel-fs-ops] chown -R override active: -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp
Die genauen Pfade und Argumente hängen von den verwendeten Templates ab, aber der wichtige Teil ist das sichtbare Markierungszeichen:
[parallel-fs-ops]
Ohne das Template kann der Deploy während einer rekursiven Dateisystemoperation lange pausieren zu scheinen. Mit dem Template teilt Ihnen das Log mit, dass:
- Der Wrapper korrekt installiert wurde.
- Eine rekursive Operation erkannt wurde.
- Die parallele Implementierung aktiv ist.
- Die verarbeiteten Originalargumente sichtbar sind.
Dies ist besonders wertvoll bei der Fehlerbehebung, da es eine langsame parallele Dateisystemdurchlaufung von einem aufgehängten Build unterscheidet.
Nach Abschluss der Operation fährt die Deployment mit ihrer normalen pups-Ausgabe fort. Der Wrapper selbst gibt nicht eine Zeile pro Datei aus, sodass selbst ein Baum mit Millionen von Uploads das Deploy-Log nicht überflutet.
Das Template
run:
- file:
path: /usr/local/bin/chmod
chmod: "+x"
contents: |
#!/bin/bash
if [[ "$*" =~ (^|[[:space:]])-R([[:space:]]|$) ]]; then
echo "[parallel-fs-ops] chmod -R override active: $*" >&2
args=()
for arg in "$@"; do
[[ "$arg" != "-R" ]] && args+=("$arg")
done
mode="${args[0]}"
targets=("${args[@]:1}")
[[ ${#targets[@]} -eq 0 ]] && targets=(".")
find "${targets[@]}" -print0 |
xargs -0 -n 32 -P 128 /bin/chmod "$mode"
else
exec /bin/chmod "$@"
fi
- file:
path: /usr/local/bin/chown
chmod: "+x"
contents: |
#!/bin/bash
if [[ "$*" =~ (^|[[:space:]])-R([[:space:]]|$) ]]; then
echo "[parallel-fs-ops] chown -R override active: $*" >&2
args=()
for arg in "$@"; do
[[ "$arg" != "-R" ]] && args+=("$arg")
done
owner="${args[0]}"
targets=("${args[@]:1}")
[[ ${#targets[@]} -eq 0 ]] && targets=(".")
find "${targets[@]}" -print0 |
xargs -0 -n 32 -P 128 /bin/chown "$owner"
else
exec /bin/chown "$@"
fi
- exec:
cmd: |
echo "[parallel-fs-ops] chmod -> $(command -v chmod)"
echo "[parallel-fs-ops] chown -> $(command -v chown)"
Das Template installiert Wrapper in /usr/local/bin, was normalerweise vor /bin in der PATH erscheint.
Wenn eine normale, nicht-rekursive Operation angefordert wird, delegiert der Wrapper direkt an das Standard-Utility:
exec /bin/chmod "$@"
Wenn -R vorhanden ist, entfernt es den rekursiven Flag, enumeriert die Ziele sicher mit Null-Trennzeichen und verarbeitet Chargen parallel:
find "${targets[@]}" -print0 |
xargs -0 -n 32 -P 128 /bin/chmod "$mode"
Dies funktioniert auch, wenn pups Befehle über /bin/sh aufruft. Der Bash-Shebang des Wrappers wird respektiert, wenn die ausführbare Datei gestartet wird, auch wenn das aufrufende Shell Dash ist.
Warum dies am wichtigsten ist, wenn Sie viele Uploads haben
Upload-lastige Communities sind genau der Ort, an dem das Deploy-Verhalten skalierbar sein muss.
Ein lang laufendes Forum kann enthalten:
- In Posts über Jahre eingebettete Bilder
- Avatare und Profilhintergründe
- Originale und optimierte Bildvarianten
- Video- und Audio-Anhänge
- Dokumente und Archive
- Sichere Uploads
- Von Plugins verwaltete Medien
- Multisite-Upload-Bäume
Die Menge an Anwendungscode kann relativ stabil bleiben, während die Anzahl der hochgeladenen Dateisystemobjekte weiter wächst. Die Durchlaufung des Dateisystems – nicht die Kompilierung oder Container-Erstellung – kann schließlich die dominante Deploy-Kostenstelle werden.
Dies ist ein ungewöhnliches Skalierungsproblem: Je erfolgreicher und inhaltsreicher die Community wird, desto teurer können routinemäßige operative Arbeiten werden.
Warum ein Template benötigt wird
Änderungen an .bashrc oder das Setzen von BASH_ENV lösen dies nicht zuverlässig. pups führt run-Befehle über /bin/sh aus, und Dash lädt weder Bash-Konfigurationen noch versteht es Bash-spezifische Funktionen.
Ein Template bietet eine wiederholbare Möglichkeit, die Wrapper früh genug zu installieren, damit nachfolgende rekursive Operationen – einschließlich solcher aus Upstream-Templates – über die parallele Implementierung aufgelöst werden:
templates:
- "templates/postgres.template.yml"
- "templates/redis.template.yml"
- "templates/web.template.yml"
- "containers/parallel-fs-ops.template.yml"
Konfigurierbare Optionen
Parallele Verarbeitungs-Optionen
Das Template verwendet:
find "${targets[@]}" -print0 |
xargs -0 -n 32 -P 128 /bin/chmod "$mode"
Die relevanten xargs-Parameter sind:
| Option | Zweck |
|---|---|
-0 |
Liest null-getrennte Pfade, die von find -print0 erzeugt werden. Dies behandelt Dateinamen mit Leerzeichen, Anführungszeichen, Tabs oder Zeilenumbrüchen sicher. |
-n 32 |
Gibt höchstens 32 Pfade an jeden chmod- oder chown-Aufruf weiter. Dies ist die Chargengröße. |
-P 128 |
Erlaubt bis zu 128 chmod- oder chown-Prozesse, die gleichzeitig ausgeführt werden. Dies ist das Parallelitätsniveau. |
Zusammen bedeutet -n 32 -P 128, dass bis zu 128 Prozesse gleichzeitig ausgeführt werden können, wobei jeder Prozess eine Charge von bis zu 32 Pfaden bearbeitet. Daher können etwa 4.096 Pfade gleichzeitig aktiv über Befehlschargen verteilt sein.
Auswahl von -n
-n steuert, wie viel Arbeit jedem Befehl zugewiesen wird:
- Niedrigere Werte bieten eine feinere Arbeitsverteilung, starten aber mehr Prozesse.
- Höhere Werte reduzieren die Prozessstart-Overhead, erzeugen aber größere, weniger gleichmäßig verteilte Chargen.
-n 1führt einenchmod- oderchown-Befehl pro Pfad aus.-n 32ist ein vernünftiger Ausgangspunkt, um Chargierung und Parallelität auszugleichen.- Sehr große Werte können die Wirksamkeit von
-Pverringern, da weniger Gesamtschargen erstellt werden.
Auswahl von -P
-P steuert, wie viele Befehle gleichzeitig ausgeführt werden dürfen:
- Niedrigere Werte reduzieren die Last auf CPU und Dateisystem.
- Höhere Werte können die Leistung auf schnellen oder verteilten Speichern verbessern.
- Exzessive Parallelität kann Festplatten überlasten, einen Metadaten-Server sättigen oder die Leistung verschlechtern.
-P 1ist effektiv eine serielle Ausführung.-P 8oder-P 16ist ein konservativer Ausgangspunkt.-P 32kann für schnellen SSD-basierten Speicher geeignet sein.-P 128sollte nur verwendet werden, wenn das Dateisystem und der Host diese Parallelität aufrechterhalten können.
Die besten Werte hängen von der Dateisystemlatenz, der Metadatenleistung, der CPU-Kapazität und der Anzahl der Dateien ab. Beide sollten idealerweise konfigurierbar und für die jeweilige Installation benchmarked werden.
Zu viel Parallelität kann ein Dateisystem überlasten, Metadaten-Server sättigen oder die Deployment-Leistung verschlechtern. Chargengröße und Parallelität sollten daher konfigurierbar sein.
Dieses Template ist ein praktischer Workaround, aber der größere Vorschlag ist breiter gefasst:
Könnte Discourse offiziell konfigurierbare Parallelität für große rekursive Dateisystemoperationen während Deploys unterstützen?
Eine Upstream-Implementierung könnte:
- Nur bekannte große Verzeichnisbäume parallelisieren
- Vermeiden, unveränderte Upload-Bäume unnötig zu durchlaufen
- Parallelität konfigurierbar machen
- Lokale gegenüber netzwerkgestützten Dateisystemen erkennen
- Die vollständige Argumentsemantik von
chmodundchownbeibehalten - Regelmäßigen Fortschritt für sehr große Bäume ausgeben
- Zeiten aufzeichnen, damit Administratoren Deploy-Engpässe identifizieren können
Wichtige Warnung
Der oben genannte Wrapper konzentriert sich auf die rekursiven Befehlsformen, die von unserem Build-Prozess verwendet werden. Er ist keine vollständige Neuimplementierung jeder möglichen chmod- oder chown-Optionskombination.
Er sollte gegen die exakten Befehle getestet werden, die von den Templates einer Seite generiert werden, bevor er produktiv eingesetzt wird. Betreiber sollten mit konservativer Parallelität beginnen und die Auswirkungen auf ihren Speicher messen.
Aber das zugrunde liegende Problem ist real: Serielle rekursive Metadatenoperationen skalieren nicht gut, wenn eine Community einen massiven Upload-Baum angesammelt hat.
Viel Glück, und ich schätze alle Kommentare oder Vorschläge (auch wenn ich vielleicht die Bemühungen eines anderen dupliziert habe, würde ich auch darauf verweisende Hinweise schätzen)!
Grüße!