Azure Native Qumulo ahora disponible en la UE, el Reino Unido y Canadá: Más información

Tras las migas de galletas: investigación de una anomalía en el rendimiento del almacenamiento

Escrito por:

Esta publicación profundiza en cómo identificamos y arreglamos una anomalía esporádica en el rendimiento del almacenamiento que observamos en uno de nuestros puntos de referencia.

En Qumulo, creamos una plataforma de datos de archivos de alto rendimiento y Continuar lanzando actualizaciones cada dos semanas. El envío de software de nivel empresarial requiere con frecuencia un amplio conjunto de pruebas para garantizar que hemos creado un producto de alta calidad. Nuestro conjunto de pruebas de rendimiento se ejecuta continuamente en todos nuestros ofertas de plataforma e incluye pruebas de rendimiento de archivos que se ejecutan en comparación con los estándares de la industria.

Ingrese la anomalía del rendimiento del almacenamiento

Durante un período de unos pocos meses, observamos variabilidad en nuestros puntos de referencia de lectura y escritura de múltiples flujos. Estos puntos de referencia utilizan IOzone para generar lecturas simultáneas y escrituras simultáneas en el clúster, y miden el rendimiento agregado en todos los clientes conectados. En particular, observamos una distribución bimodal en la que la mayoría de las ejecuciones alcanzaron un objetivo de rendimiento constantemente estable, mientras que un segundo conjunto de resultados más pequeños corrió esporádicamente alrededor de 200-300MB / s más lento, que es aproximadamente 10% peor. Aquí hay un gráfico que muestra los resultados de rendimiento.

Caracterizando el problema

Al investigar cualquier anomalía en el rendimiento del almacenamiento, el primer paso es eliminar tantas variables como sea posible. Los resultados esporádicos se identificaron por primera vez en cientos de versiones de software durante un período de meses. Para simplificar las cosas, iniciamos una serie de ejecuciones del punto de referencia, todas en el mismo hardware y en una única versión de software. Esta serie de corridas mostró la misma distribución bimodal, lo que significaba que la variabilidad no podía explicarse por diferencias de hardware o regresiones específicas de la versión de software.

Después de reproducir el rendimiento bimodal en una sola versión, comparamos los datos de rendimiento detallados recopilados de una ejecución rápida y una ejecución lenta. Lo primero que saltó a la vista fue que las latencias RPC entre nodos eran mucho más altas para las ejecuciones malas que para las buenas. Esto podría haber sido por varias razones, pero sugirió una causa raíz relacionada con la red.

Explorando el rendimiento del socket TCP

Teniendo eso en cuenta, queríamos datos más detallados sobre el rendimiento de nuestro socket TCP de las ejecuciones de prueba, por lo que permitimos que nuestro analizador de pruebas de rendimiento continuamente recopilar datos de ss. Cada vez que se ejecuta ss, genera estadísticas detalladas para cada socket en el sistema:

> 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 cubic 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 enviar 15547.8Mbps lastscnd: 348 lastscrate 1140Mbps retransmisiones: 348/30844.2 rcv_rtt: 0 rcv_space: 1540003 ESTAB 4 8546640 fe0 :: f0: 80ff: fe652b: 14f3% bond8: 30 fe0 :: f44517: 80ff: fe652b: 14: 2 saco cúbico wscale: 4030 rto : 45514 rtt: 7,7 / 204 ato: 2.975 mss: 5.791 cwnd: 40 ssthresh: 8940 bytes_acked: 10 bytes_received: 10 segs_out: 2249367594375 segs_in: 911006516679 enviar 667921849Mbps durasnd: 671354128 lastrcv: 240.4Mbps retroceso 348 rcv_rtt: 1464 rcv_space: 348…

Cada socket en el sistema corresponde a una entrada en la salida.

Como puede ver en la salida de muestra, ss vuelca sus datos de una manera que no es muy fácil de analizar. Tomamos los datos y trazamos los distintos componentes para ofrecer una vista visual del rendimiento del socket TCP en todo el clúster para una prueba de rendimiento determinada. Con este gráfico, podríamos comparar fácilmente las pruebas rápidas y las pruebas lentas y buscar anomalías.

