Kora фреймворк для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный разработчиками Т-Банк / Тинькофф

Kora is a framework for writing Java / Kotlin applications with a focus on performance, efficiency, transparency made by T-Bank / Tinkoff developers

Skip to content

Scheduling

A module for creating declarative-style planners using annotations.

When applying aspects, class must not be final

When applying aspects, class must be open

Native

Creating a scheduler using the standard ScheduledExecutorService that comes with the JVM.

In order to create a scheduler via aspects, special annotations are used that essentially duplicate the ScheduledExecutorService method signatures. The parameters of the annotations correspond to the parameters of the methods scheduleAtFixedRate, schedule, scheduleWithFixedDelay respectively.

Also all annotations have the config argument, if it is present, the parameter values will be taken from the configuration on the specified path.

Dependency

Dependency build.gradle:

annotationProcessor "ru.tinkoff.kora:annotation-processors"
implementation "ru.tinkoff.kora:scheduling-jdk"

Module:

@KoraApp
public interface Application extends SchedulingJdkModule { }

Dependency build.gradle.kts:

ksp("ru.tinkoff.kora:symbol-processors")
implementation("ru.tinkoff.kora:scheduling-jdk")

Module:

@KoraApp
interface Application : SchedulingJdkModule

Configuration

Example of the complete configuration described in the ScheduledExecutorServiceConfig class (default values are specified):

scheduling {
    threads = 20 //(1)!
    telemetry {
        logging {
            enabled = false //(2)!
        }
        metrics {
            enabled = true //(3)!
            slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(4)!
        }
        tracing {
            enabled = true //(5)!
        }
    }
}
  1. Maximum number of threads in ScheduledExecutorService
  2. Enables module logging (default false)
  3. Enables module metrics (default true)
  4. Configuring SLO for DistributionSummary metrics
  5. Enables module tracing (default true)
scheduling:
  threads: 20 #(1)!
  telemetry:
    logging:
      enabled: false #(2)!
    metrics:
      enabled: true #(3)!
      slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(4)!
    telemetry:
      enabled: true #(5)!
  1. Maximum number of threads in ScheduledExecutorService
  2. Enables module logging (default false)
  3. Enables module metrics (default true)
  4. Configuring SLO for DistributionSummary metrics
  5. Enables module tracing (default true)

Fixed interval

Scheduling with startup at fixed equal intervals. Actual interval time depends on task completion time, can start new tasks even if the last task is still running.

@Component
public class SomeService {

    @ScheduleAtFixedRate(initialDelay = 50, period = 50, unit = ChronoUnit.MILLIS)
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleAtFixedRate(initialDelay = 50, period = 50, unit = ChronoUnit.MILLIS)
    fun schedule() {
        // do something
    }
}

Configuration

It is possible to transfer parameters via configuration, it has priority over the parameters specified in the annotation:

@Component
public class SomeService {

    @ScheduleAtFixedRate(config = "job")
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleAtFixedRate(config = "job")
    fun schedule() {
        // do something
    }
}

SomeService of configuration via a config file:

job {
    initialDelay = "50ms" //(1)!
    period = "50ms" //(2)!
}
  1. Initial delay interval before the first task
  2. Intermittent interval between tasks
job:
  initialDelay: "50ms" #(1)!
  period: "50ms" #(2)!
  1. Initial delay interval before the first task
  2. Intermittent interval between tasks

Fixed delay

Waits a fixed amount of time from the end of the previous task execution before starting the next execution. It does not matter how long the current execution takes, the next execution is started exactly after the previous task is finished and the specified waiting interval between tasks.

@Component
public class SomeService {

    @ScheduleWithFixedDelay(initialDelay = 50, delay = 50, unit = ChronoUnit.MILLIS)
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleWithFixedDelay(initialDelay = 50, delay = 50, unit = ChronoUnit.MILLIS)
    fun schedule() {
        // do something
    }
}

Configuration

It is possible to transfer parameters via configuration, it has priority over the parameters specified in the annotation:

@Component
public class SomeService {

    @ScheduleWithFixedDelay(config = "job")
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleWithFixedDelay(config = "job")
    fun schedule() {
        // do something
    }
}

SomeService of configuration via a config file:

job {
    initialDelay = "50ms" //(1)!
    delay = "50ms" //(2)!
}
  1. Initial delay interval before the first task
  2. Intermittent delay interval between tasks
job:
  initialDelay: "50ms" #(1)!
  delay: "50ms" #(2)!
  1. Initial delay interval before the first task
  2. Intermittent delay interval between tasks

Once

Runs a single task at a certain fixed time interval.

@Component
public class SomeService {

