Azure Native Qumulo jetzt in der EU, im Vereinigten Königreich und in Kanada verfügbar – Erfahren Sie mehr

Folgen der Cookie-Krümel: Untersuchung einer Speicherleistungsanomalie

Geschrieben von:

Dieser Beitrag befasst sich mit der Identifizierung und Behebung einer sporadischen Speicherleistungsanomalie, die wir in einem unserer Benchmarks beobachtet haben.

Bei Qumulo bauen wir eine leistungsstarke Dateidatenplattform und veröffentlichen fortlaufend alle zwei Wochen Updates. Der Versand von Unternehmenssoftware erfordert so häufig eine umfangreiche Testsuite, um sicherzustellen, dass wir ein qualitativ hochwertiges Produkt hergestellt haben. Unsere Leistungstestsuite läuft kontinuierlich auf allen unseren Plattformangebote und umfasst Dateileistungstests, die mit branchenüblichen Benchmarks durchgeführt werden.

Geben Sie die Speicherleistungsanomalie ein

Über einen Zeitraum von einigen Monaten beobachteten wir Schwankungen in unseren Multi-Stream-Lese- und Schreib-Benchmarks. Diese Benchmarks verwenden IOzone, um gleichzeitige Lese- und Schreibvorgänge für den Cluster zu generieren und den aggregierten Durchsatz über alle verbundenen Clients hinweg zu messen. Insbesondere haben wir eine bimodale Verteilung beobachtet, bei der die meisten Läufe ein konstant stabiles Leistungsziel erreichten, während ein zweiter, kleinerer Satz von Ergebnissen sporadisch etwa 200-300 MB/s langsamer lief, was etwa 10 % schlechter ist. Hier ist eine Grafik, die die Leistungsergebnisse zeigt.

Charakterisierung des Problems

Bei der Untersuchung einer Anomalie der Speicherleistung besteht der erste Schritt darin, so viele Variablen wie möglich zu entfernen. Die sporadischen Ergebnisse wurden zunächst über Hunderte von Softwareversionen über einen Zeitraum von Monaten identifiziert. Der Einfachheit halber haben wir eine Reihe von Durchläufen des Benchmarks gestartet, alle auf derselben Hardware und auf einer einzigen Softwareversion. Diese Serie von Durchläufen zeigte die gleiche bimodale Verteilung, was bedeutete, dass die Variabilität nicht durch Hardwareunterschiede oder softwareversionsspezifische Regressionen erklärt werden konnte.

Nachdem wir die bimodale Perf in einer einzigen Version reproduziert hatten, verglichen wir dann die detaillierten Leistungsdaten, die aus einem schnellen und einem langsamen Lauf gesammelt wurden. Das erste, was auffiel, war, dass die RPC-Latenzen zwischen den Knoten bei den schlechten Läufen viel höher waren als bei den guten Läufen. Dies könnte eine Reihe von Gründen gehabt haben, deutete jedoch auf eine netzwerkbezogene Ursache hin.

Untersuchen der TCP-Socket-Leistung

Vor diesem Hintergrund wollten wir detailliertere Daten zu unserer TCP-Socket-Leistung aus Testläufen. Daher haben wir unseren Leistungstest-Profiler aktiviert, um sammle kontinuierlich daten von ss. Jedes Mal, wenn ss ausgeführt wird, gibt es detaillierte Statistiken für jeden Socket im System aus:

>ss -tio6 State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 fe80::f652:14ff:fe3b:8f30%bond0:56252 fe80::f652:14ff:fe3b:8f60:42687 Sack kubisch wscale:7,7 rto:204 rtt:0.046/0.01 ato:40 mss:8940 cwnd:10 ssthresh:87 bytes_acked:21136738172861 bytes_received:13315563865457 segs_out:3021503845 segs_in:2507786423 senden 15547.8Mbps retrans:348/1140 rcv_rtt:348 rcv_space:30844.2 ESTAB 0 1540003 fe4::f8546640:0ff:fe0b:80f652%bond14:3 fe8::f30:0ff:fe44517b:80:652 Sack Kubikwaage:14 rto :2 rtt:4030/45514 ato:7,7 mss:204 cwnd:2.975 ssthresh:5.791 bytes_acked:40 bytes_received:8940 segs_out:10 segs_in:10 send 2249367594375Mbps lastsnd:911006516679 lastrcv:667921849 671354128 rcv_rtt:240.4 rcv_space:348 …

Jeder Socket im System entspricht einem Eintrag in der Ausgabe.

