Zum Inhalt springen
Codedock
LeistungenWie wir arbeitenInsightsFallstudienKarriereKontakt
Zurück zu allen Artikeln
Enterprise-Integration

·

7 Min Lesezeit

·

Geschrieben von Tomáš Mikeš

GB/s Netzwerkverkehr in .NET verarbeiten: Architektur ohne Paket-Drops

Für Netigo verarbeiten wir IPFIX/NetFlow-Flows in GB/s-Volumen. Der Schlüssel ist Buffering, Backpressure und horizontales Scaling — kein Sync-Processing. Was sitzen muss, damit das System unter Last nichts verliert.

High-performance.NETNetzwerkBackpressure

Ein Sensor sendet 50 000 NetFlow-Flows im Peak pro Sekunde. Typisches Beginner-System: Sync-Parsing, Sync-DB-Insert. Ergebnis — der Server kommt nicht mit, UDP-Pakete fallen, 30 % der Daten verschwinden spurlos. Niemand merkt es, bis jemand einen Grund zu prüfen hat.

Für Netigo haben wir eine Pipeline gebaut, die Peaks von 2 GB/s Netzwerkverkehr ohne Drops verkraftet, auch bei langfristigem Wachstum. Die Architektur hat mehrere tragende Prinzipien; alle einzuhalten ist die Bedingung.

Prinzip 1: UDP-Ingestion abseits des Hot-Path

NetFlow/IPFIX läuft über UDP. UDP kennt kein Retry; ist es nicht schnell genug abgeholt, ist es weg. Erstens — der UDP-Socket-Listener darf nichts tun als Bytes greifen und in eine In-Memory-Queue schieben.

In .NET:

while (true) {
  var result = await udpClient.ReceiveAsync();
  await channel.Writer.WriteAsync(new RawPacket {
    Bytes = result.Buffer,
    ReceivedAt = DateTime.UtcNow,
    SourceEndPoint = result.RemoteEndPoint
  });
}

Kein Parsing. Keine Validierung. Kein DB-Call. Nur Bytes in einen System.Threading.Channels-Bounded-Channel. Pro-Packet-Zeit: ~15 μs. Heißt: ein Thread schafft ~60 000 Pakete/s.

Wenn mehr nötig: zweiter UDP-Listener auf anderem Port + Load-Balancer davor. Horizontales Ingestion-Scaling.

Prinzip 2: Bounded-Channel = implizite Backpressure

Wenn die Processing-Pipeline langsamer wird (DB langsam, Network-Blip), wächst die In-Memory-Queue. Unbegrenzte Queue = Out-of-Memory in Minuten. Richtig: Bounded-Channel mit Limit.

var options = new BoundedChannelOptions(capacity: 100_000) {
  FullMode = BoundedChannelFullMode.Wait
};
var channel = Channel.CreateBounded<RawPacket>(options);

Ist die Queue voll, wartet der Writer. Was passiert mit UDP-Paketen? Drops — aber erkannte Drops (Metric-Inkrement). Pakete kommen nicht zurück, aber Sie sehen, wann es passiert und können scale-up.

Alternative: Kernel-Level-Buffer (setsockopt SO_RCVBUF). Socket-Buffer auf 32 MB zu heben bringt einige Sekunden Reserve, bevor UDP auf OS-Level droppt.

Prinzip 3: Parser in dedizierten Threads, nicht im Thread-Pool

Der Parser liest aus dem Channel, dekodiert das IPFIX-Template, extrahiert Fields. CPU-intensiv. Läuft er im Default-Thread-Pool, konkurriert er mit HTTP-API, Logging und allem anderen.

Dedizierter Thread-Pool für den Parser. Anzahl Threads = CPU-Core-Count. In .NET:

var parserTasks = Enumerable.Range(0, Environment.ProcessorCount)
  .Select(_ => Task.Factory.StartNew(
    () => ParseLoop(channel.Reader),
    TaskCreationOptions.LongRunning))
  .ToArray();

Jeder Task zieht aus channel.Reader, parst das Paket, erzeugt 1-30 geparste Flows (IPFIX-Paket enthält mehrere Flow-Records) und schiebt sie in einen zweiten Channel für den DB-Batch-Writer.

Prinzip 4: DB-Writes in Batches, nicht einzeln

Eine Zeile in TimescaleDB einfügen = ~1 ms (Network + Parse + Write). 500 000 Zeilen/s als Einzel-Inserts ist unmöglich — 500 000 ms = 500 Sekunden Arbeit pro Sekunde Daten.

