SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Анализът и настройката на производителността е мощен инструмент за проверка на съответствието на производителността за клиентите.

Анализът на производителността може да се използва за проверка за тесни места в програма чрез прилагане на научен подход към тестване на експерименти за настройка. Тази статия дефинира общ подход за анализ на производителността и настройка, използвайки Go уеб сървър като пример.

Go е особено добър тук, защото има инструменти за профилиране pprof в стандартната библиотека.

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

стратегия

Нека създадем обобщен списък за нашия структурен анализ. Ще се опитаме да използваме някои данни, за да вземем решения, вместо да правим промени въз основа на интуиция или догадки. За да направим това, ще направим следното:

  • Определяме границите (изискванията) за оптимизация;
  • Изчисляваме транзакционното натоварване за системата;
  • Извършваме теста (създаваме данни);
  • Ние наблюдаваме;
  • Ние анализираме - изпълнени ли са всички изисквания?
  • Поставяме го научно, правим хипотеза;
  • Провеждаме експеримент, за да проверим тази хипотеза.

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Проста HTTP сървърна архитектура

За тази статия ще използваме малък HTTP сървър в Golang. Целият код от тази статия може да бъде намерен тук.

Приложението, което се анализира, е HTTP сървър, който анкетира Postgresql за всяка заявка. Освен това има Prometheus, node_exporter и Grafana за събиране и показване на показатели за приложения и системи.

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

За да опростим, считаме, че за хоризонтално мащабиране (и опростяване на изчисленията) всяка услуга и база данни се разполагат заедно:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Определяне на цели

На тази стъпка решаваме целта. Какво се опитваме да анализираме? Как да разберем кога е време да свършим? В тази статия ще си представим, че имаме клиенти и че нашата услуга ще обработва 10 000 заявки в секунда.

В Google SRE книга Подробно са разгледани методите за подбор и моделиране. Нека направим същото и да изградим модели:

  • Латентност: 99% от заявките трябва да бъдат изпълнени за по-малко от 60 ms;
  • Цена: Услугата трябва да изразходва минималната сума пари, която смятаме за разумно възможна. За да направим това, ние максимизираме пропускателната способност;
  • Планиране на капацитета: Изисква разбиране и документиране колко екземпляра на приложението ще трябва да бъдат стартирани, включително цялостна функционалност за мащабиране, и колко екземпляра ще са необходими, за да се изпълнят първоначалните изисквания за натоварване и осигуряване излишък n+1.

Латентността може да изисква оптимизация в допълнение към анализа, но пропускателната способност очевидно трябва да бъде анализирана. Когато използвате процеса SRE SLO, искането за забавяне идва от клиента или бизнеса, представляван от собственика на продукта. И нашият сервиз ще изпълни това задължение от самото начало без никакви настройки!

Настройка на тестова среда

С помощта на тестова среда ще можем да поставим измерено натоварване на нашата система. За анализ ще бъдат генерирани данни за ефективността на уеб услугата.

Натоварване на транзакцията

Тази среда използва вегетирам за създаване на персонализирана честота на HTTP заявка до спиране:

$ 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

гледане

Транзакционното натоварване ще бъде приложено по време на изпълнение. В допълнение към показателите за приложението (брой заявки, забавяне на отговора) и операционната система (памет, процесор, IOPS), профилирането на приложението ще се изпълнява, за да се разбере къде има проблеми и как се изразходва процесорното време.

Профилиране

Профилирането е вид измерване, което ви позволява да видите къде отива процесорното време, когато дадено приложение работи. Тя ви позволява да определите точно къде и колко процесорно време се изразходва:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Тези данни могат да се използват по време на анализ, за ​​да се получи представа за загубеното процесорно време и извършваната ненужна работа. Go (pprof) може да генерира профили и да ги визуализира като пламъчни графики с помощта на стандартен набор от инструменти. Ще говоря за тяхното използване и ръководство за настройка по-късно в статията.

Изпълнение, наблюдение, анализ.

Нека проведем експеримент. Ще извършваме, наблюдаваме и анализираме, докато не сме доволни от представянето. Нека изберем произволно ниска стойност на натоварване, за да я приложим, за да получим резултатите от първите наблюдения. При всяка следваща стъпка ще увеличаваме натоварването с определен коефициент на мащабиране, избран с известна вариация. Всяко изпълнение на тестване на натоварването се извършва с коригиран брой заявки: make load-test LOAD_TEST_RATE=X.

50 заявки в секунда

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Обърнете внимание на горните две графики. Горе вляво показва, че нашето приложение обработва 50 заявки в секунда (мисли), а горе вдясно показва продължителността на всяка заявка. И двата параметъра ни помагат да погледнем и анализираме дали сме в границите на ефективността си или не. Червена линия на графиката Забавяне на HTTP заявка показва SLO на 60ms. Линията показва, че сме доста под максималното време за отговор.

Нека да разгледаме разходната страна:

10000 50 заявки в секунда / 200 заявки на сървър = 1 сървъра + XNUMX

Все още можем да подобрим тази цифра.

500 заявки в секунда

По-интересни неща започват да се случват, когато натоварването достигне 500 заявки в секунда:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Отново в горната лява графика можете да видите, че приложението записва нормално натоварване. Ако това не е така, има проблем на сървъра, на който работи приложението. Графиката на забавяне на отговора се намира горе вдясно, показвайки, че 500 заявки в секунда са довели до забавяне на отговора от 25-40 ms. 99-ият процентил все още се вписва добре в 60ms SLO, избран по-горе.

По отношение на разходите:

10000 500 заявки в секунда / 20 заявки на сървър = 1 сървъра + XNUMX

Всичко все още може да се подобри.

