Расскажите про опыт оптимизации
Что проверяют
Работали ли вы с производительностью на JVM осознанно: умеете ли снять профиль, отличить проблему в коде от проблемы в базе и не путаете ли настройку флагов с оптимизацией.
Как отвечать
Одна история, доведённая до конца, сильнее списка приёмов. Порядок такой: симптом в метриках, гипотеза, замер, правка, повторный замер.
Инструменты, которые стоит называть: запись JFR и её разбор, профилировщик с флейм-графом, JMH для микробенчмарков, EXPLAIN ANALYZE для запросов, метрики приложения, логи сборщика мусора.
Порядок поиска почти всегда один: сначала данные, потом код, потом JVM.
- N+1 запросов из-за ленивых связей в JPA, лечится через
join fetch, граф сущностей или батчинг; - запрос без индекса, выборка всей таблицы в память, пагинация в памяти вместо SQL;
- исчерпанный пул соединений или пул потоков: очередь растёт, а процессор простаивает;
- неудачная структура данных: поиск перебором списка вместо словаря, упаковка примитивов в горячем цикле;
- логирование и сериализация на каждый вызов.
И только после этого память и сборка мусора: размер кучи, выбор сборщика, длительность пауз. Хорошо звучит фраза про компромисс: что усложнилось в коде и чем этот выигрыш окупился.
Где спотыкаются
Меряют вручную по таймстампам и делают вывод по одному прогону. На JVM это не замер: код сначала интерпретируется, компилятор оптимизирует его только после прогрева, а результат, который нигде не используется, может быть выброшен целиком. Микробенчмарк живёт в JMH, а сервисный замер под нагрузочным профилем.
Второе: подают замену сортировки как оптимизацию. Библиотечная сортировка уже хорошо оптимизирована, свой быстрый вариант почти всегда проигрывает ей, и уточнение про сложность и худший случай такой рассказ ломает.
Третье: настраивают сборщик мусора вслепую и просто добавляют памяти. Большая куча не делает паузы короче, а сборщик выбирают под цель: пропускная способность или короткие задержки. Если на вопрос про длительность пауз до и после ответа нет, история не засчитывается.