Wie man Gigabit auf BSD auslastet

Gigabit-Ethernet ist für Server inzwischen die bestimmende Anschlußart. Setzt man einen solchen als Router ein, dann möchte man auch die Interfaces bis zum Anschlag auslasten. Aber das ist gar nicht so einfach.

Die Überschlagsrechnung für Broadband-Services für Privatkunden ist, daß man jedem Kunden im Durchschnitt ISDN zur Verfügung stellen muß, damit die Infrastruktur beim typischen Nutzungsmix nicht überlastet wird. So lastet man 1Gbps mit 8000 Nutzern zu je 128kbps aus. Diese Kenngrößen sind zu halten, auch wenn nicht alle Systeme zur Verfügung stehen. Üblicherweise verteilt man die Last also auf mehr Server, erhält so Redundanz, Lastverteilung und höhere Durchsätze für die Kunden.

Im aktuellen Fall sollen die FreeBSD Server Ihre Gigabit-Interfaces jeweils in Volllast bedienen können. Die typischen Empfehlungen bzgl. Performance und Network-Tuning sind weitestgehend berücksichtigt:

  • Puffergrößen und Interruptraten sind hochgesetzt.
  • Das ipfw-Regelwerk ist entschlackt.
  • Devd und per-Connection Scripte gibt es nicht mehr.

Es tut.

Fragen statt unterbrechen

Nur der NAT-Teil führt regelmäßig zum Absturz: "Kernel Trap 12", "Generel protection fault" und so weiter und so fort.

Den Abstürzen ist gemein, daß sie im libalias-Code auftreten, der im Inteface-Interrupt-Handler aufgerufen wurde. Es sieht so aus, als ob die internen Datenstrukturen zerstört sind. Vielleicht klappt ja das Locking nicht?

Was liegt also näher, auf das Interrupt-Handling zu verzichten und die Interfaces auf Polling umzustellen? Außerdem steht Polling im Ruf, besseren Durchsatz zu erzielen.

Gigabit erreicht Paketraten von bis zu 2 Mpps. Die Manpage spricht von Interrupt-Raten von 1kHz bis 2kHz. Es wären also pro Poll bis zu 1000 Pakete aus den Buffern der Ethernetkarten zu lesen. Und diese 1000 sind der Maximalwert, der pro Poll geholt werden kann. Scheint also zu passen. Wenn man noch Idlepoll anmacht, kann er sogar außerhalb der Interrupts Daten auslesen. Sicher ist sicher.

Zuerst fällt auf, daß DEVICE_POLLING und HZ=2000 nicht im Standardkernel eingebaut ist. Also erstmal einen neuen Kernel bauen. Für jemanden, der das noch nie mit FreeBSD gemacht hat, ein spannendes Spiel.

Nachdem das System glücklich wieder läuft, kann man auf einem Interface probeweise Polling aktivieren:

# ifconfig em1 polling

Es lebt noch. Aber wie bekommt man heraus, ob es wirklich funktioniert? Die Manpage spricht von Zähler, die man per sysctl auslesen kann. Die schauen gut aus.

Unter Last kommt eine Ernüchterung: Die Maschine macht nicht mehr über 800Mbps, sondern limitiert sich selbst auf 650Mbps. Die kern.polling.burst Werte zeigen, daß bis zu 1000 Pakete pro Poll gelesen werden. Dann aber brechen die Werte ein: Unter Last werden kaum noch 10, höchstens mal 100 Pakete gelesen!

Wo sind die Pakete hin? Konnte die Netzkarte die Daten nicht mehr wegspeichern?

Auch hier helfen die sysctl Zähler: dev.em.1.mac_stats.recv_no_buff ist normalerweise 0, steigt aber drastisch an, wenn die Überlast eintritt. Offenbar zerstört er dabei auch die existierenden Buffer, so daß Polling nichts mehr abzuholen hat.

Polle ich zu langsam?

In der /boot/loader.conf beherzt ein kern.hz=20000 eingetragen und nochmal probiert. Es gibt keine Verbesserung. Polling ist und bleibt auf ca. 650 Mbps limitiert. vmstat -i zeigt, daß die gewünschten Interrupt-Raten auch erreicht werden, das ist es also nicht.

Bei einer näheren Betrachtung mit top -nCHSIzs1 erschrecke ich dann doch: Polling läuft nur auf einer CPU! Diese ist zu 100% ausgelastet. Aber die Kiste hat doch 8 Kerne, die mitspielen könnten! Warum greifen deren Timerinterrupts nicht?

Freundlichkeit lohnt sich nicht

Da auch unter Polling die NAT-Abstüze bestehen bleiben und da die igb-Interfaces (aus meinem lagg-Bundle) zwar Polling unterstützen, dieses aber offenbar Work in Progress ist, so daß gar keine Daten mehr durchkommen, wenn die Last über 100 Mbps steigt, lasse ich Polling aus.

Polling ist hier auf einen CPU Kern limitiert und damit für höhere Bandbreiten schlicht ungeeignet. Schade.

Nachwirkungen

Das Experiment Polling schien beendet, trotzdem kamen die Maschinen nicht wieder aus dem Knie. Es blieb zäh.

Beim Rückbau von Polling zum Interrupt-Betrieb wurde – wegen der NAT-Probleme – die Firewall-Funktionalität aus dem Interface-Interrupt entfernt: net.isr.direct=0. Desweiteren wurden mehrere Kernel-Threads aktiviert, um die Last zu verteilen: net.isr.maxthreads=5.

Wie groß war also die Überraschung, als diese Änderungen trotz Reboots nicht aktiv wurden. Die ISR Verarbeitung klemmte auf  einer CPU fest. Und die hatte 100% Last. Die Interfaces waren mit 600Mbps an der Grenze angelangt.

Erst etwas Recherche zeigte, wo mein Problem liegt: Ein Kernel mit POLLING läßt den Netzwerkkram immer nur auf einer CPU laufen! Man darf POLLING gar nicht erst einkompilieren. Huh? Deswegen hat der Standardkernel diese Option nicht aktiviert? Hinterher ist man immer schlauer.

Also nehme ich vorerst den Firewall-Code mit in den Interface-Interrupt und warte auf den nächsten Reboot: net.isr.direct=1. Ohje, das reicht nicht! Also noch net.ist.direct_force=1. Jetzt aber!

Post a comment

Related content