1000 заявки в секунда

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Страхотен старт! Приложението показва, че е обработило 1000 заявки в секунда, но ограничението за латентност е нарушено от SLO. Това може да се види в ред p99 в горната дясна графика. Въпреки факта, че линията p100 е много по-висока, действителните закъснения са по-високи от максимума от 60 ms. Нека се потопим в профилирането, за да разберем какво всъщност прави приложението.

Профилиране

За профилиране задаваме натоварването на 1000 заявки в секунда, след което използваме pprof за улавяне на данни, за да разберете къде приложението изразходва процесорно време. Това може да стане чрез активиране на HTTP крайната точка pprofи след това, под натоварване, запазете резултатите с помощта на curl:

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

Резултатите могат да бъдат показани по следния начин:

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

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Графиката показва къде и колко приложението изразходва процесорно време. От описанието от Брендън Грег:

Оста X е популацията на профила на стека, сортирана по азбучен ред (това не е време), оста Y показва дълбочината на стека, като се брои от нула в [горе]. Всеки правоъгълник е стекова рамка. Колкото по-широка е рамката, толкова по-често присъства в стековете. Това, което е отгоре, работи на процесора, а това, което е отдолу, са дъщерните елементи. Цветовете обикновено не означават нищо, а просто се избират на случаен принцип, за да се различават рамките.

Анализ – хипотеза

За настройка ще се съсредоточим върху опитите да намерим загубеното процесорно време. Ще потърсим най-големите източници на безполезни разходи и ще ги премахнем. Е, като се има предвид, че профилирането разкрива много точно къде точно приложението изразходва процесорното си време, може да се наложи да го направите няколко пъти и също така ще трябва да промените изходния код на приложението, да повторите тестовете и да видите, че производителността се доближава до целта.

Следвайки препоръките на Брендън Грег, ще прочетем графиката отгоре надолу. Всеки ред показва стекова рамка (извикване на функция). Първият ред е входната точка в програмата, родителят на всички други повиквания (с други думи, всички други повиквания ще го имат в техния стек). Следващият ред вече е различен:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Ако задържите курсора върху името на функция на графиката, ще се покаже общото време, през което тя е била в стека по време на отстраняване на грешки. Функцията HTTPServe беше там 65% от времето, други функции по време на изпълнение runtime.mcall, mstart и gc, зае останалото време. Забавен факт: 5% от общото време се изразходва за DNS заявки:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Адресите, които програмата търси, принадлежат на Postgresql. Кликнете върху FindByAge:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Интересното е, че програмата показва, че по принцип има три основни източника, които добавят забавяния: отваряне и затваряне на връзки, искане на данни и свързване към базата данни. Графиката показва, че DNS заявките, отварянето и затварянето на връзки заемат около 13% от общото време за изпълнение.

Хипотеза: Повторното използване на връзки чрез групиране трябва да намали времето на една HTTP заявка, позволявайки по-висока пропускателна способност и по-ниско забавяне.

Настройка на приложението - експеримент

Ние актуализираме изходния код, опитваме се да премахнем връзката с Postgresql за всяка заявка. Първият вариант е да използвате пул за свързване на ниво приложение. В този експеримент ние нека го настроим групиране на връзки с помощта на sql драйвер за go:

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

if err != nil {
   return nil, err
}

Изпълнение, наблюдение, анализ

След рестартиране на теста с 1000 заявки в секунда, става ясно, че нивата на латентност на p99 са се върнали към нормалното със SLO от 60ms!

каква е цената

10000 1000 заявки в секунда / 10 заявки на сървър = 1 сървъра + XNUMX

Нека го направим още по-добре!

2000 заявки в секунда

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Удвояването на натоварването показва същото, горната лява графика показва, че приложението успява да обработи 2000 заявки в секунда, p100 е под 60ms, p99 удовлетворява SLO.

По отношение на разходите:

10000 2000 заявки в секунда / 5 заявки на сървър = 1 сървъра + XNUMX

3000 заявки в секунда

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Тук приложението може да обработи 3000 заявки с p99 латентност под 60ms. SLO не е нарушен и цената се приема, както следва:

10000 3000 заявки в секунда / на 4 1 заявки на сървър = XNUMX сървъра + XNUMX (авторът е закръглил, прибл. преводач)

Нека опитаме друг кръг от анализ.

Анализ – хипотеза

Ние събираме и показваме резултатите от отстраняването на грешки в приложението при 3000 заявки в секунда:

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Все още 6% от времето се изразходва за установяване на връзки. Настройването на пула подобри производителността, но все пак можете да видите, че приложението продължава да работи по създаването на нови връзки към базата данни.

Хипотеза: Връзките, въпреки наличието на пул, все още се премахват и изчистват, така че приложението трябва да ги нулира. Задаването на броя чакащи връзки към размера на пула трябва да помогне за забавянето чрез минимизиране на времето, което приложението прекарва в създаване на връзка.

Настройка на приложението - експеримент

Опитвам се да инсталирам MaxIdleConns равен на размера на басейна (описан също тук):

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

Изпълнение, наблюдение, анализ

3000 заявки в секунда

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

p99 е по-малко от 60ms със значително по-малко p100!

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Проверката на графиката на пламъка показва, че връзката вече не се забелязва! Нека проверим по-подробно pg(*conn).query — ние също не забелязваме връзката да се установява тук.

SRE: Анализ на ефективността. Метод за конфигуриране с помощта на прост уеб сървър в Go

Заключение

Анализът на ефективността е от решаващо значение за разбирането, че очакванията на клиентите и нефункционалните изисквания са изпълнени. Анализът чрез сравняване на наблюденията с очакванията на клиента може да помогне да се определи кое е приемливо и кое не. Go предоставя мощни инструменти, вградени в стандартната библиотека, които правят анализа прост и достъпен.

Източник: www.habr.com

Добавяне на нов коментар