150 запросов на один flush, и 4343 зелёных теста. Как я делал детектор N+1 и как он сам меня обманывал

FrolovAlexander 43 минуты назад 150 запросов на один flush, и 4343 зелёных теста. Как я делал детектор N+1 и как он сам меня обманывал Простой 13 мин 1.2K PHP * Тестирование IT-систем * Высоконагруженные системы * Кейс...
<5 — 2026'da uzaya kaç SpaceX Starship fırlatması ulaşacak?
Вот важная новость с фронта ИИ: FrolovAlexander 43 минуты назад 150 запросов на один flush, и 4343 зелёных теста. Как я делал детектор N+1 и как он сам меня обманывал Простой 13 мин 1. 2K PHP * Тестирование IT-систем * Высоконагруженные системы * Кейс Все тесты зелёные, а N+1 на месте.
Один open-source тайм-трекер на Symfony имеет 4343 теста, и все они проходят. При этом каждое сохранение записи времени отправляет в базу три дополнительных SELECT запроса: ставку самой записи, ставку проекта и ставку активности. Если в одном flush() 50 записей, а столько строк показывает страница массового редактирования, только за ставками уходит 150 запросов.
Технические детали
Тесты этот код покрывают, но ни один не падает, в них нет ни одной проверки проблем с запросами к базе данных. Для решения этой проблемы сделал инструмент, которого в конце августа ещё не существовало. Правда, ещё до проверок сторонних проектов на N+1, он успел несколько раз обмануть меня самого, и всегда одинаково, показывал зелёное там, где на самом деле не работал.
2000 запросов на одну страницуКогда-то мне пришлось работать с проектом, который вроде бы работал, но на него жаловались, некоторые страницы очень медленно открываются, а сам сервис периодически “подвисает”. Времени на поиски ушло много, а виновником оказалась ленивая загрузка на неограниченном списке в разделе для сотрудников. Одно открытие такой страницы стоило больше 2000 запросов к базе.
Если страницу открывали сразу несколько сотрудников, весь сервис фактически “умирал”. Когда такие места я вычистил, проект на скромных ресурсах спокойно выдерживал нагрузку. Спустя несколько лет, когда я там уже не работал, со мной связался CTO.
Отраслевые последствия
Он удивлялся, старый проект нагрузку держит, а новый, с кластером базы данных, несколькими серверами приложения и кластером Redis, ту же нагрузку не держит, хотя функционал и количество пользователей практически одинаковое. Из той истории я вынес неприятное наблюдение, руками отслеживать, какие запросы уходят и в какой последовательности, очень долго, и это не даёт никакой гарантии на будущее, ведь через полгода кто-нибудь добавит в шаблон обращение к ещё одной связи. Ловить такое надо на этапе разработки, а не по баг-репорту от пользователей и просевшей “конверсии”.
Вспомнил я об этом, пока писал статью про VIEW в MySQL и PostgreSQL, где снова пришлось подолгу читать планы запросов. В заметки записал себе, что нужен линтер на PHP, который собирает все SELECT проекта, прогоняет по каждому EXPLAIN и выдаёт ошибки, как PHPStan, только про производительность. Почти всё в этой формулировке потом пришлось поменять.
Готовый пакет показал зелёноеСначала я проверил и поискал похожие решения. Ближе всего по духу оказался популярный пакет, в виде трейта для PHPUnit со счётчиками запросов, поиском дублей, анализом планов, при этом заявлялась поддержка Laravel и Doctrine. Я поставил его на свой проект (Symfony, Doctrine, PostgreSQL 17) и написал пробный N+1.
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.






