ProHoster > Blog > Administración > SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go
SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go
A análise e axuste do rendemento é unha poderosa ferramenta para verificar o cumprimento do rendemento dos clientes.
A análise de rendemento pode utilizarse para comprobar os pescozos de botella nun programa aplicando un enfoque científico para probar experimentos de axuste. Este artigo define un enfoque xeral para a análise e axuste do rendemento, usando un servidor web Go como exemplo.
Go é especialmente bo aquí porque ten ferramentas de creación de perfiles pprof na biblioteca estándar.
estratexia
Imos crear unha lista resumida para a nosa análise estrutural. Tentaremos utilizar algúns datos para tomar decisións en lugar de facer cambios baseados na intuición ou en adiviñas. Para iso faremos isto:
Determinamos os límites de optimización (requisitos);
Calculamos a carga de transacción para o sistema;
Realizamos a proba (crear datos);
Observamos;
Analizamos: cúmprense todos os requisitos?
Fixémolo cientificamente, facemos unha hipótese;
Realizamos un experimento para comprobar esta hipótese.
Arquitectura simple do servidor HTTP
Para este artigo utilizaremos un pequeno servidor HTTP en Golang. Pódese atopar todo o código deste artigo aquí.
A aplicación que se está a analizar é un servidor HTTP que consulta Postgresql para cada solicitude. Ademais, hai Prometheus, node_exporter e Grafana para recoller e mostrar métricas de aplicacións e sistemas.
Para simplificar, consideramos que para a escala horizontal (e para simplificar os cálculos) cada servizo e base de datos se despregan xuntos:
Definición de obxectivos
Neste paso, decidimos o obxectivo. Que tratamos de analizar? Como sabemos cando é hora de rematar? Neste artigo, imaxinaremos que temos clientes e que o noso servizo procesará 10 solicitudes por segundo.
В Libro Google SRE Os métodos de selección e modelización son discutidos en detalle. Fagamos o mesmo e creemos modelos:
Latencia: o 99% das solicitudes deben completarse en menos de 60 ms;
Custo: o servizo debería consumir a cantidade mínima de diñeiro que pensemos que é razoablemente posible. Para iso, maximizamos o rendemento;
Planificación da capacidade: require comprender e documentar cantas instancias da aplicación terán que executarse, incluída a funcionalidade de escalado xeral e cantas instancias serán necesarias para cumprir os requisitos de carga e aprovisionamento iniciais. redundancia n+1.
A latencia pode requirir optimización ademais da análise, pero é evidente que o rendemento debe ser analizado. Cando se utiliza o proceso SRE SLO, a solicitude de atraso procede do cliente ou da empresa, representada polo propietario do produto. E o noso servizo cumprirá esta obriga desde o principio sen ningunha configuración!
Configurar un ambiente de proba
Coa axuda dun ambiente de proba, poderemos colocar unha carga medida no noso sistema. Para a súa análise xeraranse datos sobre o rendemento do servizo web.
Carga de transacción
Este ambiente usa Vegeta para crear unha taxa de solicitude HTTP personalizada ata que se deteña:
$ make load-test LOAD_TEST_RATE=50
echo "POST http://localhost:8080" | vegeta attack -body tests/fixtures/age_no_match.json -rate=50 -duration=0 | tee results.bin | vegeta report
Observación
A carga transaccional aplicarase en tempo de execución. Ademais das métricas da aplicación (número de solicitudes, latencia de resposta) e do sistema operativo (memoria, CPU, IOPS), realizarase un perfil de aplicación para comprender onde ten problemas e como se consume o tempo da CPU.
Perfilado
O perfilado é un tipo de medición que che permite ver a onde vai o tempo da CPU cando se está a executar unha aplicación. Permítelle determinar exactamente onde e canto tempo se dedica ao procesador:
Estes datos pódense usar durante a análise para obter información sobre o tempo perdido da CPU e o traballo innecesario que se está a realizar. Go (pprof) pode xerar perfís e visualizalos como gráficos de chama usando un conxunto estándar de ferramentas. Falarei sobre a súa guía de uso e configuración máis adiante no artigo.
Execución, observación, análise.
Imos facer un experimento. Realizaremos, observaremos e analizaremos ata que esteamos satisfeitos coa actuación. Escollemos un valor de carga arbitrariamente baixo para aplicalo para obter os resultados das primeiras observacións. En cada paso posterior aumentaremos a carga cun determinado factor de escala, elixido con algunha variación. Cada proba de carga realízase co número de solicitudes axustado: make load-test LOAD_TEST_RATE=X.
50 solicitudes por segundo
Preste atención aos dous gráficos superiores. A parte superior esquerda mostra que a nosa aplicación procesa 50 solicitudes por segundo (pensa) e a parte superior dereita mostra a duración de cada solicitude. Ambos parámetros axúdannos a mirar e analizar se estamos dentro dos nosos límites de rendemento ou non. Liña vermella no gráfico Latencia de solicitude HTTP mostra o SLO a 60 ms. A liña mostra que estamos moi por debaixo do noso tempo máximo de resposta.
Vexamos o lado do custo:
10000 solicitudes por segundo / 50 solicitudes por servidor = 200 servidores + 1
Aínda podemos mellorar esta cifra.
500 solicitudes por segundo
Empezan a suceder cousas máis interesantes cando a carga chega a 500 solicitudes por segundo:
De novo, no gráfico superior esquerdo podes ver que a aplicación está gravando a carga normal. Se non é o caso, hai un problema no servidor no que se está a executar a aplicación. O gráfico de latencia de resposta está situado na parte superior dereita, mostrando que 500 solicitudes por segundo provocaron un atraso de resposta de 25-40 ms. O percentil 99 aínda encaixa ben no SLO de 60 ms escollido anteriormente.
En canto ao custo:
10000 solicitudes por segundo / 500 solicitudes por servidor = 20 servidores + 1
Aínda se pode mellorar todo.
1000 solicitudes por segundo
Gran lanzamento! A aplicación mostra que procesou 1000 solicitudes por segundo, pero o límite de latencia foi violado polo SLO. Isto pódese ver na liña p99 no gráfico superior dereito. A pesar do feito de que a liña p100 é moito maior, os atrasos reais son superiores ao máximo de 60 ms. Mergullémonos na creación de perfiles para descubrir o que fai realmente a aplicación.
Perfilado
Para o perfilado, configuramos a carga en 1000 solicitudes por segundo e, a continuación, utilizamos pprof para capturar datos para saber onde está a gastar a aplicación o tempo da CPU. Isto pódese facer activando o punto final HTTP pprof, e despois, baixo carga, garda os resultados usando curl:
$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof
O gráfico mostra onde e canto gasta a aplicación en tempo de CPU. Da descrición de Brendan Gregg:
O eixe X é a poboación do perfil da pila, ordenada alfabeticamente (non é o tempo), o eixe Y mostra a profundidade da pila, contando desde cero en [arriba]. Cada rectángulo é un marco de pila. Canto máis amplo sexa o cadro, máis veces estará presente nas pilas. O que está enriba execútase na CPU e o que hai debaixo son os elementos fillos. As cores normalmente non significan nada, senón que simplemente son escollidas ao chou para diferenciar os cadros.
Análise - hipótese
Para a sintonía, centrarémonos en tentar atopar tempo perdido da CPU. Buscaremos as maiores fontes de gasto inútil e eliminaremos. Ben, dado que a elaboración de perfiles revela con moita precisión onde está exactamente a aplicación a gastar o seu tempo de procesador, é posible que teñas que facelo varias veces e tamén terás que cambiar o código fonte da aplicación, volver realizar as probas e comprobar que o rendemento se achega ao obxectivo.
Seguindo as recomendacións de Brendan Gregg, leremos o gráfico de arriba a abaixo. Cada liña mostra un marco de pila (chamada de función). A primeira liña é o punto de entrada ao programa, o pai de todas as outras chamadas (noutras palabras, todas as demais chamadas terán na súa pila). A seguinte liña xa é diferente:
Se pasa o cursor sobre o nome dunha función no gráfico, amosarase o tempo total que estivo na pila durante a depuración. A función HTTPServe estivo alí o 65 % das veces, outras funcións de execución runtime.mcall, mstart и gc, ocupaba o resto do tempo. Dato curioso: o 5% do tempo total dedícase a consultas DNS:
Os enderezos que busca o programa pertencen a Postgresql. Prema en FindByAge:
Curiosamente, o programa mostra que, en principio, hai tres fontes principais que engaden atrasos: abrir e pechar conexións, solicitar datos e conectarse á base de datos. O gráfico mostra que as solicitudes de DNS, a apertura e o peche de conexións ocupan preto do 13% do tempo total de execución.
Hipótese: A reutilización de conexións mediante a agrupación debería reducir o tempo dunha única solicitude HTTP, permitindo un maior rendemento e menor latencia.
Configurar a aplicación - experimento
Actualizamos o código fonte, tentamos eliminar a conexión a Postgresql para cada solicitude. A primeira opción é usar piscina de conexión a nivel de aplicación. Neste experimento nós imos configuralo agrupación de conexións usando el controlador sql para go:
Despois de reiniciar a proba con 1000 solicitudes por segundo, está claro que os niveis de latencia de p99 volveron á normalidade cun SLO de 60 ms.
Cal é o custo?
10000 solicitudes por segundo / 1000 solicitudes por servidor = 10 servidores + 1
Imos facelo aínda mellor!
2000 solicitudes por segundo
Dobrar a carga mostra o mesmo, o gráfico superior esquerdo mostra que a aplicación consegue procesar 2000 solicitudes por segundo, p100 é inferior a 60 ms, p99 satisface o SLO.
En canto ao custo:
10000 solicitudes por segundo / 2000 solicitudes por servidor = 5 servidores + 1
3000 solicitudes por segundo
Aquí a aplicación pode procesar 3000 solicitudes cunha latencia p99 de menos de 60 ms. Non se infrinxe o SLO e o custo acéptase do seguinte xeito:
10000 solicitudes por segundo / por 3000 solicitudes por servidor = 4 servidores + 1 (o autor redondeou, aprox. tradutor)
Imos tentar outra rolda de análise.
Análise - hipótese
Recollemos e mostramos os resultados da depuración da aplicación a 3000 solicitudes por segundo:
Aínda se dedica o 6% do tempo a establecer conexións. A configuración do grupo mellorou o rendemento, pero aínda podes ver que a aplicación segue traballando na creación de novas conexións coa base de datos.
Hipótese: As conexións, a pesar da presenza dunha piscina, aínda están eliminadas e limpas, polo que a aplicación necesita restablecelas. Establecer o número de conexións pendentes ao tamaño do grupo debería axudar coa latencia ao minimizar o tempo que a aplicación dedica a crear unha conexión.
Configurar a aplicación - experimento
Tentando instalar MaxIdleConns igual ao tamaño da piscina (tamén descrito aquí):
p99 é menos de 60 ms con significativamente menos p100!
A comprobación do gráfico de chama mostra que a conexión xa non se nota. Comprobamos con máis detalle pg(*conn).query — Tampouco notamos a conexión que se establece aquí.
Conclusión
A análise de rendemento é fundamental para comprender que se están cumprindo as expectativas dos clientes e os requisitos non funcionais. A análise comparando as observacións coas expectativas dos clientes pode axudar a determinar o que é aceptable e o que non. Go ofrece potentes ferramentas integradas na biblioteca estándar que fan que a análise sexa sinxela e accesible.