четверг, 2 ноября 2017 г.

Golang runtime

       Не так давно в ходе выкладки в прод memory/cpu лимитов для компонентов наткнулся на интересную особенность поведения Golang runtime. При нехватки CPU ресурсов у приложения написанного на Golang может резко возрастать потребление памяти. Связано это с тем что при увеличении  нагрузки доля CPU выделяемая на сборку мусора уменьшается и в какой-то момент времени мусор начинает генерироваться быстрее чем идет его сборка. 
         В таком режиме go приложение начинает довольно быстро расходовать память и быстро  пристреливается OOM killer-ом. Потом оно перезапускается, но уже с пустыми кэшами. Так как кэши пустые, приложение начинает расходовать еще больше CPU и вероятность того что оно сново уйдет в крутое пике - крайне велика. 
          Первоначальное предположение было - что Go runtime не видит memory limit-ов установленных cgroup. По после чтения исходников я выяснил что go runtime и не пытается увидеть эти лимиты. Действительно, у приложения запущенного без root привилегий не так много возможностей увидеть с какими ограничениями по памяти оно запущено. И даже если у тебя есть root права - эти ограничения будут зависеть от очень многих факторов, и эти факторы будут различаться на каждой поддерживаемой платформе. В общем go runtime просто аллоцирует новые куски памяти до тех пор пока его не прикончит OOM killer или до тех пор пока  операционная система не вернет ему ошибку при очередном вызове mmap(2). В этом случае go runtime падает с паникой.