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.

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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.

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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.

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

Para simplificar, consideramos que para a escala horizontal (e para simplificar os cálculos) cada servizo e base de datos se despregan xuntos:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.prof

Os resultados pódense mostrar así:

$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

Os enderezos que busca o programa pertencen a Postgresql. Prema en FindByAge:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)

if err != nil {
   return nil, err
}

Execución, observación, análise

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

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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:

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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í):

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
   return nil, err
}

Execución, observación, análise

3000 solicitudes por segundo

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

p99 é menos de 60 ms con significativamente menos p100!

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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í.

SRE: Análise de rendemento. Método de configuración mediante un servidor web sinxelo en Go

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.

Fonte: www.habr.com

Engadir un comentario