Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

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

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

Перш чым аддаць Элвіна ў тэставанне, я правёў шмат эксперыментаў з 40 тыс. запытаў у секунду (QPS), усё паказалі затрымку менш за 10 мс. Я гатовы быў заявіць, што не згодзен з іх вынікамі. Але яшчэ раз зірнуўшы на ліст, я звярнуў увагу на нешта новае: я сапраўды не тэставаў умовы, якія яны згадалі, іх QPS быў нашмат ніжэй, чым мой. Я тэсціраваў на 40k QPS, а яны толькі на 1k. Я запусціў яшчэ адзін эксперымент, на гэты раз з больш нізкім QPS, проста каб улагодзіць іх.

Паколькі я пішу пра гэта ў блогу - верагодна, вы ўжо зразумелі: іх лічбы аказаліся правільнымі. Я правяраў свайго віртуальнага кліента зноў і зноў, усё з тым жа вынікам: нізкая колькасць запытаў не толькі павялічвае затрымку, але павялічвае колькасць запытаў з затрымкай больш за 10 мс. Іншымі словамі, калі на 40k QPS каля 50 запытаў у секунду перавышалі 50 мс, то на 1k QPS кожную секунду было 100 запытаў вышэй за 50 мс. Парадокс!

Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

Звужаем круг пошуку

Сутыкнуўшыся з праблемай затрымкі ў размеркаванай сістэме са шматлікімі кампанентамі перш за ўсё трэба скласці кароткі спіс падазраваных. Капнём крыху глыбей у архітэктуру Элвіна:

Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

Добрай адпраўной кропкай з'яўляецца спіс выкананых пераходаў уводу-высновы (сеткавыя выклікі/пошук па дыску і т. д.). Паспрабуем высветліць, дзе затрымка. Апроч відавочнага ўводу-вываду з кліентам, Элвін робіць дадатковы крок: ён звяртаецца да сховішча дадзеных. Аднак гэтае сховішча працуе ў адным кластары з Элвінам, таму там затрымка павінна быць менш, чым з кліентам. Такім чынам, спіс падазраваных:

  1. Сеткавы выклік ад кліента да Элвіна.
  2. Сеткавы выклік ад Элвіна да сховішча дадзеных.
  3. Пошук на дыску ў сховішчы дадзеных.
  4. Сеткавы выклік са сховішча дадзеных да Элвіна.
  5. Сеткавай выклік ад Элвіна да кліента.

Паспрабуем выкрасліць некаторыя пункты.

Сховішча дадзеных ні пры чым

Перш за ўсё я пераўтварыў Элвіна ў сервер ping-ping, які не апрацоўвае запыты. Атрымаўшы запыт, ён вяртае пусты адказ. Калі затрымка памяншаецца, то памылка ў рэалізацыі Элвіна або сховішчы дадзеных - нічога такога нечуванага. У першым эксперыменце атрымліваем такі графік:

Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

Як бачым, пры выкарыстанні сервера ping-ping не назіраецца ніякіх паляпшэнняў. Гэта азначае, што сховішча дадзеных не павялічвае затрымку, а спіс падазраваных скарачаецца ўдвая:

  1. Сеткавы выклік ад кліента да Элвіна.
  2. Сеткавай выклік ад Элвіна да кліента.

Выдатна! Спіс хутка скарачаецца. Я думаў, што амаль высветліў прычыну.

gRPC

Цяпер самы час прадставіць вам новага гульца: gRPC. Гэта бібліятэка з адкрытым зыходным кодам ад Google для ўнутрыпрацэснай сувязі RPC. хоць gRPC добра аптымізаваны і шырока выкарыстоўваецца, я першы раз выкарыстоўваў яго ў сістэме такога маштабу, і я чакаў, што мая рэалізацыя будзе неаптымальнай - мякка кажучы.

наяўнасць gRPC у стэку спарадзіла новае пытанне: можа, гэта мая рэалізацыя ці сам gRPC выклікае праблему затрымкі? Дадаем у спіс новага падазраванага:

  1. Кліент выклікае бібліятэку gRPC
  2. Бібліятэка gRPC на кліенце выконвае сеткавы выклік бібліятэкі gRPC на сэрвэры
  3. Бібліятэка gRPC звяртаецца да Элвіна (аперацыі няма ў выпадку сервера ping-pong)

Каб вы разумелі, як выглядае код, мая рэалізацыя кліента/Элвіна не моцна адрозніваецца ад кліент-серверных прыкладаў async.

Заўвага: прыведзены вышэй спіс крыху спрошчаны, бо gRPC дае магчымасць выкарыстання ўласнай (шаблоннай?) струменевай мадэлі, у якой пераплятаюцца стэк выканання gRPC і рэалізацыя карыстальніка. Дзеля прастаты будзем прытрымлівацца гэтай мадэлі.

Прафіляванне ўсё выправіць