Wie Sie an der Beispielausgabe sehen können, gibt ss seine Daten auf eine Weise aus, die für die Analyse nicht sehr freundlich ist. Wir nahmen die Daten und zeichneten die verschiedenen Komponenten auf, um einen visuellen Überblick über die TCP-Socket-Leistung im Cluster für einen bestimmten Leistungstest zu erhalten. Mit diesem Diagramm konnten wir die schnellen Tests und langsamen Tests leicht vergleichen und nach Anomalien suchen.

Das interessanteste dieser Diagramme war die Größe des Staufensters (in Segmenten) während des Tests. Das Staufenster (gekennzeichnet durch cwnd</var/www/wordpress>: in the above output) is crucially important to TCP performance, as it controls the amount of data outstanding in-flight over the connection at any given time. The higher the value, the more data TCP can send on a connection in parallel. When we looked at the congestion windows from a node during a low-performance run, we saw two connections with reasonably high congestion windows and one with a very small window.

Wenn man sich die RPC-Latenzen zwischen Knoten ansieht, stehen die hohen Latenzen in direktem Zusammenhang mit dem Socket mit dem winzigen Überlastungsfenster. Dies warf die Frage auf: Warum sollte ein Socket im Vergleich zu den anderen Sockets im System ein sehr kleines Überlastungsfenster beibehalten?

Nachdem wir festgestellt hatten, dass eine RPC-Verbindung eine deutlich schlechtere TCP-Leistung aufwies als die anderen, gingen wir noch einmal zurück und schauten uns die Rohausgabe von ss an. Wir haben festgestellt, dass diese „langsame“ Verbindung andere TCP-Optionen hatte als die übrigen Sockets. Insbesondere verfügte es über die Standard-TCP-Optionen. Beachten Sie, dass die beiden Verbindungen sehr unterschiedliche Überlastungsfenster haben und dass die Zeile mit einem kleineren Überlastungsfenster fehlt sack</var/www/wordpress> and wscale:7</var/www/wordpress>,7.</var/www/wordpress>

ESTAB 0 0 ::ffff:10.120.246.159:8000 ::ffff:10.120.246.27:52312 Sack kubisch wscale:7,7 rto:204 rtt:0.183/0.179 ato:40 mss:1460 cwnd:293 ssthresh:291 bytes_acked: 140908972 bytes_received:27065 segs_out:100921 segs_in:6489 send 18700.8Mbps lastsnd:37280 lastrcv:37576 lastack:37280 pacing_rate 22410.3Mbps rcv_space:29200 ESTAB 0 0 fe80::e61 d:2dff:febb:c960%bond0:33610 fe80::f652: 14ff:fe54:d600:48673 kubisch rto:204 rtt:0.541/1.002 ato:40 mss:1440 cwnd:10 ssthresh:21 bytes_acked:6918189 bytes_received:7769628 segs_out:10435 segs_in:10909 Senden Sie 212.9 Mbit/s lastsnd:1228 lastrcv:1232 lastack :1228 Pacing_Rate 255.5 Mbit/s rcv_rtt:4288 rcv_space:1131488

Das war interessant, aber die Betrachtung nur eines Socket-Datenpunkts gab uns nicht viel Vertrauen, dass die Verwendung von Standard-TCP-Optionen in hohem Maße mit unserem Problem mit dem winzigen Überlastungsfenster zusammenhängt. Um ein besseres Gefühl dafür zu bekommen, was vor sich ging, haben wir die SS-Daten aus unserer Reihe von Benchmark-Läufen gesammelt und festgestellt, dass 100 % der Sockets ohne die SACK-Optionen (selektive Bestätigung) eine maximale Überlastungsfenstergröße von 90–99.5 % kleiner hatten als jeder Socket mit nicht standardmäßigen TCP-Optionen. Hier gab es eindeutig einen Zusammenhang zwischen Sockets, denen die SACK-Option fehlte, und der Leistung dieser TCP-Sockets, was sinnvoll ist, da SACK und andere Optionen die Leistung steigern sollen.

Wie TCP-Optionen festgelegt werden

TCP-Optionen für eine Verbindung werden festgelegt, indem Optionswerte zusammen mit Nachrichten übergeben werden, die SYN-Flags enthalten. Dies ist Teil des TCP-Verbindungs-Handshakes (SYN, SYN+ACK, ACK), der zum Herstellen einer Verbindung erforderlich ist. Unten sehen Sie ein Beispiel für eine Interaktion, bei der die Optionen MSS (maximale Segmentgröße), SACK und WS (Fensterskalierung) festgelegt sind.

Wo sind also unsere TCP-Optionen geblieben?

Obwohl wir die fehlenden SACK- und Fensterskalierungsoptionen mit kleineren Überlastungsfenstern und Verbindungen mit geringem Durchsatz in Verbindung gebracht hatten, hatten wir immer noch keine Ahnung, warum diese Optionen für einige unserer Verbindungen deaktiviert waren. Schließlich wurde jede Verbindung mit demselben Code erstellt!