El más interesante de estos gráficos fue el tamaño de la ventana de congestión (en segmentos) durante la prueba. La ventana de congestión (indicada por 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.

Mirando hacia atrás en las latencias de RPC entre nodos, las altas latencias se correlacionan directamente con el socket con la pequeña ventana de congestión. Esto planteó la pregunta: ¿por qué un socket mantendría una ventana de congestión muy pequeña en comparación con los otros sockets del sistema?

Habiendo identificado que una conexión RPC estaba experimentando un rendimiento de TCP significativamente peor que las otras, volvimos y miramos la salida sin procesar de ss. Notamos que esta conexión 'lenta' tenía diferentes opciones de TCP que el resto de sockets. En particular, tenía las opciones tcp predeterminadas. Tenga en cuenta que las dos conexiones tienen ventanas de congestión muy diferentes y que falta la línea que muestra una ventana de congestión más pequeña 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 saco cúbico 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 enviar 18700.8Mbps duran: 37280 lastrcv: 37576 lastack: 37280 pacing_rate 22410.3Mbps rcv_space: 29200 ESTAB 0 0 fe80 :: e61d: 2dff: fe960: f0% 33610ff: fe80: d652: 14 cubic rto: 54 rtt: 600 / 48673 ato: 204 mss: 0.541 cwnd: 1.002 ssthresh: 40 bytes_acked: 1440 bytes_received: 10 segs_out: 21 segs_in: 6918189 enviar 7769628Mbps lastsnd: 10435 lastrcv : 10909 ritmo_paso 212.9Mbps rcv_rtt: 1228 rcv_space: 1232

Esto fue interesante, pero mirar solo un punto de datos de socket no nos dio mucha confianza de que tener opciones TCP predeterminadas estaba altamente correlacionado con nuestro pequeño problema de ventana de congestión. Para tener una mejor idea de lo que estaba sucediendo, reunimos los datos ss de nuestra serie de ejecuciones de referencia y observamos que 100% de los zócalos sin las opciones SACK (reconocimiento selectivo) mantuvieron un tamaño máximo de ventana de congestión 90-99.5% menor que cada socket con opciones TCP no predeterminadas. Claramente, aquí había una correlación entre los sockets que faltaban la opción SACK y el rendimiento de esos sockets TCP, lo que tiene sentido ya que SACK y otras opciones están destinadas a aumentar el rendimiento.

Cómo se configuran las opciones de TCP

Las opciones de TCP en una conexión se establecen pasando valores de opciones junto con mensajes que contienen indicadores SYN. Esto es parte del protocolo de enlace de la conexión TCP (SYN, SYN + ACK, ACK) necesario para crear una conexión. A continuación se muestra un ejemplo de una interacción en la que se establecen las opciones MSS (tamaño máximo de segmento), SACK y WS (escala de ventana).

Entonces, ¿a dónde se han ido nuestras opciones de TCP?

Aunque habíamos asociado las opciones de escalado de ventana y SACK faltantes con ventanas de congestión más pequeñas y conexiones de bajo rendimiento, todavía no teníamos idea de por qué estas opciones estaban desactivadas para algunas de nuestras conexiones. Después de todo, ¡todas las conexiones se crearon con el mismo código!

Decidimos centrarnos en la opción SACK porque es una marca simple, con la esperanza de que sea más fácil de depurar. En Linux, SACK está controlado globalmente por un sysctl y no puede controlarse por conexión. Y tuvimos SACK habilitado en nuestras máquinas:

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

No sabíamos cómo nuestro programa podría haberse perdido estas opciones en algunas conexiones. Comenzamos capturando el protocolo de enlace TCP durante la configuración de la conexión. Descubrimos que el mensaje SYN inicial tenía configuradas las opciones esperadas, pero SYN + ACK eliminó SACK y la escala de la ventana.

Abrimos la pila TCP del kernel de Linux y comenzamos a buscar cómo se diseñan las opciones SYN + ACK. Nosotros encontramos tcp_make_synackque llama 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 if (foc) { -> sack_ok)) {opts-> opciones | = OPTION_SACK_ADVERTISE; si (improbable (! ireq-> tstamp_ok)) restante - = TCPOLEN_SACKPERM_ALIGNED; } ... return MAX_TCP_OPTION_SPACE - restante; }

Vimos que la opción SACK simplemente se establece en función de si la solicitud entrante tiene el conjunto de opciones SACK, lo cual no fue muy útil. Sabíamos que SACK se estaba despojando de esta conexión entre SYN y SYN + ACK, y aún teníamos que encontrar dónde estaba sucediendo.

Echamos un vistazo a la solicitud entrante analizando en 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: if (opsize == TCPOLEN_SACK-> syn &&&! net-> ipv4.sysctl_tcp_sack) {opt_rx-> sack_ok = TCP_SACK_SEEN; tcp_sack_reset (opt_rx); } romper; ...}

Vimos que, para analizar positivamente una opción SACK en una solicitud entrante, la solicitud debe tener el indicador SYN (lo hizo), la conexión no debe establecerse (no lo estaba) y el sysctl net.ipv4.tcp_sack debe estar habilitado (estaba). Aquí no hubo suerte.

