Skip to content

Производительность Laravel — лучшие практики

Application-слой. Дополняет ../code-quality.md (раздел производительности) и ../migrations.md (индексы). Инфраструктурный слой (тюнинг PostgreSQL/PHP-FPM/Redis/NGINX/Docker, бюджет памяти, уровни кеширования) — в infrastructure-performance.md.

Иерархия оптимизации (сверху — наибольший эффект)

  1. Устранить N+1 (->with(), preventLazyLoading в dev) — см. eloquent-database.md
  2. Индексы БД на WHERE/ORDER BY/JOIN — см. ../migrations.md
  3. Кеширование дорогих запросов/вычислений
  4. Очереди для тяжёлых/медленных операций
  5. Production-оптимизации (opcache, config/route cache)
  6. Octane — для high-load (последний шаг, не первый)

Кеширование

php
// Redis cache tags — гранулярная инвалидация
Cache::tags(['organizations', "city:{$slug}"])->remember(
    "orgs:{$slug}:page:{$page}",
    now()->addHours(6),
    fn () => Organization::forCity($slug)->paginate(20),
);

// Инвалидация только нужных тегов (НЕ Cache::flush() — убьёт сессии/throttle)
Cache::tags(['organizations'])->flush();
  • Ключи кеша — константы (CacheKeys::ORG_LIST), не строки в разных местах.
  • Filament afterSave() → flush нужных тегов (см. ../filament.md).
  • TTL осознанно: статика — часы/сутки, динамика — минуты.
  • Tags работают только на Redis/Memcached (не file/database driver).

Очереди (Jobs)

Выносить в очередь: email/telegram/SMS, обработка файлов, внешние API, отчёты, импорт/экспорт.

php
final class ProcessImport implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 30;          // или [10, 30, 60] — прогрессивно

    public function __construct(public int $importId) {}   // ID, не модель-массив

    public function handle(ImportService $service): void
    {
        $service->run($this->importId);   // логика в Service, Job только диспатчит
    }

    public function failed(\Throwable $e): void { /* уведомить, пометить */ }
}
  • $tries, $backoff, failed() — ВСЕГДА определять.
  • Передавать ID/модель (SerializesModels), не массивы данных (stale + bloat).
  • Разделять очереди: default, notifications, heavy — тяжёлое не блокирует быстрое.
  • Horizon для Redis-очередей (мониторинг, авто-балансировка воркеров, метрики).

Production-команды (деплой)

bash
php artisan config:cache       # объединяет config в один файл (env() перестаёт работать!)
php artisan route:cache        # кеш роутов (требует только closure-free routes)
php artisan view:cache         # прекомпиляция Blade
php artisan event:cache        # кеш event-listener маппинга
composer install --optimize-autoloader --no-dev
php artisan optimize           # всё сразу (L11+)
  • После config:cacheenv() в коде возвращает null. Только config(). (ещё причина не использовать env() вне config).
  • opcache включён в prod (opcache.enable=1, preloading для горячего кода).

Профилирование

ИнструментНазначение
Telescopedev: запросы, N+1, jobs, исключения, request timeline
Pulseproduction-friendly: slow queries, slow jobs, usage (легче Telescope)
Debugbardev: панель с запросами/таймингами в браузере
Laravel Boost MCPdatabase-query + EXPLAIN для CC-анализа (см. claude-code-workflow.md)
DB::enableQueryLog()точечный замер в коде
  • Профилировать ДО оптимизации (не угадывать узкое место).
  • EXPLAIN на тяжёлых запросах — Seq Scan на больших таблицах = нужен индекс.

Octane (high-load, опционально)

  • Держит приложение в памяти (Swoole/RoadRunner/FrankenPHP) — нет bootstrap на каждый request.
  • Прирост x2-x4 RPS, НО: state leakage между запросами — статика, синглтоны с состоянием опасны.
  • Применять когда уже выжаты N+1, кеш, индексы. Не «на старте».
  • FrankenPHP — современный выбор (HTTP/3, встроен в образ).

Массовые операции

php
// Bulk insert/upsert — один запрос вместо N
Model::insert($rows);                              // без событий/timestamps
Model::upsert($rows, ['unique_col'], ['update_col']);

// Обход больших таблиц — chunkById/lazy, не get()
Model::lazy()->each(fn ($m) => ...);               // генератор, не грузит всё

Anti-patterns

ПаттернПроблемаРешение
N+1 в Blade @foreachсотни запросов->with() в контроллере
Cache::flush()убивает сессии/throttle/всёCache::tags([...])->flush()
Синхронный email в контроллереHTTP висит 2-5сJob/Queue
get() на таблице >10kOOMchunkById() / lazy()
Job без $tries/failed()бесконечный retry / silent failвсегда определять
env() в коде после config:cacheвозвращает null в prodconfig()
Octane первым шагомсложность без устранения N+1сначала базовая оптимизация
Cache file/database driver + tagstags не работаютRedis

Внутренняя база знаний студии