[{"data":1,"prerenderedAt":1858},["ShallowReactive",2],{"content:\u002F11-observability\u002F05-pprof-profiling-go":3},{"title":4,"description":5,"path":6,"body":7},"Profiling в Go: pprof, CPU, memory и goroutines","Метрики говорят, что сервис стал медленным. Traces показывают, какой запрос тормозит и какая dependency участвует. pprof помогает понять, где процесс тратит CPU, память, goroutines, mutex\u002Fblock wait.","\u002F11-observability\u002F05-pprof-profiling-go",{"type":8,"value":9,"toc":1844},"minimark",[10,14,23,26,54,59,66,69,92,98,101,111,126,130,141,608,611,667,671,674,694,697,703,706,720,723,743,746,807,830,833,844,848,851,867,870,876,879,905,908,930,933,936,993,997,1000,1016,1019,1037,1040,1046,1049,1083,1086,1090,1093,1096,1130,1133,1136,1163,1166,1180,1184,1189,1192,1223,1226,1234,1238,1241,1247,1250,1256,1259,1265,1268,1272,1275,1356,1359,1363,1366,1382,1385,1408,1412,1415,1433,1436,1470,1473,1477,1511,1676,1840],[11,12,4],"h1",{"id":13},"profiling-в-go-pprof-cpu-memory-и-goroutines",[15,16,17,18,22],"p",{},"Метрики говорят, что сервис стал медленным. Traces показывают, какой запрос тормозит и какая dependency участвует. ",[19,20,21],"code",{},"pprof"," помогает понять, где процесс тратит CPU, память, goroutines, mutex\u002Fblock wait.",[15,24,25],{},"Профилирование - это не только \"ускорить код\". В production оно часто нужно для:",[27,28,29,33,36,39,42,45,48,51],"ul",{},[30,31,32],"li",{},"goroutine leaks;",[30,34,35],{},"memory leaks;",[30,37,38],{},"allocation pressure и частого GC;",[30,40,41],{},"CPU hot paths;",[30,43,44],{},"lock contention;",[30,46,47],{},"busy retry loops;",[30,49,50],{},"зависших HTTP clients;",[30,52,53],{},"unbounded queues\u002Fcache.",[55,56,58],"h2",{"id":57},"production-safety","Production safety",[15,60,61,62,65],{},"Нельзя просто открыть ",[19,63,64],{},"\u002Fdebug\u002Fpprof"," наружу.",[15,67,68],{},"Правила:",[27,70,71,74,77,80,83,86,89],{},[30,72,73],{},"отдельный admin\u002Fdebug port;",[30,75,76],{},"bind на internal interface;",[30,78,79],{},"доступ только из VPN\u002Finternal network;",[30,81,82],{},"auth на reverse proxy, если есть шанс выхода наружу;",[30,84,85],{},"короткие окна CPU profiling;",[30,87,88],{},"не включать дорогое mutex\u002Fblock profiling постоянно;",[30,90,91],{},"не публиковать profile files в MR, если в них могут быть чувствительные данные.",[15,93,94,97],{},[19,95,96],{},"\u002Fdebug\u002Fpprof\u002Fcmdline",", heap\u002Fprofile dumps и runtime trace могут раскрыть аргументы запуска, env-like данные, куски строк, SQL, URL и payload fragments. Считайте profiles чувствительными артефактами: храните коротко, не прикладывайте к публичным задачам, удаляйте после разбора.",[15,99,100],{},"Production-like схема:",[102,103,109],"pre",{"className":104,"code":106,"language":107,"meta":108},[105],"language-text","public :8080\n  \u002Fhealth\n  \u002Fapi\u002F*\n\ndebug :6060\n  \u002Fdebug\u002Fpprof\u002F*\n  only localhost\u002FVPN\u002Fprivate network\n  optional basic auth or mTLS on reverse proxy\n","text","",[19,110,106],{"__ignoreMap":108},[15,112,113,114,117,118,121,122,125],{},"В Docker Compose безопасный вариант: приложение слушает ",[19,115,116],{},"0.0.0.0:6060"," внутри контейнера, а наружу порт публикуется только на loopback хоста: ",[19,119,120],{},"127.0.0.1:6060:6060",". Если процесс внутри контейнера слушает ",[19,123,124],{},"127.0.0.1:6060",", port publishing с хоста обычно до него не достучится.",[55,127,129],{"id":128},"подключение-pprof","Подключение pprof",[15,131,132,133,136,137,140],{},"Импорт ",[19,134,135],{},"_ \"net\u002Fhttp\u002Fpprof\""," автоматически регистрирует handlers на ",[19,138,139],{},"http.DefaultServeMux",". Для учебного production-like сервиса лучше сделать отдельный mux:",[102,142,147],{"className":143,"code":144,"language":145,"meta":146,"style":108},"language-go shiki shiki-themes github-dark","package debugserver\n\nimport (\n    \"log\u002Fslog\"\n    \"net\u002Fhttp\"\n    \"net\u002Fhttp\u002Fpprof\"\n)\n\nfunc Start(addr string, logger *slog.Logger) {\n    mux := http.NewServeMux()\n    mux.HandleFunc(\"\u002Fdebug\u002Fpprof\u002F\", pprof.Index)\n    mux.HandleFunc(\"\u002Fdebug\u002Fpprof\u002Fcmdline\", pprof.Cmdline)\n    mux.HandleFunc(\"\u002Fdebug\u002Fpprof\u002Fprofile\", pprof.Profile)\n    mux.HandleFunc(\"\u002Fdebug\u002Fpprof\u002Fsymbol\", pprof.Symbol)\n    mux.HandleFunc(\"\u002Fdebug\u002Fpprof\u002Ftrace\", pprof.Trace)\n    mux.Handle(\"\u002Fdebug\u002Fpprof\u002Fallocs\", pprof.Handler(\"allocs\"))\n    mux.Handle(\"\u002Fdebug\u002Fpprof\u002Fblock\", pprof.Handler(\"block\"))\n    mux.Handle(\"\u002Fdebug\u002Fpprof\u002Fgoroutine\", pprof.Handler(\"goroutine\"))\n    mux.Handle(\"\u002Fdebug\u002Fpprof\u002Fheap\", pprof.Handler(\"heap\"))\n    mux.Handle(\"\u002Fdebug\u002Fpprof\u002Fmutex\", pprof.Handler(\"mutex\"))\n    mux.Handle(\"\u002Fdebug\u002Fpprof\u002Fthreadcreate\", pprof.Handler(\"threadcreate\"))\n\n    go func() {\n        logger.Info(\"debug server started\", \"addr\", addr)\n        if err := http.ListenAndServe(addr, mux); err != nil {\n            logger.Error(\"debug server stopped\", \"error\", err)\n        }\n    }()\n}\n","go","no-run",[19,148,149,162,169,179,192,202,212,218,223,263,281,298,313,328,343,358,385,408,431,454,477,500,505,517,539,568,590,596,602],{"__ignoreMap":108},[150,151,154,158],"span",{"class":152,"line":153},"line",1,[150,155,157],{"class":156},"snl16","package",[150,159,161],{"class":160},"svObZ"," debugserver\n",[150,163,165],{"class":152,"line":164},2,[150,166,168],{"emptyLinePlaceholder":167},true,"\n",[150,170,172,175],{"class":152,"line":171},3,[150,173,174],{"class":156},"import",[150,176,178],{"class":177},"s95oV"," (\n",[150,180,182,186,189],{"class":152,"line":181},4,[150,183,185],{"class":184},"sU2Wk","    \"",[150,187,188],{"class":160},"log\u002Fslog",[150,190,191],{"class":184},"\"\n",[150,193,195,197,200],{"class":152,"line":194},5,[150,196,185],{"class":184},[150,198,199],{"class":160},"net\u002Fhttp",[150,201,191],{"class":184},[150,203,205,207,210],{"class":152,"line":204},6,[150,206,185],{"class":184},[150,208,209],{"class":160},"net\u002Fhttp\u002Fpprof",[150,211,191],{"class":184},[150,213,215],{"class":152,"line":214},7,[150,216,217],{"class":177},")\n",[150,219,221],{"class":152,"line":220},8,[150,222,168],{"emptyLinePlaceholder":167},[150,224,226,229,232,235,239,242,245,248,251,254,257,260],{"class":152,"line":225},9,[150,227,228],{"class":156},"func",[150,230,231],{"class":160}," Start",[150,233,234],{"class":177},"(",[150,236,238],{"class":237},"s9osk","addr",[150,240,241],{"class":156}," string",[150,243,244],{"class":177},", ",[150,246,247],{"class":237},"logger",[150,249,250],{"class":156}," *",[150,252,253],{"class":160},"slog",[150,255,256],{"class":177},".",[150,258,259],{"class":160},"Logger",[150,261,262],{"class":177},") {\n",[150,264,266,269,272,275,278],{"class":152,"line":265},10,[150,267,268],{"class":177},"    mux ",[150,270,271],{"class":156},":=",[150,273,274],{"class":177}," http.",[150,276,277],{"class":160},"NewServeMux",[150,279,280],{"class":177},"()\n",[150,282,284,287,290,292,295],{"class":152,"line":283},11,[150,285,286],{"class":177},"    mux.",[150,288,289],{"class":160},"HandleFunc",[150,291,234],{"class":177},[150,293,294],{"class":184},"\"\u002Fdebug\u002Fpprof\u002F\"",[150,296,297],{"class":177},", pprof.Index)\n",[150,299,301,303,305,307,310],{"class":152,"line":300},12,[150,302,286],{"class":177},[150,304,289],{"class":160},[150,306,234],{"class":177},[150,308,309],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fcmdline\"",[150,311,312],{"class":177},", pprof.Cmdline)\n",[150,314,316,318,320,322,325],{"class":152,"line":315},13,[150,317,286],{"class":177},[150,319,289],{"class":160},[150,321,234],{"class":177},[150,323,324],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fprofile\"",[150,326,327],{"class":177},", pprof.Profile)\n",[150,329,331,333,335,337,340],{"class":152,"line":330},14,[150,332,286],{"class":177},[150,334,289],{"class":160},[150,336,234],{"class":177},[150,338,339],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fsymbol\"",[150,341,342],{"class":177},", pprof.Symbol)\n",[150,344,346,348,350,352,355],{"class":152,"line":345},15,[150,347,286],{"class":177},[150,349,289],{"class":160},[150,351,234],{"class":177},[150,353,354],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Ftrace\"",[150,356,357],{"class":177},", pprof.Trace)\n",[150,359,361,363,366,368,371,374,377,379,382],{"class":152,"line":360},16,[150,362,286],{"class":177},[150,364,365],{"class":160},"Handle",[150,367,234],{"class":177},[150,369,370],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fallocs\"",[150,372,373],{"class":177},", pprof.",[150,375,376],{"class":160},"Handler",[150,378,234],{"class":177},[150,380,381],{"class":184},"\"allocs\"",[150,383,384],{"class":177},"))\n",[150,386,388,390,392,394,397,399,401,403,406],{"class":152,"line":387},17,[150,389,286],{"class":177},[150,391,365],{"class":160},[150,393,234],{"class":177},[150,395,396],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fblock\"",[150,398,373],{"class":177},[150,400,376],{"class":160},[150,402,234],{"class":177},[150,404,405],{"class":184},"\"block\"",[150,407,384],{"class":177},[150,409,411,413,415,417,420,422,424,426,429],{"class":152,"line":410},18,[150,412,286],{"class":177},[150,414,365],{"class":160},[150,416,234],{"class":177},[150,418,419],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fgoroutine\"",[150,421,373],{"class":177},[150,423,376],{"class":160},[150,425,234],{"class":177},[150,427,428],{"class":184},"\"goroutine\"",[150,430,384],{"class":177},[150,432,434,436,438,440,443,445,447,449,452],{"class":152,"line":433},19,[150,435,286],{"class":177},[150,437,365],{"class":160},[150,439,234],{"class":177},[150,441,442],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fheap\"",[150,444,373],{"class":177},[150,446,376],{"class":160},[150,448,234],{"class":177},[150,450,451],{"class":184},"\"heap\"",[150,453,384],{"class":177},[150,455,457,459,461,463,466,468,470,472,475],{"class":152,"line":456},20,[150,458,286],{"class":177},[150,460,365],{"class":160},[150,462,234],{"class":177},[150,464,465],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fmutex\"",[150,467,373],{"class":177},[150,469,376],{"class":160},[150,471,234],{"class":177},[150,473,474],{"class":184},"\"mutex\"",[150,476,384],{"class":177},[150,478,480,482,484,486,489,491,493,495,498],{"class":152,"line":479},21,[150,481,286],{"class":177},[150,483,365],{"class":160},[150,485,234],{"class":177},[150,487,488],{"class":184},"\"\u002Fdebug\u002Fpprof\u002Fthreadcreate\"",[150,490,373],{"class":177},[150,492,376],{"class":160},[150,494,234],{"class":177},[150,496,497],{"class":184},"\"threadcreate\"",[150,499,384],{"class":177},[150,501,503],{"class":152,"line":502},22,[150,504,168],{"emptyLinePlaceholder":167},[150,506,508,511,514],{"class":152,"line":507},23,[150,509,510],{"class":156},"    go",[150,512,513],{"class":156}," func",[150,515,516],{"class":177},"() {\n",[150,518,520,523,526,528,531,533,536],{"class":152,"line":519},24,[150,521,522],{"class":177},"        logger.",[150,524,525],{"class":160},"Info",[150,527,234],{"class":177},[150,529,530],{"class":184},"\"debug server started\"",[150,532,244],{"class":177},[150,534,535],{"class":184},"\"addr\"",[150,537,538],{"class":177},", addr)\n",[150,540,542,545,548,550,552,555,558,561,565],{"class":152,"line":541},25,[150,543,544],{"class":156},"        if",[150,546,547],{"class":177}," err ",[150,549,271],{"class":156},[150,551,274],{"class":177},[150,553,554],{"class":160},"ListenAndServe",[150,556,557],{"class":177},"(addr, mux); err ",[150,559,560],{"class":156},"!=",[150,562,564],{"class":563},"sDLfK"," nil",[150,566,567],{"class":177}," {\n",[150,569,571,574,577,579,582,584,587],{"class":152,"line":570},26,[150,572,573],{"class":177},"            logger.",[150,575,576],{"class":160},"Error",[150,578,234],{"class":177},[150,580,581],{"class":184},"\"debug server stopped\"",[150,583,244],{"class":177},[150,585,586],{"class":184},"\"error\"",[150,588,589],{"class":177},", err)\n",[150,591,593],{"class":152,"line":592},27,[150,594,595],{"class":177},"        }\n",[150,597,599],{"class":152,"line":598},28,[150,600,601],{"class":177},"    }()\n",[150,603,605],{"class":152,"line":604},29,[150,606,607],{"class":177},"}\n",[15,609,610],{},"В compose\u002Fdebug-профиле:",[102,612,616],{"className":613,"code":614,"language":615,"meta":108,"style":108},"language-yaml shiki shiki-themes github-dark","services:\n  ratedesk-api:\n    environment:\n      DEBUG_ADDR: \"0.0.0.0:6060\"\n    ports:\n      - \"127.0.0.1:6060:6060\"\n","yaml",[19,617,618,627,634,641,652,659],{"__ignoreMap":108},[150,619,620,624],{"class":152,"line":153},[150,621,623],{"class":622},"s4JwU","services",[150,625,626],{"class":177},":\n",[150,628,629,632],{"class":152,"line":164},[150,630,631],{"class":622},"  ratedesk-api",[150,633,626],{"class":177},[150,635,636,639],{"class":152,"line":171},[150,637,638],{"class":622},"    environment",[150,640,626],{"class":177},[150,642,643,646,649],{"class":152,"line":181},[150,644,645],{"class":622},"      DEBUG_ADDR",[150,647,648],{"class":177},": ",[150,650,651],{"class":184},"\"0.0.0.0:6060\"\n",[150,653,654,657],{"class":152,"line":194},[150,655,656],{"class":622},"    ports",[150,658,626],{"class":177},[150,660,661,664],{"class":152,"line":204},[150,662,663],{"class":177},"      - ",[150,665,666],{"class":184},"\"127.0.0.1:6060:6060\"\n",[55,668,670],{"id":669},"cpu-profile","CPU profile",[15,672,673],{},"CPU profile отвечает: где процесс реально проводит CPU time под нагрузкой.",[102,675,679],{"className":676,"code":677,"language":678,"meta":108,"style":108},"language-bash shiki shiki-themes github-dark","go tool pprof \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fprofile?seconds=30\"\n","bash",[19,680,681],{"__ignoreMap":108},[150,682,683,685,688,691],{"class":152,"line":153},[150,684,145],{"class":160},[150,686,687],{"class":184}," tool",[150,689,690],{"class":184}," pprof",[150,692,693],{"class":184}," \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fprofile?seconds=30\"\n",[15,695,696],{},"Внутри:",[102,698,701],{"className":699,"code":700,"language":107,"meta":108},[105],"top\ntop -cum\nlist SomeFunction\nweb\n",[19,702,700],{"__ignoreMap":108},[15,704,705],{},"Если CPU profile пустой или странный, проверьте, что:",[27,707,708,711,714,717],{},[30,709,710],{},"была нагрузка во время снятия;",[30,712,713],{},"код реально потреблял CPU, а не ждал IO;",[30,715,716],{},"окно профилирования достаточно длинное;",[30,718,719],{},"вы смотрите правильный процесс.",[15,721,722],{},"Частые находки:",[27,724,725,728,731,734,737,740],{},[30,726,727],{},"JSON marshal\u002Funmarshal на hot path;",[30,729,730],{},"regex в цикле;",[30,732,733],{},"compression\u002Fencryption;",[30,735,736],{},"busy loop retry без sleep\u002Fbackoff;",[30,738,739],{},"лишнее копирование больших структур;",[30,741,742],{},"чрезмерная аллокация, которая проявляется как CPU через GC.",[15,744,745],{},"Как читать:",[747,748,749,763],"table",{},[750,751,752],"thead",{},[753,754,755,760],"tr",{},[756,757,759],"th",{"align":758},"left","Колонка",[756,761,762],{"align":758},"Смысл",[764,765,766,777,787,797],"tbody",{},[753,767,768,774],{},[769,770,771],"td",{"align":758},[19,772,773],{},"flat",[769,775,776],{"align":758},"Сколько CPU сгорело прямо в функции.",[753,778,779,784],{},[769,780,781],{"align":758},[19,782,783],{},"flat%",[769,785,786],{"align":758},"Доля от всего profile.",[753,788,789,794],{},[769,790,791],{"align":758},[19,792,793],{},"cum",[769,795,796],{"align":758},"Функция + все, что она вызвала.",[753,798,799,804],{},[769,800,801],{"align":758},[19,802,803],{},"cum%",[769,805,806],{"align":758},"Доля с учетом детей.",[15,808,809,810,812,813,815,816,818,819,822,823,822,826,829],{},"Если ",[19,811,773],{}," высокий - функция сама hot. Если ",[19,814,793],{}," высокий, а ",[19,817,773],{}," маленький - функция ведет в дорогие вызовы ниже. Начинайте с ",[19,820,821],{},"top",", потом ",[19,824,825],{},"top -cum",[19,827,828],{},"list \u003Cfunc>"," или web graph.",[15,831,832],{},"CPU-bound vs IO-bound:",[27,834,835,838,841],{},[30,836,837],{},"CPU-bound: p95 растет, CPU высокий, profiles показывают hot functions;",[30,839,840],{},"IO-bound: CPU нормальный, traces показывают DB\u002Fprovider\u002FRedis latency;",[30,842,843],{},"lock-bound: CPU может быть не высоким, но mutex\u002Fblock profiles показывают contention.",[55,845,847],{"id":846},"heap-profile","Heap profile",[15,849,850],{},"Heap profile отвечает: что сейчас удерживает память.",[102,852,854],{"className":676,"code":853,"language":678,"meta":108,"style":108},"go tool pprof \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fheap\"\n",[19,855,856],{"__ignoreMap":108},[150,857,858,860,862,864],{"class":152,"line":153},[150,859,145],{"class":160},[150,861,687],{"class":184},[150,863,690],{"class":184},[150,865,866],{"class":184}," \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fheap\"\n",[15,868,869],{},"Полезные режимы:",[102,871,874],{"className":872,"code":873,"language":107,"meta":108},[105],"top\ntop -cum\nsample_index=inuse_space\nsample_index=inuse_objects\nsample_index=alloc_space\n",[19,875,873],{"__ignoreMap":108},[15,877,878],{},"Разница:",[27,880,881,887,893,899],{},[30,882,883,886],{},[19,884,885],{},"inuse_space",": что живет сейчас;",[30,888,889,892],{},[19,890,891],{},"alloc_space",": сколько было выделено всего, полезно для allocation churn;",[30,894,895,898],{},[19,896,897],{},"inuse_objects",": много мелких живых объектов;",[30,900,901,904],{},[19,902,903],{},"alloc_objects",": много мелких аллокаций.",[15,906,907],{},"Для поиска утечки полезно сравнивать профили:",[102,909,911],{"className":676,"code":910,"language":678,"meta":108,"style":108},"go tool pprof -base heap_1.pb heap_10.pb\n",[19,912,913],{"__ignoreMap":108},[150,914,915,917,919,921,924,927],{"class":152,"line":153},[150,916,145],{"class":160},[150,918,687],{"class":184},[150,920,690],{"class":184},[150,922,923],{"class":563}," -base",[150,925,926],{"class":184}," heap_1.pb",[150,928,929],{"class":184}," heap_10.pb\n",[15,931,932],{},"Если heap растет, а RSS растет еще сильнее, смотрите также cgo\u002Fnative memory, mmap, buffers и настройки runtime.",[15,934,935],{},"Heap leak vs allocation churn:",[747,937,938,951],{},[750,939,940],{},[753,941,942,945,948],{},[756,943,944],{"align":758},"Симптом",[756,946,947],{"align":758},"Что это может быть",[756,949,950],{"align":758},"Как проверять",[764,952,953,966,982],{},[753,954,955,960,963],{},[769,956,957,959],{"align":758},[19,958,885],{}," растет и не падает после GC",[769,961,962],{"align":758},"Удержание ссылок, unbounded cache, goroutine leak.",[769,964,965],{"align":758},"Сравнить heap snapshots во времени.",[753,967,968,976,979],{},[769,969,970,972,973,975],{"align":758},[19,971,891],{}," огромный, ",[19,974,885],{}," нормальный",[769,977,978],{"align":758},"Много временных аллокаций.",[769,980,981],{"align":758},"Смотреть hot allocation sites и latency\u002FGC.",[753,983,984,987,990],{},[769,985,986],{"align":758},"RSS растет, heap не растет",[769,988,989],{"align":758},"mmap, cgo, fragmentation, runtime arenas.",[769,991,992],{"align":758},"runtime metrics, process metrics, контейнерные metrics.",[55,994,996],{"id":995},"goroutine-profile","Goroutine profile",[15,998,999],{},"Goroutine profile отвечает: какие goroutines живы и где они ждут.",[102,1001,1003],{"className":676,"code":1002,"language":678,"meta":108,"style":108},"go tool pprof \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine\"\n",[19,1004,1005],{"__ignoreMap":108},[150,1006,1007,1009,1011,1013],{"class":152,"line":153},[150,1008,145],{"class":160},[150,1010,687],{"class":184},[150,1012,690],{"class":184},[150,1014,1015],{"class":184}," \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine\"\n",[15,1017,1018],{},"Текстовый dump:",[102,1020,1022],{"className":676,"code":1021,"language":678,"meta":108,"style":108},"go tool pprof -raw \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine\"\n",[19,1023,1024],{"__ignoreMap":108},[150,1025,1026,1028,1030,1032,1035],{"class":152,"line":153},[150,1027,145],{"class":160},[150,1029,687],{"class":184},[150,1031,690],{"class":184},[150,1033,1034],{"class":563}," -raw",[150,1036,1015],{"class":184},[15,1038,1039],{},"или в браузере:",[102,1041,1044],{"className":1042,"code":1043,"language":107,"meta":108},[105],"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine?debug=2\n",[19,1045,1043],{"__ignoreMap":108},[15,1047,1048],{},"Типичные причины утечек:",[27,1050,1051,1058,1068,1071,1074,1077,1080],{},[30,1052,1053,1054,1057],{},"забытый ",[19,1055,1056],{},"cancel()"," у context;",[30,1059,1060,1063,1064,1067],{},[19,1061,1062],{},"time.Ticker"," без ",[19,1065,1066],{},"Stop()",";",[30,1069,1070],{},"channel send\u002Freceive без второй стороны;",[30,1072,1073],{},"worker без stop condition;",[30,1075,1076],{},"HTTP response body не закрыт;",[30,1078,1079],{},"retry loop без backoff и лимита;",[30,1081,1082],{},"consumer goroutine, которая висит после shutdown.",[15,1084,1085],{},"Сравнивайте количество goroutines до нагрузки, во время и после. Если после нагрузки число не возвращается, это красный флаг.",[55,1087,1089],{"id":1088},"mutex-и-block-profiles","Mutex и block profiles",[15,1091,1092],{},"Mutex profile показывает contention на mutex. Block profile показывает блокировки goroutines на channel, select, mutex и других primitives.",[15,1094,1095],{},"Их нужно включать явно:",[102,1097,1099],{"className":143,"code":1098,"language":145,"meta":146,"style":108},"runtime.SetMutexProfileFraction(5)\nruntime.SetBlockProfileRate(10_000)\n",[19,1100,1101,1116],{"__ignoreMap":108},[150,1102,1103,1106,1109,1111,1114],{"class":152,"line":153},[150,1104,1105],{"class":177},"runtime.",[150,1107,1108],{"class":160},"SetMutexProfileFraction",[150,1110,234],{"class":177},[150,1112,1113],{"class":563},"5",[150,1115,217],{"class":177},[150,1117,1118,1120,1123,1125,1128],{"class":152,"line":164},[150,1119,1105],{"class":177},[150,1121,1122],{"class":160},"SetBlockProfileRate",[150,1124,234],{"class":177},[150,1126,1127],{"class":563},"10_000",[150,1129,217],{"class":177},[15,1131,1132],{},"Не держите высокие значения постоянно в prod без понимания overhead.",[15,1134,1135],{},"Команды:",[102,1137,1139],{"className":676,"code":1138,"language":678,"meta":108,"style":108},"go tool pprof \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fmutex\"\ngo tool pprof \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fblock\"\n",[19,1140,1141,1152],{"__ignoreMap":108},[150,1142,1143,1145,1147,1149],{"class":152,"line":153},[150,1144,145],{"class":160},[150,1146,687],{"class":184},[150,1148,690],{"class":184},[150,1150,1151],{"class":184}," \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fmutex\"\n",[150,1153,1154,1156,1158,1160],{"class":152,"line":164},[150,1155,145],{"class":160},[150,1157,687],{"class":184},[150,1159,690],{"class":184},[150,1161,1162],{"class":184}," \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fblock\"\n",[15,1164,1165],{},"Когда смотреть:",[27,1167,1168,1171,1174,1177],{},[30,1169,1170],{},"latency выросла без роста CPU;",[30,1172,1173],{},"pprof CPU не показывает hot path;",[30,1175,1176],{},"есть подозрение на lock contention;",[30,1178,1179],{},"goroutine profile показывает много blocked states.",[55,1181,1183],{"id":1182},"runtimetrace","runtime\u002Ftrace",[15,1185,1186,1188],{},[19,1187,1183],{}," дает более детальную картину runtime: scheduler, goroutines, syscalls, GC, network blocking.",[15,1190,1191],{},"Через pprof endpoint:",[102,1193,1195],{"className":676,"code":1194,"language":678,"meta":108,"style":108},"curl -o trace.out \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Ftrace?seconds=10\"\ngo tool trace trace.out\n",[19,1196,1197,1211],{"__ignoreMap":108},[150,1198,1199,1202,1205,1208],{"class":152,"line":153},[150,1200,1201],{"class":160},"curl",[150,1203,1204],{"class":563}," -o",[150,1206,1207],{"class":184}," trace.out",[150,1209,1210],{"class":184}," \"http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Ftrace?seconds=10\"\n",[150,1212,1213,1215,1217,1220],{"class":152,"line":164},[150,1214,145],{"class":160},[150,1216,687],{"class":184},[150,1218,1219],{"class":184}," trace",[150,1221,1222],{"class":184}," trace.out\n",[15,1224,1225],{},"Используйте trace, когда CPU\u002Fheap\u002Fgoroutine profiles не объясняют latency: например, есть scheduler delay, syscall wait, много short-lived goroutines или странная конкуренция.",[15,1227,1228,1230,1231,1233],{},[19,1229,21],{}," отвечает \"где тратилось CPU\u002Fmemory\u002Flock time\". ",[19,1232,1183],{}," отвечает \"как события разложились во времени\": кто кого заблокировал, когда был GC, почему goroutine ждала network или channel.",[55,1235,1237],{"id":1236},"связь-с-observability","Связь с observability",[15,1239,1240],{},"Профиль не заменяет metrics\u002Flogs\u002Ftraces. Он подключается в конце цепочки:",[102,1242,1245],{"className":1243,"code":1244,"language":107,"meta":108},[105],"alert: p95 latency high\n  -> dashboard: route \u002Fconvert degraded\n  -> traces: slow span is usecase.Convert\n  -> metrics: DB ok, Redis ok, CPU high\n  -> pprof CPU: hot path in decimal formatting\n  -> fix or mitigation\n",[19,1246,1244],{"__ignoreMap":108},[15,1248,1249],{},"Или:",[102,1251,1254],{"className":1252,"code":1253,"language":107,"meta":108},[105],"alert: memory high\n  -> metrics: goroutines grow with traffic\n  -> traces: requests timeout\n  -> goroutine profile: workers blocked on channel send\n  -> fix cancellation\u002Fbackpressure\n",[19,1255,1253],{"__ignoreMap":108},[15,1257,1258],{},"Связь с runtime metrics:",[102,1260,1263],{"className":1261,"code":1262,"language":107,"meta":108},[105],"\u002Fgc\u002Fheap\u002Fallocs:bytes\n\u002Fgc\u002Fheap\u002Flive:bytes\n\u002Fsched\u002Fgoroutines:goroutines\n\u002Fsync\u002Fmutex\u002Fwait\u002Ftotal:seconds\n",[19,1264,1262],{"__ignoreMap":108},[15,1266,1267],{},"Эти значения удобно отдавать в Prometheus через Go runtime\u002Fprocess collectors и использовать как ранний сигнал: \"пора снять profile\". Но root cause обычно виден уже в pprof\u002Ftrace.",[55,1269,1271],{"id":1270},"fault-examples-для-ratedesk","Fault examples для RateDesk",[15,1273,1274],{},"Учебные дефекты должны быть управляемыми и выключаться:",[747,1276,1277,1289],{},[750,1278,1279],{},[753,1280,1281,1284,1286],{},[756,1282,1283],{"align":758},"Flag",[756,1285,944],{"align":758},[756,1287,1288],{"align":758},"Что увидеть",[764,1290,1291,1304,1317,1330,1343],{},[753,1292,1293,1298,1301],{},[769,1294,1295],{"align":758},[19,1296,1297],{},"FAULT_CPU_HEAVY=true",[769,1299,1300],{"align":758},"CPU и p95 растут, dependencies здоровы.",[769,1302,1303],{"align":758},"CPU profile показывает hot conversion path.",[753,1305,1306,1311,1314],{},[769,1307,1308],{"align":758},[19,1309,1310],{},"FAULT_GOROUTINE_LEAK=true",[769,1312,1313],{"align":758},"Goroutines растут после каждого request.",[769,1315,1316],{"align":758},"Goroutine profile показывает зависшие send\u002Freceive.",[753,1318,1319,1324,1327],{},[769,1320,1321],{"align":758},[19,1322,1323],{},"FAULT_REDIS_MISS_STORM=true",[769,1325,1326],{"align":758},"Hit ratio падает, DB QPS растет.",[769,1328,1329],{"align":758},"Metrics\u002Ftraces показывают cache miss -> DB.",[753,1331,1332,1337,1340],{},[769,1333,1334],{"align":758},[19,1335,1336],{},"FAULT_BUSY_RETRY=true",[769,1338,1339],{"align":758},"CPU\u002Ftraffic к dependency растут.",[769,1341,1342],{"align":758},"Logs retry spam, CPU profile, provider metrics.",[753,1344,1345,1350,1353],{},[769,1346,1347],{"align":758},[19,1348,1349],{},"FAULT_UNCLOSED_BODY=true",[769,1351,1352],{"align":758},"Connections\u002Fgoroutines висят.",[769,1354,1355],{"align":758},"Goroutine profile и HTTP client metrics.",[15,1357,1358],{},"Такие flags должны быть только для local\u002Fstaging drills, не публичным API.",[55,1360,1362],{"id":1361},"load-smoke","Load smoke",[15,1364,1365],{},"Для учебного проекта не нужно сразу искать предел сервиса. Нужен controlled smoke, который двигает сигналы:",[102,1367,1369],{"className":676,"code":1368,"language":678,"meta":108,"style":108},"k6 run scripts\u002Fk6\u002Fconvert-smoke.js\n",[19,1370,1371],{"__ignoreMap":108},[150,1372,1373,1376,1379],{"class":152,"line":153},[150,1374,1375],{"class":160},"k6",[150,1377,1378],{"class":184}," run",[150,1380,1381],{"class":184}," scripts\u002Fk6\u002Fconvert-smoke.js\n",[15,1383,1384],{},"Цель:",[27,1386,1387,1390,1393,1399,1402,1405],{},[30,1388,1389],{},"p95 latency меняется на dashboard;",[30,1391,1392],{},"request counter растет;",[30,1394,1395,1396,1067],{},"logs появляются с ",[19,1397,1398],{},"request_id",[30,1400,1401],{},"traces создаются;",[30,1403,1404],{},"при искусственном сбое загорается alert;",[30,1406,1407],{},"pprof можно снять под нагрузкой.",[55,1409,1411],{"id":1410},"практика","Практика",[15,1413,1414],{},"Добавьте в RateDesk:",[1416,1417,1418,1421,1424,1427,1430],"ol",{},[30,1419,1420],{},"Закрытый debug server на admin port.",[30,1422,1423],{},"CPU\u002Fheap\u002Fgoroutine profile smoke в README.",[30,1425,1426],{},"Скрипт нагрузки или сценарий k6.",[30,1428,1429],{},"Один искусственный сценарий: CPU-heavy converter, Redis miss storm или provider timeout.",[30,1431,1432],{},"Короткий отчет в MR: какой профиль снимали, что увидели, что не оптимизировали и почему.",[15,1434,1435],{},"Acceptance:",[27,1437,1438,1441,1444,1447,1450,1453,1461],{},[30,1439,1440],{},"pprof endpoint не доступен публично;",[30,1442,1443],{},"CPU profile снимается под нагрузкой;",[30,1445,1446],{},"heap\u002Fgoroutine profiles сравниваются до\u002Fпосле smoke;",[30,1448,1449],{},"README содержит команды;",[30,1451,1452],{},"в runbook high-latency есть шаг \"когда и как снимать pprof\";",[30,1454,1455,1456,1458,1459,1067],{},"студент объясняет, чем ",[19,1457,885],{}," отличается от ",[19,1460,891],{},[30,1462,1463,1464,1466,1467,1469],{},"MR содержит короткий разбор ",[19,1465,821],{},"\u002F",[19,1468,825],{},": что было hot и какой вывод сделали.",[1471,1472],"hr",{},[55,1474,1476],{"id":1475},"интерактивная-практика","Интерактивная практика",[1478,1479,1483,1486,1506],"quiz",{"answer":1480,"id":1481,"xp":1482},"2","obs-pprof-q1","10",[15,1484,1485],{},"Как безопаснее включать pprof в учебном backend?",[1487,1488,1489],"template",{"v-slot:options":108},[27,1490,1491,1497,1500,1503],{},[30,1492,1493,1494,1496],{},"Открыть ",[19,1495,64],{}," в публичный интернет",[30,1498,1499],{},"Поднять отдельный debug\u002Fadmin listener и закрыть его сетью",[30,1501,1502],{},"Логировать все profiles в stdout",[30,1504,1505],{},"Включать CPU profile на каждый production request",[1487,1507,1508],{"v-slot:explanation":108},[15,1509,1510],{},"pprof может раскрывать internals и создавать нагрузку. Его обычно держат на закрытом debug contour.",[1512,1513,1517,1520,1671],"predict",{"answer":1514,"id":1515,"xp":1516},"cpu\\ngoroutine","obs-pprof-p1","15",[15,1518,1519],{},"Что выведет программа?",[1487,1521,1522],{"v-slot:code":108},[102,1523,1525],{"className":143,"code":1524,"language":145,"meta":108,"style":108},"package main\n\nimport \"fmt\"\n\nfunc ProfileFor(symptom string) string {\n    if symptom == \"hot-loop\" {\n        return \"cpu\"\n    }\n    return \"goroutine\"\n}\n\nfunc main() {\n    fmt.Println(ProfileFor(\"hot-loop\"))\n    fmt.Println(ProfileFor(\"blocked-workers\"))\n}\n",[19,1526,1527,1534,1538,1550,1554,1576,1592,1600,1605,1613,1617,1621,1630,1650,1667],{"__ignoreMap":108},[150,1528,1529,1531],{"class":152,"line":153},[150,1530,157],{"class":156},[150,1532,1533],{"class":160}," main\n",[150,1535,1536],{"class":152,"line":164},[150,1537,168],{"emptyLinePlaceholder":167},[150,1539,1540,1542,1545,1548],{"class":152,"line":171},[150,1541,174],{"class":156},[150,1543,1544],{"class":184}," \"",[150,1546,1547],{"class":160},"fmt",[150,1549,191],{"class":184},[150,1551,1552],{"class":152,"line":181},[150,1553,168],{"emptyLinePlaceholder":167},[150,1555,1556,1558,1561,1563,1566,1568,1571,1574],{"class":152,"line":194},[150,1557,228],{"class":156},[150,1559,1560],{"class":160}," ProfileFor",[150,1562,234],{"class":177},[150,1564,1565],{"class":237},"symptom",[150,1567,241],{"class":156},[150,1569,1570],{"class":177},") ",[150,1572,1573],{"class":156},"string",[150,1575,567],{"class":177},[150,1577,1578,1581,1584,1587,1590],{"class":152,"line":204},[150,1579,1580],{"class":156},"    if",[150,1582,1583],{"class":177}," symptom ",[150,1585,1586],{"class":156},"==",[150,1588,1589],{"class":184}," \"hot-loop\"",[150,1591,567],{"class":177},[150,1593,1594,1597],{"class":152,"line":214},[150,1595,1596],{"class":156},"        return",[150,1598,1599],{"class":184}," \"cpu\"\n",[150,1601,1602],{"class":152,"line":220},[150,1603,1604],{"class":177},"    }\n",[150,1606,1607,1610],{"class":152,"line":225},[150,1608,1609],{"class":156},"    return",[150,1611,1612],{"class":184}," \"goroutine\"\n",[150,1614,1615],{"class":152,"line":265},[150,1616,607],{"class":177},[150,1618,1619],{"class":152,"line":283},[150,1620,168],{"emptyLinePlaceholder":167},[150,1622,1623,1625,1628],{"class":152,"line":300},[150,1624,228],{"class":156},[150,1626,1627],{"class":160}," main",[150,1629,516],{"class":177},[150,1631,1632,1635,1638,1640,1643,1645,1648],{"class":152,"line":315},[150,1633,1634],{"class":177},"    fmt.",[150,1636,1637],{"class":160},"Println",[150,1639,234],{"class":177},[150,1641,1642],{"class":160},"ProfileFor",[150,1644,234],{"class":177},[150,1646,1647],{"class":184},"\"hot-loop\"",[150,1649,384],{"class":177},[150,1651,1652,1654,1656,1658,1660,1662,1665],{"class":152,"line":330},[150,1653,1634],{"class":177},[150,1655,1637],{"class":160},[150,1657,234],{"class":177},[150,1659,1642],{"class":160},[150,1661,234],{"class":177},[150,1663,1664],{"class":184},"\"blocked-workers\"",[150,1666,384],{"class":177},[150,1668,1669],{"class":152,"line":345},[150,1670,607],{"class":177},[1487,1672,1673],{"v-slot:hint":108},[15,1674,1675],{},"CPU profile ищет hot path, goroutine profile показывает блокировки\u002Fутечки goroutines.",[1677,1678,1682,1700,1827],"code-task",{"expected":1679,"id":1680,"xp":1681},"cpu\\nheap\\ngoroutine","obs-pprof-ct1","20",[15,1683,1684,1685,1688,1689,1692,1693,1696,1697,256],{},"Реализуй ",[19,1686,1687],{},"ProfileKind",": высокий CPU - ",[19,1690,1691],{},"cpu",", рост памяти - ",[19,1694,1695],{},"heap",", рост зависших worker'ов - ",[19,1698,1699],{},"goroutine",[1487,1701,1702],{"v-slot:template":108},[102,1703,1705],{"className":143,"code":1704,"language":145,"meta":108,"style":108},"package main\n\nimport \"fmt\"\n\nfunc ProfileKind(symptom string) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(ProfileKind(\"high-cpu\"))\n    fmt.Println(ProfileKind(\"memory-growth\"))\n    fmt.Println(ProfileKind(\"blocked-workers\"))\n}\n",[19,1706,1707,1713,1717,1727,1731,1750,1757,1761,1765,1773,1790,1807,1823],{"__ignoreMap":108},[150,1708,1709,1711],{"class":152,"line":153},[150,1710,157],{"class":156},[150,1712,1533],{"class":160},[150,1714,1715],{"class":152,"line":164},[150,1716,168],{"emptyLinePlaceholder":167},[150,1718,1719,1721,1723,1725],{"class":152,"line":171},[150,1720,174],{"class":156},[150,1722,1544],{"class":184},[150,1724,1547],{"class":160},[150,1726,191],{"class":184},[150,1728,1729],{"class":152,"line":181},[150,1730,168],{"emptyLinePlaceholder":167},[150,1732,1733,1735,1738,1740,1742,1744,1746,1748],{"class":152,"line":194},[150,1734,228],{"class":156},[150,1736,1737],{"class":160}," ProfileKind",[150,1739,234],{"class":177},[150,1741,1565],{"class":237},[150,1743,241],{"class":156},[150,1745,1570],{"class":177},[150,1747,1573],{"class":156},[150,1749,567],{"class":177},[150,1751,1752,1754],{"class":152,"line":204},[150,1753,1609],{"class":156},[150,1755,1756],{"class":184}," \"todo\"\n",[150,1758,1759],{"class":152,"line":214},[150,1760,607],{"class":177},[150,1762,1763],{"class":152,"line":220},[150,1764,168],{"emptyLinePlaceholder":167},[150,1766,1767,1769,1771],{"class":152,"line":225},[150,1768,228],{"class":156},[150,1770,1627],{"class":160},[150,1772,516],{"class":177},[150,1774,1775,1777,1779,1781,1783,1785,1788],{"class":152,"line":265},[150,1776,1634],{"class":177},[150,1778,1637],{"class":160},[150,1780,234],{"class":177},[150,1782,1687],{"class":160},[150,1784,234],{"class":177},[150,1786,1787],{"class":184},"\"high-cpu\"",[150,1789,384],{"class":177},[150,1791,1792,1794,1796,1798,1800,1802,1805],{"class":152,"line":283},[150,1793,1634],{"class":177},[150,1795,1637],{"class":160},[150,1797,234],{"class":177},[150,1799,1687],{"class":160},[150,1801,234],{"class":177},[150,1803,1804],{"class":184},"\"memory-growth\"",[150,1806,384],{"class":177},[150,1808,1809,1811,1813,1815,1817,1819,1821],{"class":152,"line":300},[150,1810,1634],{"class":177},[150,1812,1637],{"class":160},[150,1814,234],{"class":177},[150,1816,1687],{"class":160},[150,1818,234],{"class":177},[150,1820,1664],{"class":184},[150,1822,384],{"class":177},[150,1824,1825],{"class":152,"line":315},[150,1826,607],{"class":177},[1487,1828,1829],{"v-slot:hints":108},[27,1830,1831,1834,1837],{},[30,1832,1833],{},"CPU profile отвечает, где тратится процессорное время.",[30,1835,1836],{},"Heap profile помогает искать удерживаемую память.",[30,1838,1839],{},"Goroutine profile полезен при leaks и stuck workers.",[1841,1842,1843],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":108,"searchDepth":164,"depth":164,"links":1845},[1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857],{"id":57,"depth":164,"text":58},{"id":128,"depth":164,"text":129},{"id":669,"depth":164,"text":670},{"id":846,"depth":164,"text":847},{"id":995,"depth":164,"text":996},{"id":1088,"depth":164,"text":1089},{"id":1182,"depth":164,"text":1183},{"id":1236,"depth":164,"text":1237},{"id":1270,"depth":164,"text":1271},{"id":1361,"depth":164,"text":1362},{"id":1410,"depth":164,"text":1411},{"id":1475,"depth":164,"text":1476},1781022066151]