Ответ
Проблема: Система обработки событий в реальном времени начала деградировать при нагрузке >10k RPS. Основные симптомы: высокий GC overhead (частые stop-the-world паузы) и contention на блокировках в буфере данных, что приводило к росту latency и сбоям.
Анализ и решение:
- Профилирование: С помощью Async Profiler и JFR (Java Flight Recorder) выявил "горячие" точки: частое переаллоцирование
ArrayListи синхронизацию на уровне метода. - Оптимизации:
- Структура данных: Заменил
ArrayList<Event>наArrayDeque<Event>с предварительно заданной начальной емкостью, чтобы избежать резервного копирования при расширении. - Управление памятью: Для thread-local состояния использовал
ThreadLocalс переиспользуемыми буферами, уменьшив давление на GC. - Настройка JVM: Перешел на сборщик мусора Shenandoah для предсказуемых и коротких пауз, даже на больших heap-ах.
- Структура данных: Заменил
Ключевое изменение в коде:
// Было: динамический рост и синхронизированный доступ
private List<Event> buffer = Collections.synchronizedList(new ArrayList<>());
// Стало: фиксированная структура с разделением данных по потокам
private static final ThreadLocal<ArrayDeque<Event>> LOCAL_BUFFER =
ThreadLocal.withInitial(() -> new ArrayDeque<>(1024)); // Предотвращает частые аллокации
Результат: Система стабильно обрабатывала 50k RPS с p99 latency <50ms. Вывод: В high-performance системах иногда приходится жертвовать абстракциями стандартных коллекций в пользу более низкоуровневого управления памятью и данными.