SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Аналіз прадукцыйнасці і настройка - магутны інструмент праверкі адпаведнасці прадукцыйнасці для кліентаў.

Аналіз прадукцыйнасці можна ўжываць для праверкі вузкіх месцаў у праграме, ужываючы навуковы падыход пры праверцы эксперыментаў па наладзе. Гэты артыкул вызначае агульны падыход да аналізу прадукцыйнасці і наладзе з выкарыстаннем у якасці прыкладу вэбсервера на Go.

Go тут асабліва добра падыходзіць, паколькі ў яго ёсць прылады прафілявання pprof у стандартнай бібліятэцы.

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Стратэгія

Давайце створым зводны спіс для нашага структурнага аналізу. Мы паспрабуем выкарыстоўваць некаторыя дадзеныя для прыняцця рашэнняў замест таго, каб уносіць змены, заснаваныя на інтуіцыі ці здагадках. Для гэтага зробім так:

  • Вызначаем межы аптымізацыі (патрабаванні);
  • Разлічваем транзакцыйную нагрузку для сістэмы;
  • Выконваем тэст (ствараем дадзеныя);
  • Назіраем;
  • Аналізуем - ці ўсё патрабаванні выконваюцца?
  • Наладжваем па-навуковаму, які робіцца гіпотэзу;
  • Выконваем эксперымент для праверкі гэтай гіпотэзы.

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Архітэктура простага сервера HTTP

Для гэтага артыкула мы будзем выкарыстоўваць маленькі сервер HTTP на Golang. Увесь код з гэтага артыкула можна знайсці тут.

Аналізаваны дадатак - HTTP-сервер, які апытвае Postgresql на кожны запыт. Дадаткова ёсць Prometheus, node_exporter і Grafana для збору і адлюстравання метрык прыкладання і сістэмы.

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Для спрашчэння лічым, што для гарызантальнага маштабавання (і спрашчэння разлікаў) кожны сэрвіс і база даных разгортваюцца разам:

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Вызначаем мэты

На гэтым кроку вызначаемся з мэтай. Што мы спрабуем прааналізаваць? Як мы даведаемся, што час заканчваць? У гэтым артыкуле мы ўявім, што ў нас ёсць кліенты, і што наш сэрвіс будзе апрацоўваць 10 запытаў у секунду.

В Google SRE Book падрабязна разгледжаны спосабы выбару і мадэляванне. Зробім гэтак жа, пабудуем мадэлі:

  • Затрымка: 99% запытаў павінны выконвацца менш, чым за 60мс;
  • Кошт: сэрвіс павінен спажываць мінімальную суму грошай, якая нам здасца разумна магчымай. Для гэтага максымізуем прапускную здольнасць;
  • Планаванне магутнасцяў: патрабуецца разуменне і дакументаванне таго, колькі экзэмпляраў прыкладання спатрэбіцца запусціць, уключаючы агульную функцыю маштабавання, а таксама колькі экзэмпляраў спатрэбіцца для задавальнення пачатковых патрабаванняў па нагрузцы і забеспячэнню надмернасці 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

назіранне

У час выканання будзе прыменена транзакцыйная нагрузка. У дадатак да метрыкаў прыкладання (колькасць запытаў, затрымкі пры адказе) і аперацыйнай сістэмы (памяць, CPU, IOPS) будзе запушчана прафіляванне прыкладання, каб зразумець, дзе ў ім ёсць праблемы, а таксама як спажываецца працэсарны час.

Прафіляванне

Прафіляванне - гэта тып вымярэння, які дазваляе ўбачыць куды ідзе працэсарны час пры працы прыкладання. Яно дазваляе вызначыць дзе менавіта і колькі ідзе працэсарнага часу:

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Гэтыя дадзеныя можна выкарыстоўваць падчас аналізу, каб атрымаць уяўленне аб бескарысна выдаткаваным працэсарным часе і выкананай непатрэбнай працы. Go (pprof) можа генераваць профілі і візуалізаваць іх у выглядзе flame graph, выкарыстоўваючы стандартны набор інструментаў. Я раскажу аб іх выкарыстанні і кіраўніцтве па наладзе крыху ніжэй у артыкуле.

Выкананне, назіранне, аналіз.

Правядзем эксперымент. Мы будзем выконваць, назіраць і аналізаваць, пакуль прадукцыйнасць нас не задаволіць. Выберам адвольна нізкую велічыню нагрузкі, каб прымяніць яе для атрымання вынікаў першых назіранняў. На кожным наступным кроку будзем павялічваць нагрузку з некаторым каэфіцыентам маштабавання, абраным з некаторым роскідам. Кожны запуск нагрузачнага тэсціравання выконваецца з рэгуляваннем колькасці запытаў: make load-test LOAD_TEST_RATE=X.

50 запытаў у секунду

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Звярніце ўвагу на два верхніх графіка. Верхні левы паказвае, што наша дадатак апрацоўвае 50 запытаў у секунду (на яго думку), а верхні правы - працягласць кожнага запыту. Абодва параметры дапамагаюць нам глядзець і аналізаваць: ці залазім у нашы межы прадукцыйнасці ці не. Чырвоная лінія на графіцы HTTP Request Latency паказвае SLO у 60мс. Па лініі бачна, што мы нашмат ніжэйшыя за наш максімальны час адказу.

Паглядзім з боку кошту:

10000 запытаў у секунду / на 50 запытаў на сервер = 200 сервераў + 1