Como parte de nuestra navegación, notamos que al gestionar solicitudes de conexión en tcp_conn_request, a veces borra las opciones:

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); ... return 0; }

Rápidamente descubrimos que la 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.

¿Qué son las cookies TCP SYN?

Las cookies TCP SYN se pueden caracterizar de la siguiente manera.

Inundación SYN

Los servidores TCP normalmente tienen una cantidad limitada de espacio en la cola SYN para las conexiones que aún no se han establecido. Cuando esta cola está llena, el servidor no puede aceptar más conexiones y debe eliminar las solicitudes SYN entrantes.

Este comportamiento conduce a un ataque de denegación de servicio llamado SYN flooding. El atacante envía muchas solicitudes SYN a un servidor, pero cuando el servidor responde con SYN + ACK, el atacante ignora la respuesta y nunca envía un ACK para completar la configuración de la conexión. Esto hace que el servidor intente reenviar mensajes SYN + ACK con temporizadores de retroceso progresivos. Si el atacante nunca responde y continúa enviando solicitudes SYN, puede mantener la cola SYN de los servidores llena en todo momento, evitando que los clientes legítimos establezcan conexiones con el servidor.

Resistiendo la inundación SYN

Las cookies TCP SYN resuelven este problema al permitir que el servidor responda con SYN + ACK y establezca una conexión incluso cuando la cola SYN está llena. Lo que hacen las cookies SYN es en realidad codificar las opciones que normalmente se almacenarían en la cola SYN (más un hash criptográfico de la hora aproximada y las IP y puertos de origen / destino) en el valor del número de secuencia inicial en SYN + ACK. El servidor puede deshacerse de la entrada de la cola SYN y no desperdiciar memoria en esta conexión. Cuando el cliente (legítimo) finalmente responda con un mensaje ACK, contendrá el mismo número de secuencia inicial. Luego, el servidor puede decodificar el hash del tiempo y, si es válido, decodificar las opciones y completar la configuración de la conexión sin usar ningún espacio de cola SYN.

Inconvenientes de las cookies SYN

El uso de cookies SYN para establecer una conexión tiene un inconveniente: no hay suficiente espacio en el número de secuencia inicial para codificar todas las opciones. La pila TCP de Linux solo codifica el tamaño máximo del segmento (una opción requerida) y envía un SYN + ACK que rechaza todas las demás opciones, incluidas las opciones de SACK y escala de ventana. Esto no suele ser un problema porque solo se usa cuando el servidor tiene una cola SYN completa, lo cual no es probable a menos que esté bajo un ataque de inundación SYN.

A continuación se muestra un ejemplo de interacción que muestra cómo se crearía una conexión con las cookies SYN cuando la cola SYN de un servidor está llena.

La anomalía del rendimiento del almacenamiento: el problema de TCP de Qumulo

Después de estudiar las cookies de TCP SYN, reconocimos que probablemente eran responsables de que nuestras conexiones perdieran periódicamente las opciones de TCP. Seguramente, pensamos, nuestras máquinas de prueba no estaban bajo un ataque de inundación SYN, por lo que sus colas SYN no deberían estar llenas.

Volvimos a leer el kernel de Linux y descubrimos que el tamaño máximo de cola SYN estaba configurado en 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; ...}

A partir de ahí, rastreamos a través de las personas que llamaron para encontrar que el valor de la acumulación se estableció directamente en el escuchan syscall. Sacamos el código de socket de Qumulo y rápidamente vimos que cuando escuchamos las conexiones, siempre usábamos una acumulación de tamaño 5.

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

Durante la inicialización del clúster, estábamos creando una red de malla conectada entre todas las máquinas, por lo que, por supuesto, teníamos más de 5 conexiones creadas a la vez para cualquier clúster de tamaño suficiente. ¡SYN inundó nuestro propio clúster desde adentro!

Rápidamente hicimos un cambio para aumentar el tamaño de la cartera de pedidos que usaba Qumulo y todos los resultados de mal desempeño desaparecieron: ¡Caso cerrado!

Nota del editor: esta publicación se publicó en diciembre de 2020.

Más información

De qumulo Equipo de ingeniería está contratando y tenemos varios ofertas de trabajo - échales un vistazo y aprende sobre la vida en Qumulo.

Contáctanos

Haz una prueba de manejo. Haga una demostración de Qumulo en nuestros nuevos laboratorios prácticos interactivos, o solicite una demostración o una prueba gratuita.

Suscríbete al blog de Qumulo para historias de clientes, conocimientos técnicos, tendencias de la industria y noticias de productos.

Artículos Relacionados

Ir al Inicio