Поддержите проект, сделав пожертвование

Обработка зависших запросов в PostgreSQL

При работе с PostgreSQL иногда возникает необходимость управлять зависшими запросами, которые могут блокировать миграции или другие важные процессы. В данной статье мы рассмотрим, как проверить активные процессы и блокировки, а также как принудительно завершать зависшие транзакции.

Проверка активных процессов и блокировок Первым шагом для диагностики проблем в базе данных является проверка активных процессов и блокировок. Для этого можно использовать следующий SQL-запрос:

SELECT pid, age(clock_timestamp(), query_start) AS duration, usename, query 
FROM pg_stat_activity 
WHERE state != 'idle' 
  AND pid != pg_backend_pid();

Этот запрос вернет информацию о текущих активных процессах, включая их идентификатор процесса (PID), продолжительность выполнения запроса, имя пользователя и сам запрос. Таким образом, можно определить, какие запросы выполняются слишком долго или блокируют другие процессы.

Принудительное завершение зависшей транзакции Если вы обнаружили процесс, который блокирует другие операции, его можно завершить принудительно. Для этого используйте следующий SQL-запрос:

SELECT pg_terminate_backend(pid);

Замените pid на идентификатор процесса, который необходимо завершить. Этот запрос завершит выбранный процесс и освободит ресурсы, которые он занимал.

Завершение всех зависших процессов Если необходимо завершить все зависшие процессы, кроме текущей сессии, можно использовать следующий запрос. Он завершит все процессы, которые находятся в состоянии, отличном от idle, и выполняются более 10 минут:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state != 'idle'
  AND pid != pg_backend_pid()
  AND query_start < NOW() - INTERVAL '10 minutes';

Этот запрос позволяет автоматизировать процесс очистки зависших запросов, что может быть особенно полезно при выполнении длительных операций или миграций.

Заключение Управление зависшими запросами в PostgreSQL — важная задача для обеспечения стабильной и эффективной работы базы данных. С помощью приведенных выше SQL-запросов можно диагностировать и устранять проблемы, связанные с зависшими процессами, что поможет поддерживать высокую производительность и надежность системы.

Паттерн "Обработчик" (Handler) с использованием DTO и VO

  1. Изоляция бизнес-логики: Бизнес-логика изолирована в обработчиках, что позволяет сделать код более организованным и легко поддерживаемым.
  2. Тестируемость: Обработчики легко тестируются отдельно, так как они не зависят от инфраструктурного кода (например, контроллеров).
  3. Переиспользование: Обработчики могут быть легко переиспользованы в различных частях приложения.
  4. Ясность и читаемость кода: Использование DTO и VO позволяет четко определить структуру передаваемых данных, что улучшает читаемость и понимание кода.
  5. Соблюдение принципов SOLID: Обработчики помогают соблюдать принципы единственной ответственности (SRP) и разделения интерфейсов (ISP).
  6. Иммутабельность VO: Значения VO не изменяются после создания, что помогает избежать непреднамеренных изменений и улучшает предсказуемость кода.

Рассмотрим на примере API маршрута, который создает товар

  1. Маршрут (Router) Создадим маршрут для обработки POST-запросов на создание нового товара.
<?php

use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;

Route::post('products', [ProductController::class, 'store'])
            ->name('products.store');
  1. Контроллер (Controller) Контроллер принимает запрос от клиента и вызывает соответствующий обработчик.
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Domains\Product\Core\Handlers\CreateProductHandler;
use App\Http\Requests\CreateProductRequest;
use Illuminate\Http\JsonResponse;

class ProductController extends Controller
{
    public function store(CreateProductRequest $request, CreateProductHandler $handler): JsonResponse
    {
        $product = $handler->handle($request->getDto());

        return response()->json([
            'message' => 'Product created successfully',
            'data' => $product
        ], 201);
    }
}
  1. Реквест (Request) Реквест используется для валидации входящих данных и создания DTO.
<?php

declare(strict_types=1);

namespace App\Http\Requests;

