Skip to content

Последовательное выполнение

Jobby предоставляет два способа определять набор задач, которые будут выполняться строго последовательно:

  • Создание цепочки задач через JobsSequenceBuilder.
  • Определение для задач параметра SerializableGroupId для объединения задач в группы, задачи которых выполняются строго последовательно.

JobsSequenceBuilder

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

csharp
var sequenceBuilder = jobbyClient.Factory.CreateSequenceBuilder();

// Задачи будут выполнены в строго указанном ниже порядке
sequenceBuilder.Add(jobbyClient.Factory
    .Create(new SendEmailCommand { Email = "first@email.com" }));

sequenceBuilder.Add(jobbyClient.Factory
    .Create(new SendEmailCommand { Email = "second@email.com" }));

var jobs = sequenceBuilder.GetJobs();

await jobbyClient.EnqueueBatchAsync(jobs);

Задачи созданные через JobsSequenceBuilder гарантированно будут выполнены в указанном порядке даже если они были созданы в разных очередях.

В случае провала одной из задач последующие запущены не будут.

Группы последовательного выполнения

Jobby позволяет при создании задачи указать идентификатор группы в параметре SerializableGroupId. Задачи, имеющие один и тот же идентификатор, Jobby будет выполнять строго по одной в порядке возрастания запланированного времени запуска. Jobby гарантирует, что из каждой группы будет одновременно выполняться максимум по одной задаче.

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

Данный способ гарантирует, что задачи одной группы будут выполнены строго по одной, но порядок их запуска по возрастанию запланированного времени запуска гарантируется только если все задачи группы создаются в одной очереди.

Идентификатор группы для задачи

Параметр SerializableGroupId можно определить на уровне команды, реализовав интерфейс IHasDefaultJobOptions:

csharp
class UpdateOrderCommand : IJobCommand, IHasDefaultJobOptions
{
    public static string GetJobName() => "UpdateOrderJob";

    public required string OrderId { get; init; }

    // Задачи для одинаковых OrderId
    // будут выполняться последовательно
    public JobOpts GetOptionsForEnqueuedJob()
    {
        return new JobOpts
        {
            SerializableGroupId = OrderId
        }
    }

    public RecurrentJobOpts GetOptionsForRecurrentJob()
    {
        return default;
    }
}

Или SerializableGroupId можно указать непосредственно при создании конкретного экземпляра задачи:

csharp
await jobbyClient.EnqueueCommandAsync(command, new JobOpts
{
    SerializableGroupId = command.OrderId
});

Аналогично идентификатор группы можно определять и для задач по расписанию.

Отключение функционала групп последовательного выполнения

Если вы не планируете заполнять в ваших задачах SerializableGroupId, то поддержку данного функционала можно отключить с целью повышения производительности библиотеки (но это не является обязательным):

csharp
builder.Services.AddJobbyServerAndClient(jobbyBuilder =>
{    
    jobbyBuilder.ConfigureJobby((sp, jobby) =>
    {
        jobby
            .UseServerSettings(new JobbyServerSettings
            {
                // Отключить группы последовательного выполнения
                DisableSerializableGroups = true
            });
    });
});

Флаг DisableSerializableGroups можно так же определить на уровне отдельной очереди. Например группы последовательного выполнения можно отключить глобально и включить только для одной очереди, в которой планируется создание задач с заполненным SerializableGroupId:

csharp
builder.Services.AddJobbyServerAndClient(jobbyBuilder =>
{    
    jobbyBuilder.ConfigureJobby((sp, jobby) =>
    {
        jobby
            .UseServerSettings(new JobbyServerSettings
            {
                // Отключить группы последовательного выполнения глобально
                DisableSerializableGroups = true,
                Queues = [
                    new()
                    {
                        QueueName = "for_groups",

                        // Но включить для отдельной очереди
                        DisableSerializableGroups = false,
                    }
                ]
            });
    });
});

Манипуляции с флагом DisableSerializableGroups не обязательны, но позволят добиться более оптимального режима работы Jobby в случае, если вы планируете использовать SerializableGroupId не для всех задач.

Блокировка группы при ошибке

По умолчанию группа не блокируется, если одна из её задач была провалена. После провала всех попыток задача перейдёт в статус Failed и будет запущена следующая задача из группы.

Это поведение можно изменить для обычных не рекуррентных задач: в случае провала всех попыток группа останется заблокированной и никакие другие задачи из группы запускаться не будут. Для этого необходимо установить у задачи параметр JobOpts.LockGroupIfFailed=true:

csharp
class UpdateOrderCommand : IJobCommand, IHasDefaultJobOptions
{
    public static string GetJobName() => "UpdateOrderJob";

    public required string OrderId { get; init; }

    public JobOpts GetOptionsForEnqueuedJob()
    {
        return new JobOpts
        {
            SerializableGroupId = OrderId,

            // Если задача будет провалена
            // другие задачи из группы не запустятся
            LockGroupIfFailed = true,
        }
    }

    public RecurrentJobOpts GetOptionsForRecurrentJob()
    {
        return default;
    }
}

// Или непосредственно при создании экземпляра задачи
await jobbyClient.EnqueueCommandAsync(command, new JobOpts
{
    SerializableGroupId = command.OrderId,
    LockGroupIfFailed = true,
});

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

Заморозка и разморозка заблокированных групп

Jobby имеет встроенную возможность по автоматическому обнаружению групп, заблокированных упавшими задачами. В случае обнаружения большого количества готов к запуску задач в таких заблокированных группах, Jobby будет переводить их в статус Frozen чтобы они не оказывали негативного эффекта на производительность процесса запуска задач.

В будущих версиях будет добавлен UI, на котором можно будет увидеть список заблокированных групп и, при необходимости, запустить процесс разблокировки и разморозки задач.

До появления UI данные операции при необходимости можно сделать через SQL-запросы.

sql
-- Получить id групп заблокированных упавшими задачами
SELECT serializable_group_id FROM jobby_jobs as locker
WHERE 
locker.is_group_locker = true
AND (
    locker.status = 4
    OR (
        locker.status = 2
        AND locker.can_be_restarted = false
        AND NOT EXISTS (
            SELECT 1 FROM jobby_servers as s
            WHERE s.id = locker.server_id
        )
    )
);

-- Запустить разблокировку и разморозку для группы
INSERT INTO jobby_unlocking_groups (group_id, created_id)
VALUES (<id группы для разблокировки>, now());