Выкрасліўшы сховішчы дадзеных, я падумаў, што амаль скончыў: «Цяпер лёгка! Ужыем профіль і даведаемся, дзе ўзнікае затрымка». Я вялікі прыхільнік дакладнага прафілявання, таму што CPU вельмі хуткія і часцей за ўсё не зяўляюцца вузкім месцам. Большасць затрымак адбываецца, калі працэсар павінен спыніць апрацоўку, каб зрабіць нешта яшчэ. Дакладнае прафіляванне CPU зроблена менавіта для гэтага: яно сапраўды запісвае ўсё кантэкстныя перамыкачы і дае зразумець, дзе ўзнікаюць затрымкі.

Я ўзяў чатыры профіля: пад высокім QPS (маленькая затрымка) і з ping-pong серверам на нізкім QPS (вялікая затрымка), як на боку кліента, так і на боку сервера. І проста на ўсялякі выпадак таксама ўзяў узор профіля працэсара. Пры параўнанні профіляў я звычайна шукаю анамальны стэк выклікаў. Напрыклад, на дрэнным баку з высокай затрымкай адбываецца значна больш пераключэнняў кантэксту (у 10 і больш разоў). Але ў маім выпадку колькасць пераключэнняў кантэксту практычна супадала. Да майго жаху, там не аказалася нічога істотнага.

Дадатковая адладка

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

Што, калі

З самага пачатку мяне турбавала пэўны час затрымкі 50 мс. Гэта вельмі вялікае час. Я вырашыў, што буду выразаць кавалкі з кода, пакуль не змагу высветліць дакладна, якая частка выклікае гэтую памылку. Затым рушыў услед эксперымент, які спрацаваў.

Як звычайна, заднім розумам здаецца, што ўсё было відавочна. Я змясціў кліента на адну машыну з Элвінам - і адправіў запыт у localhost. І павелічэнне затрымкі знікла!

Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

Нешта было не так з сеткай.

Асвойваем навыкі сеткавага інжынера

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

На шчасце, інтэрнет любіць тых, хто хоча вучыцца. Спалучэнне ping і tracert здавалася дастаткова добрым пачаткам для адладкі праблем сеткавага транспарту.

Па-першае, я запусціў PsPing на TCP-порт Элвіна. Я выкарыстоўваў параметры па змаўчанні - нічога асаблівага. З больш за тысячу пінгаў ні адзін не перавысіў 10 мс, за выключэннем першага для разагравання. Гэта супярэчыць назіранаму павелічэнню затрымкі 50 мс у 99-м працэнты: там на кожныя 100 запытаў мы павінны былі ўбачыць каля аднаго запыту з затрымкай 50 мс.

Затым я паспрабаваў tracert: можа, праблема на адным з вузлоў па маршруце паміж Элвінам і кліентам. Але і трэйсер вярнуўся з пустымі рукамі.

Такім чынам, прычынай затрымкі быў не мой код, не рэалізацыя gRPC і ня сетка. Я пачаў ужо хвалявацца, што ніколі гэтага не разумею.

Цяпер, на якой АС мы знаходзімся

gRPC шырока выкарыстоўваецца ў Linux, але для Windows гэта экзотыка. Я вырашыў правесці эксперымент, які спрацаваў: я стварыў віртуальную машыну Linux, скампіляваў Элвіна для Linux і разгарнуў яе.

Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

І вось што атрымалася: у ping-pong серверы Linux не было такіх затрымак, як у аналагічнага вузла Windows, хоць крыніца дадзеных не адрознівалася. Аказваецца, праблема ў рэалізацыі gRPC для Windows.

Алгарытм Нейгла

Увесь гэты час я думаў, што мне не хапае сцяга gRPC. Цяпер я зразумеў, што на самой справе гэта ў gRPC не хапае сцяга Windows. Я знайшоў унутраную бібліятэку RPC, у якой быў упэўнены, што яна добра працуе для ўсіх усталяваных сцягоў. Winsock. Затым дадаў усе гэтыя сцягі ў gRPC і разгарнуў Элвіна на Windows, у выпраўленым серверы ping-pong пад Windows!

Часам больш - гэта менш. Калі памяншэнне нагрузкі прыводзіць да павелічэння затрымкі

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

Алгарытм Нейгла спрабуе паменшыць колькасць пакетаў, адпраўленых па сетцы, шляхам затрымкі перадачы паведамленняў датуль, пакуль памер пакета не перавысіць пэўную колькасць байт. Хаця гэта можа быць прыемна для сярэдняга карыстальніка, але разбуральна для сервераў рэальнага часу, паколькі АС будзе затрымліваць некаторыя паведамленні, выклікаючы затрымкі на нізкім QPS. У gRPC быў усталяваны гэты сцяг у рэалізацыі Linux для сокетаў TCP, але не для Windows. Я гэта выправіў.

Заключэнне

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

Што да эксперыменту localhost, ён, верагодна, не дакранаўся фактычнага сеткавага кода, і алгарытм Нейгла не запусціўся, таму праблемы з затрымкай зніклі, калі кліент звярнуўся да Элвіна праз localhost.

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

Крыніца: habr.com

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