Lösung: COPY FROM (Postgres-Bulk-Insert). Batchen Sie 5 000-10 000 Zeilen in ein COPY-Statement. Latenz ~30 ms pro Batch. Durchsatz ~200 000 Zeilen/s pro Writer, und Writer skalieren horizontal.

In .NET via NpgsqlBinaryImporter:

using var writer = conn.BeginBinaryImport(
  "COPY flows (ts, src_ip, dst_ip, bytes, ...) FROM STDIN BINARY");
foreach (var flow in batch) {
  writer.StartRow();
  writer.Write(flow.Timestamp, NpgsqlDbType.TimestampTz);
  writer.Write(flow.SrcIp, NpgsqlDbType.Inet);
  // ...
}
await writer.CompleteAsync();

Gegenüber einzelnen INSERTs: 100× höherer Durchsatz.

Prinzip 5: Per-Stage-Metriken, nicht nur End-to-End

Wenn das System langsamer wird, müssen Sie wissen WO. Unsere Metriken pro Stufe:

  • UDP-Empfang: Pakete/s, Bytes/s, Drop-Count (Socket-Stats)
  • Raw-Channel: aktuelle Tiefe, Enqueue-Wait-Zeit (wenn voll)
  • Parser: Pakete geparst/s, Parse-Errors/s, Parse-Zeit p99
  • Parsed-Channel: aktuelle Tiefe, Enqueue-Wait-Zeit
  • DB-Writer: Batches/s, Zeilen/s, COPY-Zeit p99, DB-Connection-Pool-Auslastung
  • End-to-End-Latenz: UDP-Empfang bis DB-Commit (p99)

Wenn End-to-End-p99-Latenz steigt, UDP-Drop-Count aber 0 bleibt — Backpressure funktioniert; eine Stufe ist langsam. Per-Stage anschauen. Parser-p99 verschlechtert? Dort ist das Problem. DB-Writer-Batches/s gefallen? Die DB ist langsam.

Prinzip 6: Horizontales Scaling, nicht vertikales

Wenn Sie Single-Node-Limits erreichen (~1M Pakete/s auf unserer HW), skalieren Sie nicht RAM/CPU. Scale-out.

Jeder Sensor schickt Flows an einen von vier Ingestion-Nodes (stateless, load-balanced UDP via Consistent-Hashing nach Sensor-IP). Jeder Node hat die komplette Pipeline (Parser + Writer) und schreibt in geteilte TimescaleDB.

4 Nodes × 500k Pakete/s = 2M Pakete/s Kapazität. Wird die DB zum Bottleneck, fügen wir Multi-Node-TimescaleDB hinzu oder sharden nach Sensor-ID.

Netigo-Ergebnis nach 12 Monaten Betrieb

  • Peak-Durchsatz: 850 000 Flows/s (1,7 GB/s Netzwerkdaten)
  • Durchschnitt: 350 000 Flows/s
  • UDP-Drop-Rate: < 0,001 % (weniger als 1 in einer Million)
  • End-to-End-p99-Latenz: 2,3 s (UDP → DB committet)
  • Single-Node-TimescaleDB, 4 Ingestion-Nodes
  • Downtime im Jahr: 0

Zum Mitnehmen

Große Volumen von Netzwerkdaten zu verarbeiten heißt viel nicht zu versuchen, zu viel an einem Ort zu tun. Jede Pipeline-Stufe hat eigene Queue-Größe, eigene Threads, eigene Metriken. Wird eine Stufe langsam, isoliert Backpressure sie vom Rest.

Eine synchrone End-to-End-Pipeline (UDP-Empfang → Parse → Insert) schafft 20-50 MB/s. Darüber muss man teilen. Das Prinzip ist sprachunabhängig — Java, Go, Rust sehen alle gleich aus. .NET hat gute Tools (Channels, NpgsqlBinaryImporter), ist aber nicht dramatisch besser oder schlechter als andere Mainstream-Umgebungen.

Arbeiten Sie an etwas Ähnlichem?

Vereinbaren Sie ein 30-minütiges technisches Gespräch. Kein Vertriebsprozess — direktes architektonisches Feedback.

Termin auswählen

Architektur, Cloud und Integration für komplexe Systeme. Ein Senior-Architekt in jedem Projekt.

Navigation

LeistungenWie wir arbeitenInsightsFallstudienKarriereKontaktAgentur vs. Freelancer vs. wir

Leistungen

EntwicklungCloudDevOpsAI & DatenBeratungDelivery

Kontakt

CodeDock s.r.o.

Zlenická 863/9, 104 00 Praha 22

Tschechische Republik

info@codedock.com

IČO: 14292769

DIČ: CZ14292769


© 2026 Codedock

KontaktDatenschutzerklärung
Termin buchen