Проектирование Java Highload приложения с высоким SLA по RPS и latency затрагивает множество аспектов. В частности, OLTP, мультиплексирование запросов, неблокирующий I/O и прочее. В данной статье хотел бы поговорить про сборку мусора.
1125 открытий750 показов
Несколько лет работаю Java инженером в финтехе. Недавно вывели на рынок очередное HL-решение со строгим SLA по RPS и Latency. Хотел бы кратко описать проблемы и путь их решения.
Когда бизнес затребовал систему для обработки транзакций в реальном времени с пиковой нагрузкой в 50 тысяч операций в секунду, я понял, что серые будни enterprise инженера наконец станут весельем. Наш сервис должен был не просто работать — он должен был дышать под давлением, сохраняя отклик в пределах 10 мс. Не все проблемы в мире можно решить горизонтальным масштабированием, и одним из камней преткновения стала Java, а точнее — её «мусор».
Выбор GC: G1, Shenandoah или ZGC?
Первое, с чем столкнулась наша команда, — продолжительные STW-прерывания при нагрузке. Изначально использовали G1GC как «дефолтный» выбор для баланса между latency и throughput. Но под нагрузкой 80% CPU даже G1 не справлялся: пиковые паузы достигали 400 мс, что для платежей было неприемлемо.
Мы устроили мозговой штурм с performance-инженерами. Варианты:
Скрутка и накрутка опыта: работает ли это в айтишкеtproger.ru
1. Shenandoah — низкие паузы, но требуется больше CPU.
2. ZGC — субмиллисекундные паузы даже на терабайтных хипах, но тогда (на старте проекта) он был экспериментальным в OpenJDK 11.
3. Ручная настройка G1 — максимизировать предсказуемость через MaxGCPauseMillis
и InitiatingHeapOccupancyPercen
.
После недели тестов на стенде с имитацией пиковой нагрузки (JMeter + Gatling) и профилирования через async-profiler, выбор пал на ZGC. Его паузы не превышали 2 мс даже при 32 ГБ куче, а алгоритм «цветных указателей» позволял масштабироваться без блокировок. Риск? Да. ZGC был новым, но его поддержка в последних LTS-версиях и тесты убедили нас.
Тонкая настройка и проклятие «тихих» утечек
С ZGC мы выставили:
1. -Xmx48g
(с запасом для избежания частых аллокаций),
2. -XX:SoftMaxHeapSize=40g
(чтобы ZGC активнее возвращал память ОС),
3. -XX:+UseLargePages
(снижение overhead на page faults).
Что значит быть инженером в новых реалиях? И какой смысл мы вкладываем в эти слова — расскажем на GPB CONF!tproger.ru
Но через месяц нагрузочного тестирования обнаружили «ползучее» увеличение потребления памяти. Performance-команда, анализируя дампы через Eclipse MAT, нашла проблему: кэш данных транзакций в Apache Ignite не учитывал soft-ссылки, накапливая объекты. Вместо ConcurrentHashMap
перешли на `Caffeine
` с политикой expiry после 10 секунд, что снизило давление на GC.
Команда performance-инженеров: алхимики метрик
Работа с perfomance-инженерами напоминала лабораторию безумных ученых. Каждый параметр JVM, каждый HTTP-эндпоинт тестировался через JMH, а Grafana-дашборды визуализировали следующее:
1. GC pauses (ZGC собирал статистику через -Xlog:gc*
),
2. Allocation rate (до 1.5 ГБ/сек в пиках),
3. CPU utilization (наши самописные RateLimiter’ы съедали 15% ресурсов).
Совместно мы написали скрипты на Python, которые автоматически подбирали -XX:ConcGCThreads
и -XX:ParallelGCThreads
в зависимости от нагрузки на CI/CD стендах. Это сократило время настройки на 70%.
Продакшн: огненное крещение
После релиза первые дни мы мониторили всё: New Relic для трейсинга медленных методов, Prometheus для сбора JVM-метрик. ZGC не подвел — 99-й перцентиль отклика оставался на уровне 12 мс. Но однажды ночью мы получили алерты по потреблению CPU. Оказалось, фоновый процесс генерации отчетов создавал миллионы временных объектов. Решение: выделили его в отдельный микросервис с выделенным пулом памяти и CMS (ему хватало).
Итоги
Через полгода система обрабатывала 55k операций/сек с пиковыми паузами GC в 1.8 мс. Главные уроки:
1. GC — не серебряная пуля. Даже ZGC требует понимания аллокаций в коде.
2. Performance-инженеры — ваши лучшие союзники. Их взгляд со стороны JVM спас нас десятки раз.
3. Документировать всё. Наши конфиги и скрипты настройки GC стали стандартом для других команд банка.
Теперь, когда я вижу, как сервис работает без перебоев, вспоминаю те бессонные недели с дампами и тестами… и понимаю: это того стоило. Ведь в мире highload каждая миллисекунда — это деньги. Или, как говорят у нас в банке, «миллисекунда — миллион».