Als Entwickler stehen wir oft vor Herausforderungen, wenn es um die Verarbeitung und Bereitstellung umfangreicher Daten geht. Bei Kamero haben wir kürzlich einen erheblichen Engpass in unserer Dateibereitstellungspipeline behoben. Unsere Anwendung ermöglicht es Benutzern, Tausende von Dateien, die mit einem bestimmten Ereignis verknüpft sind, als eine einzige ZIP-Datei herunterzuladen. Diese Funktion, die von einer Node.js-basierten Lambda-Funktion unterstützt wird, die für das Abrufen und Komprimieren von Dateien aus S3-Buckets verantwortlich ist, hatte mit Speicherbeschränkungen und langen Ausführungszeiten zu kämpfen, als unsere Benutzerbasis wuchs.
Dieser Beitrag beschreibt detailliert unseren Weg von einer ressourcenhungrigen Node.js-Implementierung zu einer schlanken und blitzschnellen Go-Lösung, die massive S3-Downloads effizient verarbeitet. Wir werden untersuchen, wie wir unser System optimiert haben, um Benutzern ein nahtloses Erlebnis zu bieten, wenn sie eine große Anzahl von Dateien von bestimmten Veranstaltungen anfordern, alles verpackt in einem praktischen einzigen Zip-Download.
Unsere ursprüngliche Lambda-Funktion hatte bei der Verarbeitung großer ereignisbasierter Dateisätze mehrere kritische Probleme:
Unsere ursprüngliche Implementierung verwendete die s3-zip-Bibliothek, um ZIP-Dateien aus S3-Objekten zu erstellen. Hier ist ein vereinfachter Ausschnitt davon, wie wir Dateien verarbeitet haben:
const s3Zip = require("s3-zip"); // ... other code ... const body = s3Zip.archive( { bucket: bucketName }, eventId, files, entryData ); await uploadZipFile(Upload_Bucket, zipfileKey, body);
Obwohl dieser Ansatz funktionierte, wurden alle Dateien vor dem Erstellen der ZIP-Datei in den Speicher geladen, was zu einer hohen Speicherauslastung und potenziellen Fehlern wegen unzureichendem Arbeitsspeicher bei großen Dateisätzen führte.
Wir haben uns entschieden, unsere Lambda-Funktion in Go neu zu schreiben und dabei deren Effizienz und integrierte Parallelitätsfunktionen zu nutzen. Die Ergebnisse waren verblüffend:
Wir haben das AWS SDK für Go v2 verwendet, das im Vergleich zu v1 eine bessere Leistung und eine geringere Speichernutzung bietet:
cfg, err := config.LoadDefaultConfig(context.TODO()) s3Client = s3.NewFromConfig(cfg)
Gos Goroutinen ermöglichten es uns, mehrere Dateien gleichzeitig zu verarbeiten:
var wg sync.WaitGroup sem := make(chan struct{}, 10) // Limit concurrent operations for _, photo := range photos { wg.Add(1) go func(photo Photo) { defer wg.Done() semDieser Ansatz ermöglicht es uns, mehrere Dateien gleichzeitig zu verarbeiten und gleichzeitig den Grad der Parallelität zu kontrollieren, um eine Überlastung des Systems zu verhindern.
3. Streaming-Zip-Erstellung
Anstatt alle Dateien in den Speicher zu laden, streamen wir den ZIP-Inhalt direkt nach S3:
pipeReader, pipeWriter := io.Pipe() go func() { zipWriter := zip.NewWriter(pipeWriter) // Add files to zip zipWriter.Close() pipeWriter.Close() }() // Upload streaming content to S3 uploader.Upload(ctx, &s3.PutObjectInput{ Bucket: &destBucket, Key: &zipFileKey, Body: pipeReader, })Dieser Streaming-Ansatz reduziert die Speichernutzung erheblich und ermöglicht uns die Verarbeitung viel größerer Dateimengen.
Die Ergebnisse
Die Neufassung von Go brachte beeindruckende Verbesserungen:
- Speichernutzung: Reduziert um 99 % (von 10 GB auf 100 MB)
- Verarbeitungsgeschwindigkeit: um ca. 1000 % erhöht
- Zuverlässigkeit: Verarbeitet 20.000 Dateien erfolgreich und ohne Probleme
- Kosteneffizienz: Geringere Speichernutzung und schnellere Ausführungszeit führen zu geringeren AWS Lambda-Kosten
Gelernte Lektionen
- Language Choice Matters: Das Effizienz- und Parallelitätsmodell von Go hat in unserem Anwendungsfall einen gewaltigen Unterschied gemacht.
- Verstehen Sie Ihre Engpässe: Durch die Profilierung unserer Node.js-Funktion konnten wir wichtige Verbesserungsbereiche identifizieren.
- Nutzung Cloud-nativer Lösungen: Die Verwendung von AWS SDK for Go v2 und das Verständnis der S3-Funktionen ermöglichten eine bessere Integration und Leistung.
- Denken Sie in Streams: Die Verarbeitung von Daten als Streams, anstatt alles in den Speicher zu laden, ist für groß angelegte Vorgänge von entscheidender Bedeutung.
Abschluss
Das Umschreiben unserer Lambda-Funktion in Go hat nicht nur unsere unmittelbaren Skalierungsprobleme gelöst, sondern auch eine robustere und effizientere Lösung für unsere Dateiverarbeitungsanforderungen bereitgestellt. Obwohl Node.js uns anfangs gute Dienste leistete, verdeutlichte diese Erfahrung, wie wichtig es ist, das richtige Tool für die jeweilige Aufgabe auszuwählen, insbesondere bei der Bewältigung ressourcenintensiver Aufgaben in großem Maßstab.
Denken Sie daran, dass die beste Sprache oder das beste Framework von Ihrem spezifischen Anwendungsfall abhängt. In unserem Szenario stimmten die Leistungsmerkmale von Go perfekt mit unseren Anforderungen überein, was zu einer deutlich verbesserten Benutzererfahrung und geringeren Betriebskosten führte.
Standen Sie mit serverlosen Funktionen vor ähnlichen Herausforderungen? Wie haben Sie sie überwunden? Wir würden gerne von Ihren Erfahrungen in den Kommentaren unten hören!
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3