Мы ўсё яшчэ можам палепшыць гэты паказчык.

500 запытаў у секунду

Больш цікавыя рэчы пачынаюць адбывацца, калі нагрузка становіцца 500 запытаў у секунду:

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Ізноў жа на левым верхнім графіцы відаць, што прыкладанне фіксуе звычайную нагрузку. Калі гэта не так - ёсць праблема на серверы, на якім запушчана дадатак. Графік з затрымкай адказу размешчаны зверху справа, паказвае, што 500 запытаў у секунду прывялі да затрымкі адказу ў 25-40мс. 99 перцентиль усё яшчэ шыкоўна залазіць у SLO 60мс, абраны вышэй.

З пункту гледжання кошту:

10000 запытаў у секунду / на 500 запытаў на сервер = 20 сервераў + 1

Усё яшчэ можна палепшыць.

1000 запытаў у секунду

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Выдатны запуск! Прыкладанне паказвае, што апрацавала 1000 запытаў у секунду, аднак мяжа па затрымцы была парушана з боку SLO. Гэта відаць па лініі p99 на верхнім правым графіцы. Нягледзячы на ​​тое, што лінія p100 нашмат вышэй, рэальныя затрымкі вышэй максімуму ў 60мс. Давайце нырнем у прафіляванне, каб даведацца, што на самой справе робіць прыкладанне.

Прафіляванне

Для прафілявання мы ставім нагрузку ў 1000 запытаў у секунду, затым выкарыстоўваем pprof для зняцця дадзеных, каб даведацца, дзе дадатак марнуе працэсарны час. Гэта можна зрабіць актывуючы HTTP endpoint 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

Графік паказвае, дзе і колькі дадатак марнуе працэсарнага часу. З апісання ад Brendan Gregg:

Па восі X - запаўненне профіля стэка, адсартаванае ў алфавітным парадку (гэта не час), вось Y паказвае глыбіню стэка, лічачы ад нуля ў [top]. Кожны прастакутнік - кадр стэка. Чым шырэй кадр - тым часцей яна прысутнічае ў стэках. Тое, што зверху – працуе на CPU, а ніжэй – даччыныя элементы. Колеры, як правіла, не азначаюць нічога, а проста выбіраюцца выпадковым чынам для распазнавання кадраў.

Аналіз - гіпотэза

Для налады мы засяродзімся на спробе знайсці бескарысныя марнаванні працэсарнага часу. Будзем шукаць найболей буйныя крыніцы бескарысных марнаванняў і выдаляць іх. Ну, а з улікам таго, што прафіляванне вельмі сапраўды расчыняе, дзе менавіта прыкладанне марнуе свой працэсарны час, магчыма прыйдзецца гэта зрабіць некалькі разоў, а таксама запатрабуецца змяняць зыходны код прыкладання, перазапускаць тэсты і назіраць, што прадукцыйнасць набліжаецца да вызначанай.

Прытрымліваючыся рэкамендацый Brendan Gregg мы будзем чытаць графік зверху ўніз. Кожны радок адлюстроўвае кадр стэка (выклік функцыі). Першы радок – кропка ўваходу ў праграму, бацька ўсіх астатніх выклікаў (гэта значыць усе іншыя выклікі будуць мець яе ў сваім стэку). Наступны радок ужо адрозніваецца:

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Калі навесці курсор на імя функцыі на графіцы - адлюструецца агульны час, колькі яна была ў стэку падчас адладкі. Функцыя HTTPServe знаходзілася тамака 65% часу, іншыя функцыі runtime, 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 60мс!

Што па кошце?

10000 запытаў у секунду / на 1000 запытаў на сервер = 10 сервераў + 1

Давайце зробім яшчэ лепш!

2000 запытаў у секунду

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Падваенне нагрузкі паказвае тое ж самае, левы верхні графік дэманструе, што прыкладанне паспявае адпрацаваць 2000 запытаў у секунду, p100 ніжэй за 60мс, p99 задавальняе SLO.

З пункту гледжання кошту:

10000 запытаў у секунду / на 2000 запытаў на сервер = 5 сервераў + 1

3000 запытаў у секунду

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Тут дадатак можа апрацаваць 3000 запытаў з затрымкай p99 менш за 60мс. SLO не парушаецца, а кошт прынята так:

10000 запытаў у секунду / на 3000 запытаў на сервер = 4 сервера + 1 (аўтар акругліў да большага, заўв. перакладчыка)

Давайце паспрабуем яшчэ адзін раунд аналізу.

Аналіз - гіпотэза

Збіраны і адлюстроўваем вынікі адладкі прыкладання на 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 менш за 60мс з значна меншым p100!

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Праверка flame graph паказвае, што ўстаноўка злучэння больш не заўважная! Правяраем больш падрабязна pg(*conn).query - таксама не заўважаны ўсталёўкі злучэння тут.

SRE: Аналіз прадукцыйнасці. Спосаб наладкі з выкарыстаннем простага вэбсервера на Go

Заключэнне

Аналіз прадукцыйнасці крытычны для разумення таго, што кліенцкія чаканні і нефункцыянальныя патрабаванні задаволены. Аналіз шляхам супастаўлення назіранняў з чаканнямі кліентаў можа дапамагчы вызначыцца з тым, што з'яўляецца прымальным, а што - не. Go дае эфектыўныя прылады, убудаваныя ў стандартную бібліятэку, з дапамогай якіх можна зрабіць аналіз простым і даступным.

Крыніца: habr.com

Дадаць каментар