Wir haben uns entschieden, uns auf die SACK-Option zu konzentrieren, da es sich um ein einfaches Flag handelt, in der Hoffnung, dass das Debuggen einfacher wäre. Unter Linux wird SACK global von einem Sysctl gesteuert und kann nicht pro Verbindung gesteuert werden. Und wir hatten SACK auf unseren Maschinen aktiviert:

>sysctl net.ipv4.tcp_sack
net.ipv4.tcp_sack = 1</var/www/wordpress>

Wir wussten nicht, wie unser Programm die Einstellung dieser Optionen bei manchen Verbindungen übersehen konnte. Wir begannen mit der Erfassung des TCP-Handshakes während des Verbindungsaufbaus. Wir haben festgestellt, dass in der ersten SYN-Nachricht die erwarteten Optionen festgelegt waren, SYN+ACK jedoch SACK und Fensterskalierung entfernte.

Wir haben den TCP-Stack des Linux-Kernels geknackt und mit der Suche nach der Gestaltung der SYN+ACK-Optionen begonnen. Wir fanden tcp_make_synack, die anruft tcp_synack_options:

static unsigned int tcp_synack_options(const struct sock *sk, struct request_sock *req, unsigned int mss, struct sk_buff *skb, struct tcp_out_options *opts, const struct tcp_md5sig_key *md5, struct tcp_fastopen_cookie *foc) { ... if (likely(ireq ->sack_ok)) { opts->options |= OPTION_SACK_ADVERTISE; if (unwahrscheinlich(!ireq->tstamp_ok)) verbleibend -= TCPOLEN_SACKPERM_ALIGNED; } ... return MAX_TCP_OPTION_SPACE - übrig; }

Wir haben gesehen, dass die SACK-Option einfach basierend darauf festgelegt wird, ob die SACK-Option für die eingehende Anfrage festgelegt ist, was nicht sehr hilfreich war. Wir wussten, dass SACK von dieser Verbindung zwischen SYN und SYN+ACK getrennt wurde, und wir mussten immer noch herausfinden, wo das geschah.

Wir haben uns die Analyse der eingehenden Anfrage angesehen tcp_parse_options:

void tcp_parse_options (const struct net *net, const struct sk_buff *skb, struct tcp_options_received *opt_rx, int estab, struct tcp_fastopen_cookie *foc) {... case tcpopt_sack_perm: ipsize == tcpolen_sack_sack_sack_sack_sack_sack_sack_polen_sack_polen_sack_polen_sack- net->ipv4.sysctl_tcp_sack) { opt_rx->sack_ok = TCP_SACK_SEEN; tcp_sack_reset(opt_rx); } brechen; ... }

Wir haben gesehen, dass zum positiven Parsen einer SACK-Option bei einer eingehenden Anfrage die Anfrage das SYN-Flag haben muss (dies war der Fall), die Verbindung darf nicht hergestellt sein (was nicht der Fall war) und der net.ipv4.tcp_sack sysctl muss aktiviert sein (war es auch). Kein Glück hier.

Beim Stöbern ist uns zufällig aufgefallen, dass bei der Bearbeitung von Verbindungsanfragen in tcp_conn_request, löscht es manchmal die Optionen:

int tcp_conn_request(struct request_sock_ops *rsk_ops, const struct tcp_request_sock_ops *af_ops, struct sock *sk, struct sk_buff *skb) { ... tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc); if (want_cookie && !tmp_opt.saw_tstamp) tcp_clear_options(&tmp_opt); ... 0 zurückgeben; }

Wir fanden schnell heraus, dass die want_cookie</var/www/wordpress> variable indicates that Linux wants to use the TCP SYN cookies feature, but we didn’t have any idea what that meant.

Was sind TCP-SYN-Cookies?

TCP-SYN-Cookies können wie folgt charakterisiert werden.

SYN-Überflutung

TCP-Server verfügen normalerweise nur über begrenzten Speicherplatz in der SYN-Warteschlange für noch nicht hergestellte Verbindungen. Wenn diese Warteschlange voll ist, kann der Server keine weiteren Verbindungen mehr annehmen und muss eingehende SYN-Anfragen verwerfen.

Dieses Verhalten führt zu einem Denial-of-Service-Angriff namens SYN-Flooding. Der Angreifer sendet viele SYN-Anfragen an einen Server, aber wenn der Server mit SYN+ACK antwortet, ignoriert der Angreifer die Antwort und sendet nie eine ACK, um den Verbindungsaufbau abzuschließen. Dies führt dazu, dass der Server versucht, SYN+ACK-Nachrichten mit eskalierenden Backoff-Timern erneut zu senden. Wenn der Angreifer nie antwortet und weiterhin SYN-Anfragen sendet, kann er die SYN-Warteschlange des Servers jederzeit voll halten und so verhindern, dass legitime Clients Verbindungen mit dem Server herstellen.

