En tant que développeurs, nous sommes souvent confrontés à des défis lorsque nous traitons du traitement et de la livraison de données à grande échelle. Chez Kamero, nous avons récemment résolu un goulot d'étranglement important dans notre pipeline de livraison de fichiers. Notre application permet aux utilisateurs de télécharger des milliers de fichiers associés à un événement particulier sous la forme d'un seul fichier zip. Cette fonctionnalité, optimisée par une fonction Lambda basée sur Node.js, responsable de la récupération et de la compression des fichiers à partir des compartiments S3, était confrontée à des contraintes de mémoire et à de longs temps d'exécution à mesure que notre base d'utilisateurs grandissait.
Cet article détaille notre parcours depuis une implémentation de Node.js gourmande en ressources jusqu'à une solution Go légère et ultra-rapide qui gère efficacement les téléchargements S3 massifs. Nous explorerons comment nous avons optimisé notre système pour offrir aux utilisateurs une expérience transparente lors de la demande d'un grand nombre de fichiers provenant d'événements spécifiques, le tout regroupé dans un téléchargement zip unique et pratique.
Notre fonction Lambda d'origine était confrontée à plusieurs problèmes critiques lors du traitement de grands ensembles de fichiers basés sur des événements :
Notre implémentation originale utilisait la bibliothèque s3-zip pour créer des fichiers zip à partir d'objets S3. Voici un extrait simplifié de la façon dont nous traitions les fichiers :
const s3Zip = require("s3-zip"); // ... other code ... const body = s3Zip.archive( { bucket: bucketName }, eventId, files, entryData ); await uploadZipFile(Upload_Bucket, zipfileKey, body);
Bien que cette approche ait fonctionné, elle a chargé tous les fichiers en mémoire avant de créer le zip, ce qui a entraîné une utilisation élevée de la mémoire et des erreurs potentielles de manque de mémoire pour les grands ensembles de fichiers.
Nous avons décidé de réécrire notre fonction Lambda dans Go, en tirant parti de son efficacité et de ses fonctionnalités de concurrence intégrées. Les résultats ont été stupéfiants :
Nous avons utilisé le SDK AWS pour Go v2, qui offre de meilleures performances et une utilisation moindre de la mémoire par rapport à la v1 :
cfg, err := config.LoadDefaultConfig(context.TODO()) s3Client = s3.NewFromConfig(cfg)
Les goroutines de Go nous ont permis de traiter plusieurs fichiers simultanément :
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() semCette approche nous permet de traiter plusieurs fichiers simultanément tout en contrôlant le niveau de concurrence pour éviter de surcharger le système.
3. Création de Zip en streaming
Au lieu de charger tous les fichiers en mémoire, nous diffusons le contenu zip directement vers 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, })Cette approche de streaming réduit considérablement l'utilisation de la mémoire et nous permet de gérer des ensembles de fichiers beaucoup plus volumineux.
Les résultats
La réécriture vers Go a apporté des améliorations impressionnantes :
- Utilisation de la mémoire : réduite de 99 % (de 10 Go à 100 Mo)
- Vitesse de traitement : augmentée d'environ 1 000 %
- Fiabilité : gère avec succès 20 000 fichiers sans problème
- Rapport coût-efficacité : une utilisation moindre de la mémoire et un temps d'exécution plus rapide entraînent une réduction des coûts AWS Lambda
Leçons apprises
- Le choix de la langue est important : le modèle d'efficacité et de concurrence de Go a fait une énorme différence dans notre cas d'utilisation.
- Comprenez vos goulots d'étranglement : le profilage de notre fonction Node.js nous a aidé à identifier les domaines clés à améliorer.
- Tirez parti des solutions cloud natives : l'utilisation du SDK AWS pour Go v2 et la compréhension des capacités de S3 ont permis une meilleure intégration et de meilleures performances.
- Penser en flux : traiter les données sous forme de flux plutôt que de tout charger en mémoire est crucial pour les opérations à grande échelle.
Conclusion
La réécriture de notre fonction Lambda dans Go a non seulement résolu nos problèmes de mise à l'échelle immédiats, mais a également fourni une solution plus robuste et plus efficace pour nos besoins de traitement de fichiers. Bien que Node.js nous ait bien servi au début, cette expérience a souligné l'importance de choisir le bon outil pour le travail, en particulier lorsqu'il s'agit de tâches gourmandes en ressources à grande échelle.
N'oubliez pas que le meilleur langage ou framework dépend de votre cas d'utilisation spécifique. Dans notre scénario, les caractéristiques de performance de Go correspondaient parfaitement à nos besoins, ce qui se traduisait par une expérience utilisateur considérablement améliorée et une réduction des coûts opérationnels.
Avez-vous été confronté à des défis similaires avec les fonctions sans serveur ? Comment les avez-vous surmontés ? Nous serions ravis de connaître vos expériences dans les commentaires ci-dessous !
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3