Тема
Производительность: быстрые победы
Что даёт максимум ускорения за минимум усилий — в порядке отдачи. Глубокая теория: практики performance и инфраструктура.
Правило №0: сначала измерь
Оптимизация без замера — гадание. Инструменты по нарастающей:
| Инструмент | Что показывает | Когда |
|---|---|---|
| Debugbar | Запросы/тайминги текущей страницы | Dev, точечно |
| Telescope | Запросы, джобы, кеш-промахи по всем запросам | Dev, постоянно |
| Pulse | Медленные запросы/джобы/endpoint'ы в проде | Prod, дешёвый мониторинг |
EXPLAIN ANALYZE (postgres MCP) | Почему конкретный запрос медленный | Разбор конкретного запроса |
Benchmark::dd() | Микрозамер куска кода | Быстрая проверка гипотезы |
Симптом → куда смотреть:
| Симптом | Первый подозреваемый |
|---|---|
| Все страницы медленные одинаково | Нет optimize/opcache; сессии/кеш не в Redis |
| Одна страница медленная | N+1 или тяжёлый запрос без индекса (Debugbar) |
| Медленно под нагрузкой, быстро в одиночку | Пул БД, блокировки, память PHP-FPM |
| Быстро гостю, медленно залогиненному | Запросы в middleware/composer'ах на каждый запрос |
| Периодические провалы | Крон/очередь конкурирует за ресурсы; кеш-стампид |
| Медленный первый запрос после деплоя | Холодный opcache/кеши — прогрев в деплое |
Победа 1: кеши фреймворка (5 минут, всегда)
php artisan optimize в деплое = config + routes + views + events cache. Плюс composer install --optimize-autoloader --no-dev. Без этого прод исполняет сотни лишних stat() и парсит конфиги на каждый запрос. Opcache в PHP-образе — обязателен (по спецификации Docker).
Победа 2: убрать N+1 и лишние столбцы (час, кратный эффект)
->with()на все связи, используемые в цикле/Blade — по стандарту N+1 это баг.withCount()/withSum()вместо загрузки связей ради агрегата.select()только нужного на списках: тащитьcontentLONGTEXT в листинг из 50 карточек — типичный незаметный тормоз.pluck()/value()там, где нужны не модели, а значения.
Победа 3: индексы под реальные запросы (час)
- Индексировать столбцы из WHERE и ORDER BY реальных запросов (Pulse/pg_stat_statements покажут топ), составные — под частые комбинации.
- Кириллический
ILIKEне использует btree — functional index наlower(). - Полнотекст на 1M+ строк — GIN + tsvector (готовый рецепт в миграциях).
- Периодический аудит: неиспользуемые индексы замедляют запись — сносить.
Победа 4: кеш данных с гранулярной инвалидацией (полдня)
- Тяжёлые выборки (меню, фасеты, блоки главной) —
Cache::rememberс тегами. - Инвалидация — в
afterSaveFilament по тегам, неCache::flush()(стандарт студии). - Кеш-стампид на популярных ключах —
Cache::flexible()(stale-while-revalidate). - Страничный кеш для гостей (spatie/laravel-responsecache или свой middleware) — самый дешёвый способ держать сотни RPS на контентном сайте.
Победа 5: вынести медленное из запроса (полдня)
Всё, что дольше ~100мс и не нужно для ответа — в очередь: почта, Telegram, ресайз изображений, внешние API, пересчёты. Лёгкая альтернатива для некритичного — defer(). HTTP-клиент — всегда с timeout, иначе чужие тормоза становятся вашими.
Победа 6: фронт и статика (час)
- Ассеты Vite — с хешем в имени: отдавать с
Cache-Control: immutable(Caddy-заголовок). - Изображения: WebP-конверсии medialibrary +
loading="lazy"+ размеры под вывод (не грузить 2000px в карточку 300px). - gzip/zstd на прокси — в студийном Caddy уже включено.
- Критичный CSS инлайном, остальное — deferred, шрифты —
font-display: swap.
Что НЕ делать преждевременно
| Соблазн | Почему рано |
|---|---|
| Octane | Типовому корпсайту не нужен; сначала кеши+индексы+N+1. Профит — на API с высоким RPS |
| Микросервисы/шардинг | Монолит на Laravel держит намного больше, чем кажется |
| Свой «умный» кеш-слой | Сложность > выгода; теги + remember закрывают 95% |
| Реплики БД для чтения | Сначала pg_stat_statements: обычно виноваты 3–5 запросов |
| Переписать «медленный Eloquent» на raw SQL | Обычно медленный не Eloquent, а отсутствие индекса/N+1 |
Бюджет производительности (ориентиры для типовых проектов студии)
| Метрика | Целевое |
|---|---|
| TTFB контентной страницы (с кешем) | < 200 мс |
| TTFB без кеша | < 500 мс |
| Запросов к БД на страницу | < 15 |
| Ответ API-endpoint'а | < 300 мс |
| Обработка очереди уведомлений | < 30 с от события |
Превышение — не «оптимизировать всё», а найти виновника по таблице «симптом → подозреваемый».