Node.js ist gut in der I/O-Konkurrenz. Es ist nicht gut darin, CPU-Arbeit als asynchron darzustellen, nur weil die Handler-Funktion async verwendet.
Diese Unterscheidung ist wichtig, denn viele Leistungsprobleme stammen nicht von langsamen Datenbanken oder langsamen Netzwerken. Sie entstehen durch eine Anfrage, die zu viel synchrone Arbeit verrichtet, während jede andere Anfrage dahinter wartet.
Das Irreführende Beispiel
Diese Route sieht auf den ersten Blick harmlos aus:
app.post("/webhook", async (req, res) => {
const payload = JSON.parse(req.body.raw);
await database.save(payload);
res.send("ok");
});
Der Datenbankaufruf ist asynchron. JSON.parse ist es nicht.
Wenn die Nutzlast groß ist, erfolgt das Parsen im Haupt-Thread, und der Ereignis-Loop kann andere eingehende Anfragen nicht bedienen, bis diese Arbeit abgeschlossen ist.
Was Tatsächlich Blockiert
In realen Node-Diensten sind die üblichen Übeltäter:
- große
JSON.parse und JSON.stringify Aufrufe
- Bild- oder PDF-Erstellung
- Krypto-Arbeiten an der falschen Stelle
- große synchrone Dateioperationen
- teure Regex- oder Transformationsschleifen
Die Lösung ist nicht "alles asynchron machen". Die Lösung besteht darin, die Arbeit zu verlagern oder umzugestalten.
Bessere Optionen
Wann immer möglich:
- streamen Sie anstelle von Puffern großer Nutzlasten
- verwenden Sie Worker für CPU-intensive Aufgaben
- verlagern Sie teure Transformationen aus heißen Anforderungswegen
Zum Beispiel sind Worker-Threads oft die sauberere Grenze für rechenintensive Operationen:
import { Worker } from "node:worker_threads";
export function runHeavyTask(input: unknown) {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL("./worker.js", import.meta.url), {
workerData: input,
});
worker.once("message", resolve);
worker.once("error", reject);
});
}
Das macht die Arbeit nicht günstiger. Es hält den Ereignis-Loop reaktionsschnell, während die Arbeit woanders geschieht.
Der Kompromiss
Node ist immer noch eine gute Wahl für viele Backendsysteme. Sie müssen nur das Laufzeitmodell respektieren. Wenn Ihr Dienst die meiste Zeit mit dem Warten auf I/O verbringt, passt Node natürlich. Wenn er die meiste Zeit mit der Verarbeitung von Daten auf der CPU verbringt, benötigen Sie eine überlegte Isolation.
Weitere Lektüre