Рост RSS не доказывает утечку. Рантайм берёт память у ОС и возвращает постепенно. Важна нижняя точка после сборки и то, что остаётся живым.

Пила после сборки

Здоровый сервис показывает пилу: память растёт до сборки и падает примерно к одному уровню. Утечка поднимает нижние точки со временем.

здоровый сервис рост кучи GC прежний уровень утечка рост кучи GC выше прошлого

О сборке говорит пила, об утечке - рост нижних точек после GC. RSS при этом может оставаться высоким.

Первые графики:

  • go_goroutines - не растёт ли число горутин;
  • go_memstats_heap_inuse_bytes - сколько живёт сейчас;
  • go_memstats_next_gc_bytes - цель следующей сборки;
  • process_resident_memory_bytes - весь процесс, включая вне Go-память.

Если горутины растут, начинаем с pprof/goroutine. Если куча растёт - с pprof/heap и разреза inuse_space.

GOGC и GOMEMLIMIT

GOGC задаёт отношение роста кучи, а не лимит. GOMEMLIMIT - мягкий предел, при приближении к которому GC работает чаще.

В контейнере GOMEMLIMIT обычно ставят на 80-90% лимита, оставляя место для cgo, стеков и памяти вне Go. Слишком низкий предел вызывает постоянные сборки и высокий CPU.

GOGC меняют после профиля: увеличение снижает число сборок ценой памяти, уменьшение - наоборот.

Утечка или задержка возврата

Память после GC может не вернуться ОС сразу. Это выглядит как высокая полка, но она не растёт бесконечно. Утечка растёт монотонно и меняет нижнюю точку.

Различают:

  • живые объекты;
  • стеки горутин;
  • память cgo;
  • ещё не возвращённые страницы ОС.

Поэтому сравнивают heap_inuse, stack_inuse, sys и resident.

Коротко

  • Утечка видна по росту нижней точки после GC, не по одному графику RSS.
  • Сначала горутины, затем heap, затем память вне Go.
  • GOMEMLIMIT мягкий, GOGC отношение, а не предел.
  • Высокая полка после сборки не всегда утечка.