Dateien von Microsoft Azure oder Amazon S3 Batch-downloaden
Gepostet am 20. Februar 2023
6 Minuten • 1079 Wörter • Andere Sprachen: English
Wenn du in der Cloud arbeitest, bist du wahrscheinlich schon irgendwann einmal auf die Anforderung gestoßen, Dateien aus dem Cloud-Speicher im Batch herunterzuladen. Egal, ob du die Berichte des Vorjahres, vom Benutzer hochgeladene Dateien oder andere zur Verfügung stellen möchtest - das Bereitstellen eines Datenarchivs ist ein gar nicht so ungewöhnlicher Anwendungsfall. Als ich versucht habe, eine native Lösung mit Microsoft Azure zu finden, gab es nur Beispiele wie hier (MSDN, extern), wo Dateien entweder einzeln oder parallel heruntergeladen werden, aber nicht als Batch-Download mit nur einem Link bereitgestellt werden konnten.
In diesem Artikel werde ich zuerst meine Reise zur aktuellen Lösung erklären. Im Fall von TLDR, klicke hier.
Die “einfache” serverseitige Lösung
Bei meinem ersten Versuch habe ich versucht, die Anforderungen mit so einem “Best-Practice”-Ansatz zu lösen - und bin leider kläglich gescheitert. Der implementierte Ansatz sah in etwa so aus:
Der Client fordert das Archiv über eine RESTful-API an
- es wird ein zeitlich begrenzter, signierter Link für jede betroffene Datei im Microsoft Azure BLOB generiert
- die Dateien werden in ein temporäres Verzeichnis heruntergeladen
- der gesamte Inhalt wird in eine Zip-Datei komprimiert
- die Zip-Datei wird an den Client geschickt
Für ein paar kleine Dateien funktioniert dieser Ansatz sogar sehr gut. Sobald jedoch die Datenmenge wächst, stößt man auf einige ernsthafte Probleme:
- der HTTP-Server, Loadbalancer, Firewall, … oder sogar der Client können in ein Timeout laufen
- der Server ist damit beschäftigt, die Archiv-Anfrage zu erfüllen und kann deshalb die anderen Anfragen nicht mehr ordnungsgemäß verarbeiten
- der temporäre Speicherplatz kann für den gesamten Download nicht ausreichend sein
- durch den langen Datentransfer wird der Pod von der Infrastruktur als “aufgehangen” interpretiert, sodass er einfach abgeschossen wird
…und wahrscheinlich einige andere, die mir gerade nicht einfallen. Insgesamt ist das keine besondere Nachhaltige Lösung, weil sie eine Menge Potenzial hat, zu versagen (und ich habe vermutlich jede Fehlerquelle gefunden).
Die “etwas-komplexere” serverseitige Lösung
Weil das Ausliefern der Daten in Echtzeit nicht immer möglich ist, war meine nächste Lösung, den Job “auszulagern”, was auch eine Inspiration für meinen Artikel zu KEDA war. Der Ablauf für den Client hat sich nun ein bisschen geändert und dafür konnte die Arbeit komplett im Hintergrund erledigt werden:
Der Client fordert das Archiv über eine RESTful-API an Der Server antwortet, dass die Anforderung im Hintergrund bearbeitet wird
- es wurde ein neuer Eintrag in Redis erstellt
- durch den Eintrag und wurde ein “erstelle-Archiv”-Job getriggert
- KEDA hat einen docker-container gespawned, um den Job zu erledigen
Der Ablauf des Jobs war dann im Grunde genommen der gleiche, der vorher auf dem Server ausgeführt wurde. Anstatt das Ergebnise jedoch an den Client zu senden, wurde es erneut als Zip-Archiv auf dem Azure Blob abgelegt, und dem Client am Ende nur noch der fertige Link mitgeteilt.
Der Hintergrundjob hat allerdings ein paar neue Probleme erzeugt:
- der Client hat nun keine Informationen mehr, über den aktuellen Status (was bedeutet, wir müssen bei Fertigstellung eine Mail verschicken, den Status per Websocket austauschen oder ähnliches)
- über die Zeit können eine Menge docker-container erzeugt werden, sodass der Cluster an seine Grenze kommt
- die Dateien werden nun insgesamt 3 Mal gespeichert (im Original, im temporären Speicher und dann das Ergebnis im Zip-Format)
- bei Übertragungsproblemen muss der Job erneut gestartet werden oder liefert womöglich ein korruptes Archiv aus
- der temporäre Speicherplatz kann auch hier wieder zum Problem werden
Also selbst jetzt, wo wir die Arbeit vom Server weg verlagert haben, bleibt immer noch eine lange Liste an Dingen, die schief gehen können. Außerdem ist nun sowohl der Prozess, als auch das Management der dahinterliegenden Infrastruktur deutlich komplexer geworden. Wenn wir die Dateien doch nur direkt von Azure oder Amazon S3 an den User schicken könnten. Aber das ist ja nicht möglich… oder, vielleicht doch?
Eine Client-basierte Lösung zum Download des Zip-Archivs
Gehen wir noch einmal zwei Schritte zurück zu den ursprünglichen Anforderungen. Wir wollen
- mehrere Dateien von einem BLOB Storage
- die von dem User gedownloaded werden können
- mit der Möglichkeit, ein Limit für parallele Downloads zu steuern
- mit der Möglichkeit, die Downloads zu “throttlen” (z.B. indem wir ein paar Sekunden nach jedem Batch warten)
- in Form eines Zip-Archivs speichern (potentiell mit Unterordnern usw.)
- und dabei den aktuellen Status an den Client kommunizieren
- ohne dabei die “harte Arbeit” in unserer Infrastruktur zu erledigen
Ist das überhaupt möglich, ohne dass Microsoft Azure BLOBs oder Amazon S3 Batch-Downloads anbietet? Ja, das ist es!
Ich habe in diesem Repository einen Proof-of-concept bereitgestellt. Die Demo ist in Vue geschrieben, aber um zu verstehen was passiert, sind wirklich keine Vue-Kenntnisse nötig. Der Ablauf ist wie folgt:
Wenn du auf den Client Batch Download Example
-Button klickst, wird die downloadAndStoreAsZip
-Funktion angetriggert, die versucht, 1000 Dateien von einem Roboter-Bilder-Generator namens robohash.org (welcher übrigens großartig ist) herunterzuladen. Per default ist die downloadInParallel
-Funktion so geschrieben, dass sie fetch-Promises für 10 Bilder pro Batch-Durchlauf generiert, und diese als BLOB in den RAM lädt. Bei Bedarf, kannst du hier auch den “throttle” einstellen, um zwischen den einzelnen Batch-Downloads ein paar Sekunden zu warten. Ansonsten macht die Funktion einfach weiter, bis alles heruntergeladen ist. Währenddessen wird nach jedem Batch-Download der Status aktualisiert, sodass wir dem Client mitteilen können, wie weit wir mit dem Download sind. Als letztes kommt die JSZip-Bibliothek ins Spiel, mit der wir die Dateien in der richtigen Ordnerstruktur zippen und schlussendlich im Browser zum Speichern auf die Festplatte anbieten.
Diese Lösung ist deutlich besser, als alle, die wir davor hatten:
- der Client sieht den Fortschritt
- die Daten werden nur einmal transferiert - und das direkt zum Client
- wir nutzen die Cloud-Infrastruktur von Microsoft Azure BLOBs oder Amazon S3 aus, ohne unsere dabei zu belasten
- wir können die parallelen Downloads beeinflussen und zwischen den Batches warten
- wenn ein Client die Verbindung verliert oder den Download erneut starten muss, ist der Traffic auf Client-Seite
Nicht verheimlichen möchte ich an der Stelle, dass es auch zwei Nachteile bei dieser Methode gibt:
- die Dateien werden als BLOB vollständig in den RAM geladen, sodass es ein Limit auf Client-Seite bezüglich der Dateigröße gibt
- die Daten werden in Originalgröße heruntergeladen, weil die Komprimierung erst auf Client-Seite erfolgt
Für den ersten Nachteil gibt es bei Bedarf eine Lösung, die sogar in der von uns genutzten FileSaver-Bibliothek verlinkt ist: StreamSaver macht es möglich, noch während des Downloads die BLOBs direkt auf die Festplatte des Clients zu schreiben und somit nicht bis zum Ende des kompletten Vorgangs im RAM vorhalten zu müssen.
Zum zweiten Punkt kann ich nach einigem Abwägen sagen, dass ich bereit bin, diesen für meine Zwecke zu akzeptieren, weil die Vorteile hier die Nachteile bei weitem überwiegen.