    @ScheduleOnce(delay = 50, unit = ChronoUnit.MILLIS)
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleOnce(delay = 50, unit = ChronoUnit.MILLIS)
    fun schedule() {
        // do something
    }
}

Configuration

It is possible to transfer parameters via configuration, it has priority over the parameters specified in the annotation:

@Component
public class SomeService {

    @ScheduleOnce(config = "job")
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleOnce(config = "job")
    fun schedule() {
        // do something
    }
}

SomeService of configuration via a config file:

job {
    delay = "50ms" //(1)!
}
  1. Initial delay interval before the task
job:
  delay: "50ms" #(1)!
  1. Initial delay interval before the task

Graceful Shutdown

If you want to pre-terminate processing on a scheduled service termination without waiting for the service to end, you need to check Thread.currentThread().isInterrupted() status and terminate the service yourself.

Quartz

A library-based implementation of Quartz as a scheduler for creating aspects.

Dependency

Dependency build.gradle:

annotationProcessor "ru.tinkoff.kora:annotation-processors"
implementation "ru.tinkoff.kora:scheduling-quartz"

Module:

@KoraApp
public interface Application extends QuartzModule { }

Dependency build.gradle.kts:

ksp("ru.tinkoff.kora:symbol-processors")
implementation("ru.tinkoff.kora:scheduling-quartz")

Module:

@KoraApp
interface Application : QuartzModule

Configuration

Configuration is specified as Properties values in key and value format:

quartz {
    "org.quartz.threadPool.threadCount": "10"
}
scheduling {
    telemetry {
        logging {
            enabled = false //(1)!
        }
        metrics {
            enabled = true //(2)!
            slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(3)!
        }
        tracing {
            enabled = true //(4)!
        }
    }
}
  1. Enables module logging (default false)
  2. Enables module metrics (default true)
  3. Configuring SLO for DistributionSummary metrics
  4. Enables module tracing (default true)
quartz:
  org.quartz.threadPool.threadCount: "10"
scheduling:
  telemetry:
    logging:
      enabled: false #(1)!
    metrics:
      enabled: true #(2)!
      slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(3)!
    telemetry:
      enabled: true #(4)!
  1. Enables module logging (default false)
  2. Enables module metrics (default true)
  3. Configuring SLO for DistributionSummary metrics
  4. Enables module tracing (default true)

Default settings are used from:

quartz.properties
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

Cron

Usage Cron expressions to run scheduled tasks.

Starts a single task at a certain fixed time interval.

@Component
public class SomeService {

    @ScheduleWithCron("0 0 * * * * ?") //(1)!
    void schedule() {
        // do something
    }
}
  1. Cron expression saying to run a task every hour and every day
@Component
class SomeService {

    @ScheduleWithCron("0 0 * * * * ?") //(1)!
    fun schedule() {
        // do something
    }
}
  1. Cron expression saying to run a task every hour and every day

Configuration

It is possible to transfer parameters via configuration, it has priority over the parameters specified in the annotation:

@Component
public class SomeService {

    @ScheduleWithCron(config = "job")
    void schedule() {
        // do something
    }
}
@Component
class SomeService {

    @ScheduleWithCron(config = "job")
    fun schedule() {
        // do something
    }
}

Configuration example:

job {
    cron = "0 0 * * * * ?" //(1)!
}
  1. Cron expression saying to run a task every hour and every day
job:
  cron: "0 0 * * * * ?" #(1)!
  1. Cron expression saying to run a task every hour and every day

Trigger

This involves creating your custom trigger based on the Quartz library and registering it in the application dependency container with a specific tag and then using it via annotation.

@KoraApp
public interface Application extends QuartzModule {

    @Tag(SomeService.class) //(1)!
    default Trigger myTrigger() {
        return TriggerBuilder.newTrigger()
                .withIdentity("myTrigger")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMilliseconds(50)
                        .repeatForever())
                .build();
    }
}

@Component
public class SomeService {

    @ScheduleWithTrigger(@Tag(SomeService.class)) //(2)!
    void schedule() {
        // do something
    }
}
  1. Trigger tag
  2. Trigger tag
@KoraApp
interface Application : QuartzModule {

    @Tag(SomeService::class) //(1)!
    fun myTrigger(): Trigger {
        return TriggerBuilder.newTrigger()
            .withIdentity("myTrigger")
            .startNow()
            .withSchedule(
                SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInMilliseconds(50)
                    .repeatForever()
            )
        .build()
    }
}

@Component
class SomeService {

    @ScheduleWithTrigger(@Tag(SomeService.class)) //(2)!
    fun schedule() {
        // do something
    }
}
  1. Trigger tag
  2. Trigger tag