Створення кастомного Middleware для моніторингу та оптимізації продуктивності запитів у Laravel з MongoDB

Перекладено ШІ 0 Laravel News 23 квітня, 2026

Чи знаєте ви, які саме запити сповільнюють роботу вашого проєкту на Laravel та MongoDB? Навчіться створювати систему моніторингу для автоматичного виявлення та аналізу проблем із продуктивністю.

Проблеми з продуктивністю часто найскладніше розв'язати, адже вони не є багами у прямому сенсі. Найчастіше вони ховаються у запитах до бази даних: застосунок працює коректно, але повільно.

Якщо маршрут (route) гальмує, причина зазвичай у неефективних запитах, відсутності індексів або занадто ресурсомістких агрегаціях. Такі вузькі місця важко виявити без належного моніторингу.

У цьому гайді ми створимо легку систему моніторингу для застосунків на Laravel та MongoDB. Наша мета — відстежувати час виконання запитів та тривалість обробки запитів, щоб розробники могли швидко знаходити та оптимізувати повільні операції.

Laravel та MongoDB — це потужне поєднання продуктивного PHP-фреймворка та гнучкої бази даних, створеної для масштабування. Завдяки офіційному пакету Laravel MongoDB ви можете використовувати Eloquent ORM для керування даними без жорстких обмежень традиційних SQL-схем.

Наприкінці цього посібника ви отримаєте систему, яка:

  • Фіксує час виконання запитів до MongoDB.
  • Автоматично ідентифікує повільні запити.
  • Логує дані про продуктивність для подальшого аналізу.
  • Автоматично видаляє старі логи за допомогою TTL-індексів.

# Попередні вимоги

Для роботи з туторіалом вам знадобляться:

  • Налаштоване середовище розробки для Laravel та MongoDB.
  • Досвід роботи з фреймворком Laravel.
  • Акаунт у MongoDB Atlas.

# Архітектура системи

Розглянемо, як працюватиме наш моніторинг. Система складається з двох компонентів: Request Middleware та MongoDB Command Subscriber.

Request Middleware

Це кастомний Middleware, який вимірює загальну тривалість HTTP-запиту від моменту отримання до повернення відповіді клієнту.

MongoDB Command Subscriber

Це PHP-клас, який підписується на події драйвера MongoDB PHP. Драйвер дозволяє відстежувати виконання команд у базі даних у реальному часі.

Підписавшись на ці події, ми зможемо отримувати дані про тип операції (find, insert, aggregate), назву колекції та час виконання.

Нижче наведена схема роботи: від надходження запиту до збереження результатів у колекції MongoDB.

Схема роботи моніторингу

# Налаштування проєкту

Створимо новий проєкт Laravel:

composer create project laravel/laravel devrel-tutorial-customMiddlewareForQueryPerformance

Переконайтеся, що у вас встановлено MongoDB PHP Extension та додано до php.ini. Якщо потрібна допомога з інсталяцією, скористайтеся офіційною інструкцією.

Тепер встановимо пакет Laravel MongoDB:

composer require mongodb/laravel-mongodb

# Конфігурація .env

Додайте дані для підключення до MongoDB Atlas у файл .env. Замініть MONGODB_URL на свій рядок підключення.

# Налаштування config/database.php

Додайте конфігурацію MongoDB у масив connections файлу config/database.php:

'mongodb' => [
    'driver' => 'mongodb',
    'dsn' => env('MONGODB_URI'),
    'database' => env('MONGODB_DATABASE'),
],

Також встановіть MongoDB як з'єднання за замовчуванням:

'default' => env('DB_CONNECTION', 'mongodb'),

# Створення моделі Post для тестування

Для перевірки трекера нам потрібні дані. Створимо модель Post, фабрику та seeder:

php artisan make:model Post -mf

Оновіть app/Models/Post.php:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use MongoDB\Laravel\Eloquent\Model;
 
