
Für jemanden, der nach einem einfachen, schnellen Programm sucht, das eine CSV-Datei einliest, die Empfänger-Validierungs-API aufruft und eine CSV-Datei ausgibt, ist dieses Programm genau das Richtige für Sie.
Beim Erstellen von E-Mail-Anwendungen müssen Entwickler oft mehrere Dienste und APIs integrieren. Das Verständnis von E-Mail-API-Grundlagen in der Cloud-Infrastruktur bietet die Grundlage für den Aufbau robuster Werkzeuge wie das Massenvalidierungssystem, das wir in diesem Leitfaden erstellen werden.
Eine der Fragen, die wir gelegentlich erhalten, ist: Wie kann ich E-Mail-Listen mit Empfängervalidierung in großen Mengen validieren? Es gibt hier zwei Möglichkeiten: Eine besteht darin, eine Datei über die SparkPost-Benutzeroberfläche zur Validierung hochzuladen, die andere besteht darin, einzelne Anfragen pro E-Mail an die API zu richten (da die API eine Einzel-E-Mail-Validierung ist).
Die erste Option funktioniert großartig, hat jedoch eine Begrenzung von 20 MB (etwa 500.000 Adressen). Was ist, wenn jemand eine E-Mail-Liste mit Millionen von Adressen hat? Es könnte bedeuten, dass man diese in Tausende von CSV-Datei-Uploads aufteilen muss.
Da das Hochladen von Tausenden von CSV-Dateien ein wenig weit hergeholt erscheint, habe ich diesen Anwendungsfall genommen und begann mich zu fragen, wie schnell ich die API zum Laufen bringen könnte. In diesem Blogbeitrag werde ich erklären, was ich ausprobiert habe und wie ich schließlich zu einem Programm gekommen bin, das ungefähr 100.000 Validierungen in 55 Sekunden durchführen konnte (während ich in der Benutzeroberfläche etwa 100.000 Validierungen in 1 Minute 10 Sekunden erreicht habe). Und obwohl es immer noch etwa 100 Stunden dauern würde, um etwa 654 Millionen Validierungen abzuschließen, kann dieses Skript im Hintergrund laufen und erheblich Zeit sparen.
Die endgültige Version dieses Programms finden Sie hier.
Mein erster Fehler: die Verwendung von Python
Python ist eine meiner Lieblingsprogrammiersprachen. Es glänzt in vielen Bereichen und ist unglaublich unkompliziert. In einem Bereich glänzt es jedoch nicht: bei parallelen Prozessen. Während Python die Fähigkeit hat, asynchrone Funktionen auszuführen, hat es das, was als der Python Global Interpreter Lock oder GIL bekannt ist.
„Der Python Global Interpreter Lock oder GIL, einfach ausgedrückt, ist ein Mutex (oder eine Sperre), die nur einem Thread erlaubt, die Kontrolle über den Python-Interpreter zu halten.
Das bedeutet, dass zu jedem Zeitpunkt nur ein Thread im Zustand der Ausführung sein kann. Die Auswirkung des GIL ist für Entwickler, die Programme mit einem einzigen Thread ausführen, nicht sichtbar, kann jedoch in CPU-intensivem und multi-threaded Codes eine Leistungsbremse darstellen.
Da der Global Interpreter Lock (GIL) nur einem Thread die Ausführung gleichzeitig erlaubt, selbst auf Multi-Core-Systemen, hat es den Ruf erlangt, ein „berüchtigtes“ Merkmal von Python zu sein (siehe Real Python’s Artikel über das GIL).
Zuerst war mir das GIL nicht bewusst, also begann ich mit dem Programmieren in Python. Am Ende, obwohl mein Programm asynchron war, wurde es blockiert, und egal wie viele Threads ich hinzufügte, ich erreichte trotzdem nur etwa 12-15 Iterationen pro Sekunde.
Der Hauptteil der asynchronen Funktion in Python ist unten zu sehen:
Also habe ich den Einsatz von Python verworfen und bin zurück ans Reißbrett…
Ich entschied mich dafür, NodeJS zu nutzen, da es nicht-blockierende I/O-Operationen extrem gut ausführen kann. Eine weitere ausgezeichnete Option zur Handhabung von asynchronem API-Processing ist der Bau von Serverless-Webhook-Konsumenten mit Azure Functions, die variable Arbeitslasten effizient verarbeiten können. Außerdem bin ich ziemlich vertraut mit dem Programmieren in NodeJS.
Unter Nutzung der asynchronen Aspekte von Node.js funktionierte dieser Ansatz gut. Für weitere Details über asynchrones Programmieren in Node.js, siehe RisingStack’s Leitfaden für asynchrones Programmieren in Node.js.
Mein zweiter Fehler: der Versuch, die Datei in den Speicher zu lesen
Aufschlüsselung des finalen Codes
Nach dem Einlesen und Überprüfen der Terminalargumente führe ich den folgenden Code aus. Zuerst lese ich die CSV-Datei der E-Mails ein und zähle jede Zeile. Diese Funktion hat zwei Zwecke: 1) Sie ermöglicht es mir, über den Fortschritt der Datei genau zu berichten [wie wir später sehen werden], und 2) es ermöglicht mir, einen Timer zu stoppen, wenn die Anzahl der E-Mails in der Datei der abgeschlossenen Validierungen entspricht. Ich habe einen Timer hinzugefügt, damit ich Benchmarks durchführen und sicherstellen kann, dass ich gute Ergebnisse erziele.
Dann rufe ich die Funktion validateRecipients auf. Beachten Sie, dass diese Funktion asynchron ist. Nachdem ich überprüft habe, dass die infile und outfile CSV-Dateien sind, schreibe ich eine Kopfzeile und starte einen Programm-Timer mit der JSDOM-Bibliothek.
Das folgende Skript ist wirklich der Hauptteil des Programms, daher werde ich es aufbrechen und erklären, was passiert. Für jede Zeile der infile:
Diese Zeile asynchron nehmen und die Empfänger-Validierungs-API aufrufen.
Dann, bei der Antwort
Die E-Mail zum JSON hinzufügen (um die E-Mail in der CSV ausgeben zu können)
Wenn der Grund null ist, prüfen, und falls ja, einen leeren Wert einfügen (dies dient dazu, das CSV-Format konsistent zu halten, da in einigen Fällen ein Grund in der Antwort angegeben wird)
Die Optionen und Schlüssel für das json2csv-Modul festlegen.
Das JSON in CSV konvertieren und ausgeben (mithilfe von json2csv)
Fortschritt im Terminal schreiben
Schließlich, wenn die Anzahl der E-Mails in der Datei = abgeschlossenen Validierungen ist, den Timer stoppen und die Ergebnisse drucken
Ein letztes Problem, das ich fand, war, dass dies auf Mac hervorragend funktionierte, ich jedoch nach etwa 10.000 Validierungen auf Windows auf den folgenden Fehler stieß:
Fehler: connect ENOBUFS XX.XX.XXX.XXX:443 – Lokale (undefined:undefined) mit E-Mail XXXXXXX@XXXXXXXXXX.XXX
Nach weitergehenden Recherchen scheint es ein Problem mit dem NodeJS-HTTP-Client-Verbindungspool zu sein, der Verbindungen nicht wiederverwendet. Ich fand diesen Stackoverflow-Artikel zu dem Problem und nach weiterer Recherche eine gute Standardkonfiguration für die axios-Bibliothek, die dieses Problem gelöst hat. Ich bin mir immer noch nicht sicher, warum dieses Problem nur unter Windows und nicht auf Mac auftritt.
Nächste Schritte
Für jemanden, der nach einem einfachen, schnellen Programm sucht, das eine CSV einliest, die Empfänger-Validation API aufruft und eine CSV ausgibt, ist dieses Programm genau das Richtige.
Einige Ergänzungen zu diesem Programm wären folgende:
Erstellen Sie eine Benutzeroberfläche oder ein Frontend für die einfachere Nutzung
Bessere Fehlerbehandlung und erneute Versuche, da das Programm derzeit den Aufruf nicht wiederholt, falls die API aus irgendeinem Grund einen Fehler ausgibt
Erwägen Sie die Implementierung als eine serverlose Azure-Funktion für automatisches Skalieren und reduzierte Infrastrukturverwaltung
Ich wäre auch neugierig zu sehen, ob schnellere Ergebnisse mit einer anderen Sprache wie Golang oder Erlang/Elixir erzielt werden könnten. Neben der Sprachwahl können auch Infrastrukturbeschränkungen die Leistung beeinflussen - das haben wir aus erster Hand erfahren, als wir auf undokumentierte DNS-Limits in AWS stießen, die unsere hochvolumigen E-Mail-Verarbeitungssysteme beeinträchtigten.
Für Entwickler, die daran interessiert sind, API-Verarbeitung mit visuellen Workflow-Tools zu kombinieren, sehen Sie sich an, wie Sie Flow Builder mit Google Cloud Functions integrieren können, um No-Code-Automationsworkflows zu realisieren.
Bitte zögern Sie nicht, mir Feedback oder Vorschläge zur Erweiterung dieses Projekts zu geben.