Der SYN-Flut widerstehen

TCP-SYN-Cookies lösen dieses Problem, indem sie es dem Server ermöglichen, mit SYN+ACK zu antworten und eine Verbindung aufzubauen, selbst wenn die SYN-Warteschlange voll ist. Was SYN-Cookies tatsächlich bewirken, ist die Codierung der Optionen, die normalerweise in der SYN-Warteschlange gespeichert würden (plus eines kryptografischen Hashs der ungefähren Zeit und der Quell-/Ziel-IPs und -Ports), und zwar als Eintrag in den anfänglichen Sequenznummernwert im SYN+ACK. Der Server kann dann den SYN-Warteschlangeneintrag verwerfen und verschwendet keinen Speicher für diese Verbindung. Wenn der (legitime) Client schließlich mit einer ACK-Nachricht antwortet, enthält diese dieselbe anfängliche Sequenznummer. Der Server kann dann den Hash der Zeit dekodieren und, wenn er gültig ist, die Optionen dekodieren und den Verbindungsaufbau abschließen, ohne SYN-Warteschlangenraum zu verwenden.

Nachteile von SYN-Cookies

Die Verwendung von SYN-Cookies zum Herstellen einer Verbindung hat einen Nachteil: In der anfänglichen Sequenznummer ist nicht genügend Platz, um alle Optionen zu kodieren. Der Linux-TCP-Stack kodiert nur die maximale Segmentgröße (eine erforderliche Option) und sendet ein SYN+ACK, das alle anderen Optionen, einschließlich der SACK- und Fensterskalierungsoptionen, ablehnt. Dies stellt normalerweise kein Problem dar, da es nur verwendet wird, wenn der Server über eine volle SYN-Warteschlange verfügt, was unwahrscheinlich ist, es sei denn, er ist einem SYN-Flood-Angriff ausgesetzt.

Nachfolgend finden Sie eine Beispielinteraktion, die zeigt, wie eine Verbindung mit SYN-Cookies hergestellt wird, wenn die SYN-Warteschlange eines Servers voll ist.

Die Anomalie der Speicherleistung: Qumulos TCP-Problem

Nachdem wir TCP-SYN-Cookies untersucht hatten, erkannten wir, dass sie wahrscheinlich dafür verantwortlich waren, dass bei unseren Verbindungen regelmäßig TCP-Optionen fehlten. Sicherlich, dachten wir, waren unsere Testmaschinen keinem SYN-Flood-Angriff ausgesetzt, daher hätten ihre SYN-Warteschlangen nicht voll sein dürfen.

Wir lasen erneut den Linux-Kernel und stellten fest, dass die maximale SYN-Warteschlangengröße festgelegt war inet_csk_listen_start:

int inet_csk_listen_start(struct sock *sk, int backlog) { ... sk->sk_max_ack_backlog = backlog; sk->sk_ack_backlog = 0; ... }

Von dort aus verfolgten wir die Anrufer und stellten fest, dass der Rückstandswert direkt im festgelegt wurde hören Systemaufruf. Wir haben den Socket-Code von Qumulo aufgerufen und schnell festgestellt, dass wir beim Abhören von Verbindungen immer einen Rückstand der Größe 5 verwendet haben.

if (listen(fd, 5) == -1) return error_new(system_error, errno, "listen");

Während der Cluster-Initialisierung haben wir ein verbundenes Mesh-Netzwerk zwischen allen Maschinen erstellt, sodass für jeden Cluster mit ausreichender Größe natürlich mehr als fünf Verbindungen gleichzeitig erstellt wurden. Wir haben SYN unseren eigenen Cluster von innen überflutet!

Wir haben schnell eine Änderung vorgenommen, um die von Qumulo verwendete Backlog-Größe zu erhöhen, und alle schlechten Leistungsergebnisse verschwanden: Fall abgeschlossen!

Anmerkung der Redaktion: Dieser Beitrag wurde im Dezember 2020 veröffentlicht.

Mehr erfahren

Qumulo's Entwickler Team stellt ein und wir haben mehrere Stellenangebote – Schauen Sie sich diese an und erfahren Sie mehr darüber Leben in Qumulo.

Kontakt

Machen Sie eine Probefahrt. Testen Sie Qumulo in unseren neuen interaktiven Hands-On Labs oder fordern Sie eine Demo oder eine kostenlose Testversion an.

Abonnieren Sie den Qumulo-Blog für Kundengeschichten, technische Einblicke, Branchentrends und Produktneuigkeiten.

Verwandte Artikel

Nach oben scrollen