# Redis и распределённая инфраструктура

Elang может совместно использовать кеш, сессии, ограничения частоты запросов и блокировки через Redis. SQL остаётся долговременным авторитетным хранилищем заданий и истории воспроизведения событий в реальном времени. Потоки Redis переносят уведомления о зафиксированных изменениях, которые пробуждают обработчики очереди и процессы WebSocket. Такое устройство сохраняет запись данных приложения, добавление заданий в очередь и публикацию событий внутри одной транзакции SQL.

## Подключение приложения

Установите полный фреймворк, создайте служебные таблицы и настройте один эндпоинт Redis, общий для всех процессов приложения:

```dotenv
REDIS_URL=redis://127.0.0.1:6379/0
```

```bash
php elang platform:setup
php elang outbox:work
php elang queue:work
```

Размещайте Redis в закрытой сети, требуйте аутентификацию и ACL и используйте TLS, если трафик пересекает границы доверия. Храните DSN в менеджере секретов или неотслеживаемом `.env`; никогда не помещайте учётные данные в команды, журналы, снимки экрана или Git. Ограничивайте ключи и команды потребностями приложения. Номер базы Redis не изолирует Pub/Sub, поэтому Elang также добавляет префикс каждому каналу приложения и пространству имён ключей.

Префикс по умолчанию выводится из `APP_URL`. Приложения, использующие один URL, должны явно задать уникальный префикс развёртывания в конфигурации:

```php
'redis' => [
    'url' => Elang\Support\Config::env('REDIS_URL', ''),
    'prefix' => 'acme:production',
],
```

Для тестовой среды используйте другой префикс. Изменение префикса завершает пользовательские сессии на Redis и создаёт новые пространства имён ограничителя частоты и кеша. Не запускайте команду очистки кеша для пространства имён постороннего приложения.

## Кеш и блокировки

```php
$cache = $app->container->get(Psr\SimpleCache\CacheInterface::class);
$cache->set('catalog.version', 4, 300);

$runtime = $app->container->get(Elang\Infrastructure\Runtime::class);
$lock = $runtime->lock('report:42', 30);
if ($lock->acquire()) {
    try {
        // Perform bounded, idempotent work. Refresh before the lease expires.
    } finally {
        $lock->release();
    }
}
```

Без `REDIS_URL` кеш и блокировки используют закрытую локальную файловую систему. Этот запасной вариант полезен на одном хосте. Он не координирует несколько хостов. Используйте `Runtime::lock()` для ресурсов с префиксом приложения; при прямом внедрении Symfony `LockFactory` необходимо самостоятельно выбирать имена ресурсов с корректным пространством имён.

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

## Долговременный outbox в SQL

Когда Redis включён, `Queue::push()` и `Events::publish()` добавляют запись outbox через то же подключение SQL, что используется для основной записи данных. Откат удаляет все связанные изменения. Процесс пересылки читает только зафиксированные строки, публикует их в поток Redis ограниченного размера и помечает доставленными после принятия Redis. Ключ дедупликации защищает обычные повторные попытки пересылки в течение семи дней. Потребителям по-прежнему нужна идемпотентная обработка: это доставка как минимум один раз, а не обещание выполнения ровно один раз.

```bash
php elang outbox:dispatch
php elang outbox:work
php elang operations:prune
```

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

Потеря Redis не удаляет задания SQL или историю воспроизведения. Восстановите Redis, перезапустите процесс пересылки и следите за числом ожидающих записей outbox. Недоступность кеша, сессий и ограничителя частоты имеет собственные последствия: чтение кеша приложения может возвращать результат как при отсутствии записи в кеше, а операции сессий, ограничителя и блокировок сообщают о сбоях Redis. Они не переключаются незаметно на независимое локальное состояние авторизации. Приложения должны возвращать контролируемую ошибку сервиса и оповещать оператора.

## Порядок событий в реальном времени и восстановление

Идентификаторы событий монотонно возрастают внутри канала. Блокировка строки SQL сериализует выделение последовательности канала на протяжении внешней бизнес-транзакции. Это не позволяет принимать порядок выделения автоинкрементных значений за порядок фиксации. Откаты не публикуют фантомные события. Номера последовательностей разных каналов нельзя сравнивать глобально.

Процессы WebSocket читают ограниченные пакеты уведомлений Redis и группируют клиентов по каналу и курсору, затем выполняют один SQL-запрос для каждой выбранной группы. Также выполняется ограниченный сверочный обход SQL для восстановления после пропущенных уведомлений Redis. Реализация использует синхронные ограниченные вызовы SQL и Redis; она не является измеренной гарантией неблокирующей пропускной способности. Изолируйте процессы WebSocket, настраивайте индексы и пулы базы данных и выполняйте собственный реалистичный нагрузочный тест.

Согласуйте срок хранения истории с ожидаемым временем отсутствия клиентов. Клиент, подключающийся после удаления нужной истории, должен обновить снимок состояния приложения. Краткосрочные разрешения WebSocket не заменяют проверку действующих бизнес-прав при новых операциях записи арендатора. См. [обмен в реальном времени](/docs/v0.1/realtime), [очереди](/docs/v0.1/queues) и [развёртывание](/docs/v0.1/deployment).