use App\Domains\Product\Core\DTO\CreateProductDTO;
use Illuminate\Foundation\Http\FormRequest;

class CreateProductRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // Здесь можно добавить логику авторизации
    }

    public function rules(): array
    {
        return [
            'product.name'        => 'required|string|max:255',
            'product.description' => 'required|string',
            'product.price'       => 'required|integer|min:0',
            'tags'                => 'array',
            'tags.*'              => 'integer|exists:tags,id',
            'images'              => 'array',
            'images.*'            => 'string',
        ];
    }

    public function getDto(): CreateProductDTO
    {
        return CreateProductDTO::fromArray($this->validated());
    }
}
  1. DTO (Data Transfer Object) DTO используется для передачи данных запроса между слоями приложения.
<?php

declare(strict_types=1);

namespace App\Domains\Product\Core\DTO;

use App\Domains\Product\Core\ValueObjects\ProductVO;
use Illuminate\Support\Arr;

final class CreateProductDTO
{
    public function __construct(
        public ProductVO $product
        public array $tags,
        public array $images
    ) {
    }

    public static function fromArray(array $data): self
    {
        return new self(
            ProductVO::fromArray(Arr::get($data, 'product')),
            Arr::get($data, 'tags', []),
            Arr::get($data, 'images', [])
        );
    }
}
  1. VO (Value Object) VO инкапсулирует небольшое количество данных.
<?php

declare(strict_types=1);

namespace App\Domains\Product\Core\ValueObjects;

use Illuminate\Support\Arr;

final class ProductVO
{
    public function __construct(
        public string $name,
        public string $description,
        public float $price
    ) {
    }

    public static function fromArray(array $data): self
    {
        return new self(
            Arr::get($data, 'name'),
            Arr::get($data, 'description'),
            null !== Arr::get($data, 'price') ? (float) Arr::get($data, 'price') : 0
        );
    }
}
  1. Обработчик (Handler) Обработчик содержит логику создания товара.
<?php

declare(strict_types=1);

namespace App\Domains\Product\Core\Handlers;

use App\Domains\Product\Core\DTO\CreateProductDTO;
use App\Models\Product;
use Illuminate\Support\Facades\DB;

final class CreateProductHandler
{
    public function handle(CreateProductDTO $dto): Product
    {
        return DB::transaction(function () use ($dto) {
            // Создание товара
            $product = new Product();
            $product->name = $dto->product->name;
            $product->description = $dto->product->description;
            $product->price = $dto->product->price;
            $product->save();

            // Привязка тегов
            $product->tags()->attach($dto->tags);

            // Сохранение изображений
            foreach ($dto->images as $image) {
                $product->images()->create(['path' => $image]);
            }

            return $product;
        });
    }
}

Пошаговое объяснение

  1. Маршрут (Router): В файле маршрутов Laravel определяем маршрут для POST-запросов на создание нового товара. Этот маршрут связывается с методом store контроллера ProductController.
  2. Контроллер (Controller): Контроллер ProductController принимает HTTP-запрос и вызывает метод getDto() реквеста CreateProductRequest для получения DTO. Затем он передает DTO обработчику CreateProductHandler для создания товара и возвращает JSON-ответ с данными о созданном товаре.
  3. Реквест (Request): CreateProductRequest используется для валидации входящих данных. Метод getDto() создает DTO из валидированных данных и возвращает его.
  4. DTO (Data Transfer Object): CreateProductDTO используется для передачи данных запроса. Он инкапсулирует объект ProductVO, массив тегов и массив изображений.
  5. VO (Value Object): ProductVO инкапсулирует данные о товаре (название, описание и цену). Он используется для структурирования данных внутри DTO.
  6. Обработчик (Handler): CreateProductHandler содержит логику создания товара. Он принимает DTO и создает товар, привязывает теги и сохраняет изображения в транзакции.

Для успешного использования паттерна “Обработчик” с DTO и VO программист должен иметь уровень подготовки Middle (средний).

1