class Post extends Model
{
    use HasFactory;
 
    protected $connection = 'mongodb';
    protected $collection = 'posts';
    protected $fillable = [
        'title',
        'body'
    ];
}

# Наповнення даними (Seeding)

Налаштуйте фабрику в database/factories/PostFactory.php:

<?php
 
namespace Database\Factories;
 
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
 
class PostFactory extends Factory
{
    protected $model = Post::class;
 
    public function definition(): array
    {
        return [
            'title' => fake()->sentence(),
            'body' => fake()->paragraph(5),
        ];
    }
}

Підготуйте seeder у database/seeders/PostSeeder.php:

<?php
 
namespace Database\Seeders;
 
use Illuminate\Database\Seeder;
use App\Models\Post;
 
class PostSeeder extends Seeder
{
    public function run(): void
    {
        Post::factory()->count(10)->create();
    }
}

Запустіть наповнення бази даних:

php artisan db:seed

# Лог продуктивності

Створимо модель PerformanceLog для збереження метрик у колекції performance_logs:

php artisan make:model PerformanceLog

Оновіть app/Models/PerformanceLog.php:

<?php
 
namespace App\Models;
 
use MongoDB\Laravel\Eloquent\Model;
 
class PerformanceLog extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'performance_logs';
    protected $fillable = [
        'route',
        'collection',
        'operation',
        'duration_ms',
        'request_duration',
        'is_slow',
        'created_at'
    ];
}

# Створення QueryMonitorService

Цей сервіс збиратиме метрики запитів. Створіть app/Services/QueryMonitorService.php:

<?php
 
namespace App\Services;
 
use App\Models\PerformanceLog;
 
class QueryMonitorService
{
    protected array $queries = [];
    protected int $slowThreshold = 200; // поріг у мілісекундах
 
    public function record(string $collection, string $operation, float $duration): void
    {
        $this->queries[] = [
            'collection' => $collection,
            'operation' => $operation,
            'duration_ms' => $duration,
            'is_slow' => $duration > $this->slowThreshold
        ];
    }
 
    public function persist(string $route, float $requestDuration): void
    {
        foreach ($this->queries as $query) {
            PerformanceLog::create([
                'route' => $route,
                'collection' => $query['collection'],
                'operation' => $query['operation'],
                'duration_ms' => $query['duration_ms'],
                'request_duration' => $requestDuration,
                'is_slow' => $query['is_slow'],
                'created_at' => now()
            ]);
        }
    }
}

# Підписка на події драйвера MongoDB

Використовуємо Command Subscriber для перехоплення команд до бази даних. Створіть app/Monitoring/MongoCommandSubscriber.php:

<?php
 
namespace App\Monitoring;
 
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use App\Services\QueryMonitorService;
 
class MongoCommandSubscriber implements CommandSubscriber
{
    protected array $startTimes = [];
    protected array $operations = [];
    protected array $collections = [];
 
    public function commandStarted(CommandStartedEvent $event): void
    {
        $requestId = $event->getRequestId();
        $this->startTimes[$requestId] = microtime(true);
 
        $operation = $event->getCommandName();
        $command = get_object_vars($event->getCommand());
        $collection = $command[$operation] ?? 'unknown';
 
        $this->operations[$requestId] = $operation;
        $this->collections[$requestId] = $collection;
    }
 
    public function commandSucceeded(CommandSucceededEvent $event): void
    {
        $requestId = $event->getRequestId();
        if (!isset($this->startTimes[$requestId])) return;
 
        $duration = (microtime(true) - $this->startTimes[$requestId]) * 1000;
        $operation = $this->operations[$requestId] ?? $event->getCommandName();
        $collection = $this->collections[$requestId] ?? 'unknown';
 
        app(QueryMonitorService::class)->record($collection, $operation, $duration);
 
        $this->cleanup($requestId);
    }
 
