Stemy.Persistence.Scheduler.Valkey 1.0.0
Stemy.Persistence.Scheduler.Valkey
Хранилище заданий Quartz 4 в Valkey (Redis) для Stemy.Persistence.Scheduler. Задания, триггеры, календари, паузы, выполняющиеся срабатывания и список узлов кластера лежат в Valkey, PostgreSQL планировщику не нужен. Несколько реплик сервиса делят одно хранилище: каждое срабатывание выполняет ровно одна из них.
Установка
<PackageReference Include="Stemy.Persistence.Scheduler.Valkey" Version="1.0.0" />
Подключение
IConnectionMultiplexer берётся из контейнера — собственного подключения пакет не создаёт.
builder.Services.AddValkey(builder.Configuration); // Stemy.Valkey
builder.Services.AddQuartzScheduler(quartz => quartz.UseValkey(
builder.Configuration,
options => options.KeyPrefix = "my-service",
scheduler => scheduler.RetryCounts = 3));
Перегрузка без IConfiguration — quartz.UseValkey(options => ...). Использование планировщика не отличается
от хранилища PostgreSQL (IPersistenceScheduler: Enqueue, Schedule, SchedulePeriodic, ScheduleCron).
Конфигурация
Секция ValkeyClient:Scheduler:
| Параметр | По умолчанию | Описание |
|---|---|---|
KeyPrefix |
quartz |
Префикс всех ключей. К нему добавляется имя планировщика, поэтому сервисы с разными префиксами не пересекаются |
LockTimeout |
30 с | Срок жизни кластерной блокировки. Запись устаревшего владельца отклоняется |
LockWaitTimeout |
60 с | Сколько операция ждёт блокировку до ошибки |
MisfireThreshold |
5 с | Опоздание, после которого срабатывание считается пропущенным |
MaxConsecutiveFireFailures |
5 | Сколько срабатываний подряд могут упасть до перевода триггера в Error. 0 — без ограничения |
NodeCheckInInterval |
7,5 с | Как часто узел отмечается в кластере |
NodeFailureThreshold |
30 с | Через сколько без отметки узел считается упавшим. Больше NodeCheckInInterval |
Реплики одного сервиса должны использовать один и тот же KeyPrefix и имя планировщика, разные сервисы — разные.
Как это работает
- Блокировка. Составные операции выполняются под блокировкой в Valkey (
SET NX PX, токен-владелец, освобождение Lua-скриптом). Изменения копятся в памяти и уходят одной транзакциейMULTIс условием «блокировка всё ещё моя»: если блокировка истекла и её забрал другой узел, запись отклоняется, а не портит состояние. - Расписание. Ожидающие триггеры лежат в
ZSETс оценкой «время срабатывания, затем приоритет». Захват пачки читает индекс страницами, а не целиком; удаление триггера не загружает всю его группу. - Кластер. Каждый узел отмечается каждые
NodeCheckInInterval. Узел, не отмечавшийся дольшеNodeFailureThreshold, считается упавшим: его захваченные триггеры возвращаются в ожидание, а выполнявшиеся задания сRequestRecoveryполучают триггер восстановления. - Закрепление. Триггер с
PreferredNodeдостаётся только своему узлу, пока тот жив. - Ключи. Все ключи планировщика содержат hash-tag
{префикс:имя планировщика}, поэтому Lua и транзакции работают и в Redis Cluster.
Проверка
Реализация проверена набором контрактных тестов самого Quartz (JobStoreContractTest, 113 сценариев), который запускается
и против RAMJobStore (эталон), и против этого хранилища, плюс сценарии кластера: упавший узел, закрепление, ограждение блокировки,
три узла под нагрузкой. Тесты — Stemy.Persistence.Scheduler.Valkey.Tests (нужен Docker).
Пропускная способность на одном узле (Valkey в Docker, локально, 2026-10-01): 2000 триггеров планируются за 0,2 с,
захват + срабатывание + завершение — около 350 триггеров/с (PostgreSQL ADO-хранилище в том же сценарии — около 94/с);
время не растёт с числом ожидающих триггеров. Скорость даёт опережающее чтение: операция запрашивает все нужные ключи
одним конвейером (Internal/StoreSession.Prefetch.cs), а не десятками обращений подряд.
Сравнение с Stemy.Queue (постановка и выполнение) — проект Stemy.BackgroundJobs.Benchmarks.
Ограничения
- Состояние планировщика — только в Valkey: потеря данных Valkey (без персистентности) означает потерю расписаний. Для периодических задач, регистрируемых при старте сервиса, это самовосстанавливается; для разовых — нет. Включайте AOF/RDB.
- Хранилище опирается на внутренние члены Quartz 4.3 (
TriggerDetailsUpdate,JobDataMap.Clone,MisfireInstruction), доступные через рефлексию; при обновлении Quartz на minor/major нужно прогнать контрактные тесты. - Все операции идут через одну блокировку на планировщик: масштабирование — числом реплик планировщика (разные
KeyPrefix), а не ростом нагрузки на одно хранилище.
Лицензии
Логика хранилища — порт RAMJobStore из Quartz.NET (© Marko Lahma, Apache License 2.0), контрактные тесты взяты оттуда же.
См. NOTICE.md.
No packages depend on Stemy.Persistence.Scheduler.Valkey.
.NET 10.0
- Stemy.Persistence.Scheduler (>= 2.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Options (>= 10.0.12)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.12)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.12)
- Quartz (>= 4.3.0)
- StackExchange.Redis (>= 3.3.1)
- Microsoft.EntityFrameworkCore (>= 10.0.12)
- Microsoft.Extensions.Configuration (>= 10.0.12)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.12)
- Microsoft.Extensions.DependencyInjection (>= 10.0.12)
- Microsoft.Extensions.DependencyModel (>= 10.0.12)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Logging (>= 10.0.12)
- Npgsql (>= 10.0.3)
- Remote.Linq (>= 7.3.3)
- Remote.Linq.EntityFrameworkCore (>= 7.3.3)
- Remote.Linq.Newtonsoft.Json (>= 7.3.3)
- Remote.Linq.Text.Json (>= 7.3.0)
- Scrutor (>= 7.0.0)
| Version | Downloads | Last updated |
|---|---|---|
| 1.0.0 | 0 | 10/11/2026 |