Что происходит если памяти не хватает
Что проверяют
Различаете ли вы нехватку памяти на уровне ядра, на уровне рантайма Go и обычную деградацию под давлением сборщика. И понимаете ли, что из этого можно перехватить в коде, а что нельзя вообще.
Как отвечать
Разберите случаи по отдельности.
- Процесс упёрся в лимит ядра или cgroup: ядро присылает SIGKILL. Ни
defer, ниrecover, ни логирование не выполняются. Снаружи это выглядит как внезапная смерть контейнера с кодом137и записью OOM в системном журнале. - Рантайм Go не смог получить память у системы:
fatal error: out of memory. Это не паника, а throw рантайма,recoverего не ловит и отложенные функции не запускаются. - Отдельно стоит
makeс некорректной длиной: это паникаmakeslice: len out of range, вот она ловится, но это ошибка аргумента, а не исчерпание памяти. - До жёсткого падения обычно наступает деградация: куча близка к лимиту, сборщик запускается всё чаще, доля CPU на нём растёт, задержки растут. На серверах своп чаще выключен, в контейнерах его нет, так что запаса между «тесно» и «убит» почти не остаётся.
Дальше про управление. GOGC задаёт, на сколько куча может вырасти между сборками, GOMEMLIMIT это мягкий лимит: сборщик старается в него уложиться, но процесс не убивает. Разумно ставить мягкий лимит ниже лимита контейнера, чтобы получить работающий сервис вместо убитого. Диагностика: heap-профиль в pprof с разделением inuse_space и alloc_space, трассировка сборщика через GODEBUG, метрики рантайма.
Где спотыкаются
Обещают перехватить нехватку памяти через recover. Ни SIGKILL, ни fatal error рантайма так не перехватываются, отработать корректное завершение не получится.
Говорят «сборщик есть, значит утечек не бывает», путая утечку с удержанием ссылок. Незавершённые горутины, срез, взятый от большого буфера и держащий весь массив, кэш без вытеснения: память жива и законна.
Видят рост RSS и сразу называют это утечкой. Go возвращает память системе не мгновенно, поэтому смотреть надо на inuse_space в профиле, а не на цифру из монитора.