# Фоновые задания и планирование

Выполняйте медленные операции вне HTTP-запросов. Elang предоставляет очередь заданий с JSON-данными в базе данных, отложенные задания, ограниченные повторные попытки, просмотр неудачных заданий и планирование по UTC. Одно подключение к базе данных позволяет совместно зафиксировать изменение данных приложения и добавление соответствующего задания в очередь.

## Подготовка служебных таблиц

Стартовые проекты аутентификации, блога, панели управления и API включают миграцию. В другом приложении выполните:

```bash
php elang queue:setup
```

Проверьте изменения схемы перед использованием `--force` в рабочей среде. SQLite удобен локально. Для нескольких серверов приложения используйте общую базу данных MySQL или PostgreSQL.

## Регистрация обработчика

Регистрируйте обработчики при начальной загрузке приложения или в провайдере сервисов. HTTP-процессы и обработчики очереди должны загружать одинаковые регистрации. Полезная нагрузка содержит данные JSON; Elang никогда не десериализует объекты PHP и не принимает имя класса из данных задания.

```php
use Elang\Queue\Jobs;
use Psr\Log\LoggerInterface;

$app->container->get(Jobs::class)->register(
    'reports.generate',
    function (array $payload, LoggerInterface $logger): void {
        // Load the record by ID and authorize the business operation here.
        $logger->info('Report requested', ['report_id' => $payload['report_id']]);
    }
);
```

## Добавление и выполнение заданий

```php
use Elang\Queue\Queue;

$id = $app->container->get(Queue::class)->push(
    'reports.generate',
    ['report_id' => 42],
    delay: 10
);
```

```bash
php elang queue:work
php elang queue:work --once
php elang queue:work --stop-when-empty --max-jobs=100
php elang queue:failed
php elang queue:retry JOB_ID
```

Задания резервируются с помощью условного обновления базы данных и токена резервирования. Гарантия доставки — **как минимум один раз**: аварийное завершение обработчика очереди или истечение срока резервирования может привести к новой попытке. Делайте обработчики идемпотентными с помощью уникальных ограничений на уровне приложения и ключей идемпотентности внешних провайдеров.

По умолчанию срок резервирования составляет 120 секунд, а максимальное число попыток — пять. Настройте `queue.lease_seconds` и `queue.max_attempts` в `config/app.php`. Длительность обработчика должна быть меньше срока резервирования; тайм-ауты диспетчера процессов также задавайте меньше этого срока, а долгие отчёты разбивайте на ограниченные части. Elang не прерывает выполняющийся PHP-обработчик и не продлевает его резервирование автоматически. Задания с истёкшим резервированием могут быть зарезервированы повторно; старое резервирование не может подтвердить выполнение задания, которое уже обрабатывает новый процесс.

Повторные попытки используют экспоненциально растущую задержку с пределом в один час. Списки неудачных заданий показывают класс исключения и метаданные, но не полезную нагрузку или сообщения исключений. Данные заданий находятся в закрытом хранилище базы данных; передавайте идентификаторы записей вместо секретов. Диспетчер процессов должен перезапускать обработчики очереди после развёртывания и неожиданных завершений. `--max-jobs` ограничивает цикл работы обработчика заданным числом успешных попыток; обработчик, который раз за разом обнаруживает пустую очередь, продолжает работать.

## Совместная фиксация данных предметной области и задания

```php
$database->connection()->transactional(function ($connection) use ($queue): void {
    $connection->insert('reports', ['status' => 'pending']);
    $queue->push('reports.generate', ['report_id' => (int) $connection->lastInsertId()]);
});
```

Обе операции должны использовать одно и то же внедрённое подключение `Database`. Исключение откатывает их вместе. Внешние HTTP-вызовы, доставка почты и отдельные базы данных находятся за пределами этой транзакции.

## Планирование заданий

```php
use Elang\Queue\Schedule;

$schedule = $app->container->get(Schedule::class);
$schedule->everyMinute('reports.minute', 'reports.generate', ['report_id' => 42]);
$schedule->dailyAt('reports.daily', '02:00', 'reports.generate', ['report_id' => 43]);
```

Запускайте `php elang schedule:run` раз в минуту через cron. Время задаётся в UTC. Общий уникальный ключ запуска в базе данных не позволяет двум процессам планировщика поставить в очередь одно расписание с тем же ID в одну и ту же минуту. Это не препятствует наложению долгого задания на следующий запуск по расписанию. Если такое наложение недопустимо, добавьте блокировку приложения. Пропущенные минуты не восполняются.

Продолжите изучение с [событий в реальном времени](/docs/v0.1/realtime) и [архитектуры крупных приложений](/guides/large-applications).