    public function commandFailed(CommandFailedEvent $event): void
    {
        $this->cleanup($event->getRequestId());
    }
 
    protected function cleanup($requestId): void
    {
        unset($this->startTimes[$requestId], $this->operations[$requestId], $this->collections[$requestId]);
    }
}

# Реєстрація підписника

Зареєструйте підписника в app/Providers/AppServiceProvider.php:

public function register(): void
{
    $this->app->singleton(QueryMonitorService::class, fn() => new QueryMonitorService());
}
 
public function boot(): void
{
    \MongoDB\Driver\Monitoring\addSubscriber(new MongoCommandSubscriber());
}

# Створення Performance Middleware

Створіть Middleware для вимірювання тривалості запиту:

php artisan make:middleware PerformanceMiddleware

Код app/Http/Middleware/PerformanceMiddleware.php:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use App\Services\QueryMonitorService;
 
class PerformanceMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $start = microtime(true);
        $response = $next($request);
        $duration = (microtime(true) - $start) * 1000;
 
        $route = $request->route()?->getName() ?? $request->route()?->uri() ?? $request->path();
        app(QueryMonitorService::class)->persist($route, $duration);
 
        return $response;
    }
}

Зареєструйте його в bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        PerformanceMiddleware::class,
    ]);
})

# Тестування

Додайте тестовий маршрут у routes/web.php:

Route::get('/posts', function () {
    return Post::where('title', 'like', '%API%')->limit(10)->get();
});

Запустіть сервер (php artisan serve) та перейдіть за адресою /posts. Після цього в колекції performance_logs з'являться дані про продуктивність запиту.

# Очищення старих логів через TTL-індекси

Щоб колекція логів не розросталася безмежно, використовуйте TTL (Time To Live) індекси. MongoDB автоматично видалятиме документи через заданий час.

У MongoDB Atlas:

  1. Відкрийте колекцію performance_logs.
  2. На вкладці Indexes натисніть Create Index.
  3. Поле: created_at, Тип: Ascending (1), TTL: 604800 (7 днів).

Або через консоль:

db.performance_logs.createIndex( { created_at: 1 }, { expireAfterSeconds: 604800 })

# Висновок

Ця архітектура розділяє логіку моніторингу та самого застосунку. Завдяки Command Subscriber ми отримуємо глибоку аналітику запитів без модифікації коду репозиторіїв чи контролерів.

Повний сирцевий код доступний на GitHub. Також ви можете скористатися готовим Laravel-пакетом для швидкого впровадження цієї системи у ваші проєкти.

Популярні

Інше, що варто прочитати

26 Оновлено 23 квітня, 2026

"SQLSTATE[HY000] [2002] Connection refused" у Laravel в GitHub Actions

Чи стикалися ви з помилкою «SQLSTATE[HY000] [2002] Connection refused» під час налаштування GitHub Actions для вашого додатку на Laravel? У нашій статті ми розглянемо три поширені причини цієї помилки та надамо рішення для їх усунення. Читайте далі, щоб дізнатися, як ваш CI/CD потік може працювати бездоганно!

13 Оновлено 23 квітня, 2026

Простий пакет RabbitMQ для Laravel

Вам цікаво дізнатися, як спростити інтеграцію RabbitMQ у вашому Laravel-додатку? У нашій статті ми розглянемо пакет Simple RabbitMQ, який дозволяє легко налаштувати багатозʼєднання, публікувати повідомлення та обробляти черги за допомогою простого синтаксису. Читайте далі, щоб дізнатися більше!

16 Оновлено 23 квітня, 2026

Перетворення даних у типобезпечні DTO за допомогою пакету Data Model

Досліджуйте новий пакет Data Model для PHP, який спрощує процес гідратації об'єктів без зайвих складнощів! Дізнайтеся, як впровадження типобезпечних об'єктів може революціонізувати ваш підхід до розробки, читаючи нашу статтю