
Шёл за утечкой памяти, нашёл утечку диска: SXSSFWorkbook без dispose() в Apache POI
igoresha_s 1 час назад Шёл за утечкой памяти, нашёл утечку диска: SXSSFWorkbook без dispose() в Apache POI Средний 9 мин 3.2K Java * Apache * Kubernetes * Тестирование IT-систем * DevOps * Кейс Меня зовут Игорь Симаков,...
Вот важная новость с фронта ИИ: igoresha_s 1 час назад Шёл за утечкой памяти, нашёл утечку диска: SXSSFWorkbook без dispose() в Apache POI Средний 9 мин 3. 2K Java * Apache * Kubernetes * Тестирование IT-систем * DevOps * Кейс Меня зовут Игорь Симаков, работаю engineering manager’ом и руковожу командами разработкиНа одном из наших сервисов, который работает с XLSX-файлами, прилетел production-алерт на высокое потребление памяти. Стандартный P3, обычно решается рестартом.
Пошёл смотреть поды и нашёл проблему, к памяти отношения не имеющую, но представляющую больший риск, чем сам алерт. Об этом и расскажу ниже: чем «утечка диска» отличается от «утечки памяти», как мы наткнулись на грабли в Apache POI и как закрыли их на уровне архитектурыУтечка памяти и утечка диска: в чём разницаВ обоих случаях логика одинаковая: приложение выделило ресурс и не освободило. Со временем расход растёт, пока не упрётся в потолокУтечка памяти.
Технические детали
Объекты копятся в куче Java (heap) или в native-памяти за её пределами - direct buffers (память для сетевого I/O в обход heap), нативные библиотеки, стеки потоков. Ссылка живёт там, где не должна, GC её не собирает. Память пода растёт, упирается в limits.
memory, Kubernetes делает OOMKill, под перезапускается. Локальная проблема: страдает только сам сервис, лечится рестартомУтечка диска. Приложение создаёт файлы и не удаляет.
Здесь начинается интересное: упирается это не в потолок пода, а в свободное место на диске ноды. По умолчанию /tmp в поде - это emptyDir без sizeLimit, физически он лежит на диске ноды (тот же /dev/vda2, где живёт ОС, container runtime, логи контейнеров). У пода лимита нет, он пишет, пока место есть на ноде.
Отраслевые последствия
Когда место заканчивается, write() возвращает ENOSPC, и падает не только наш сервис - падает всё, что пишет на этот диск, включая чужие поды и kubeletУтечка памятиУтечка диска (host disk)Где пределlimits. memory подасвободное место на нодеЧто происходит при упореOOMKill, под перезапускаетсяI/O начинает возвращать ENOSPCКого задеваеттолько сам подвсе поды на нодеАвтоматическое лечениеда (рестарт)нетУтечка памяти - локальная: Kubernetes отрабатывает её рестартом. Утечка диска распределённая, физический диск ноды один на всех, и автоматически его никто не «перезагрузит»Что такое RSSДальше я буду писать «RSS пода = N MiB».
Расшифрую сразу, чтобы не отвлекало. RSS (Resident Set Size) — это сколько физической оперативной памяти процесс реально занимает прямо сейчас. Сюда входит всё: JVM heap, non-heap (метаспейс, code cache), native-аллокации (direct buffers, пулы Netty), стеки потоков, замапленные в память файлыВиртуальной памяти процесс может «зарезервировать» больше, чем есть физической.
Но в RSS считается только то, что реально лежит в RAM. Именно RSS показывает top в одноимённой колонке, и именно RSS Kubernetes сравнивает с limits. memory пода, чтобы решить, не пора ли OOMKillЧем мы измеряем память: контейнер и JVMПод «потреблением памяти» в Kubernetes обычно смешивают два разных набора метрик.
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.




