Scheduler 서비스
플러그인을 작성할 때 정해진 스케줄로 실행되거나, 백엔드 플러그인이 실행되는 인스턴스 전체에 분산되는 cron 작업과 비슷한 것을 실행하고 싶을 때가 있어요. 이를 위해 플러그인별로 범위가 지정된 태스크 스케줄러를 제공해서, 이런 태스크를 만들고 그 실행을 조율할 수 있게 해줘요.
출처: 문서
본문
플러그인을 작성할 때 정해진 스케줄로 실행되거나, 백엔드 플러그인이 실행되는 인스턴스 전체에 분산되는 cron 작업과 비슷한 것을 실행하고 싶을 때가 있어요. 이를 위해 플러그인별로 범위가 지정된 태스크 스케줄러를 제공해서, 이런 태스크를 만들고 그 실행을 조율할 수 있게 해줘요.
서비스 사용하기
다음 예시는 example 백엔드에서 scheduler 서비스를 가져와 주어진 간격으로 인스턴스 전체에서 실행되는 예약 태스크를 발행하는 방법을 보여줘요.
import {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
scheduler: coreServices.scheduler,
},
async init({ scheduler }) {
await scheduler.scheduleTask({
frequency: { minutes: 10 },
timeout: { seconds: 30 },
id: 'ping-google',
fn: async () => {
await fetch('http://google.com/ping');
},
});
},
});
},
});
REST API
스케줄러는 각 플러그인의 베이스 URL 위에 REST API를 노출하며, 이를 통해 해당 플러그인의 모든 태스크의 현재 상태를 검사하고 영향을 줄 수 있어요.
GET <pluginBaseURL>/.backstage/scheduler/v1/tasks
주어진 플러그인이 시작 시점에 등록한 모든 태스크와 그 현재 상태를 나열해요.
예를 들어 Catalog 플러그인의 모든 예약 태스크를 나열하려면:
curl 'https://<instance-name>/api/catalog/.backstage/scheduler/v1/tasks'
Backstage 데모 인스턴스에서 시도해볼 수 있어요.
curl 'https://demo.backstage.io/api/catalog/.backstage/scheduler/v1/tasks'
응답 모양은 다음과 같아요.
{
"tasks": [
{
"taskId": "InternalOpenApiDocumentationProvider:refresh",
"pluginId": "catalog",
"scope": "global",
"settings": {
"version": 2,
"cadence": "PT10S",
"initialDelayDuration": "PT10S",
"timeoutAfterDuration": "PT1M"
},
"taskState": {
"status": "idle",
"startsAt": "2025-04-11T20:35:13.418+02:00",
"lastRunEndedAt": "2025-04-11T20:35:03.453+02:00"
},
"workerState": {
"status": "initial-wait"
}
}
]
}
각 태스크는 다음과 같은 속성을 가져요.
| Field | Format | Description | |
|---|---|---|---|
taskId |
string | A unique (per plugin) ID for the task | |
pluginId |
string | The plugin where the task is scheduled | |
scope |
string | Either local (runs on each worker node with potential overlaps, similar to setInterval), or global (runs on one worker node at a time, without overlaps) |
|
settings |
object | Serialized form of the initial settings passed in when scheduling the task. The only completely fixed well known field is version; the others depend on what version is used |
|
settings.version |
string | Internal identifier of the format of the settings object. The format of this object can change completely for each version. This document describes version 2 specifically | |
settings.cadence |
string; ISO duration | How often the task runs. Either the string manual (only runs when manually triggered), or an ISO duration string starting with the letter P, or a cron format string |
|
settings.initialDelayDuration |
string; ISO duration | How long workers wait at service startup before starting to look for work, to give the service some time to stabilize, as an ISO duration string (if configured) | |
settings.timeoutAfterDuration |
string; ISO duration | How long after a task starts that it's considered timed out and available for retries | |
taskState |
object | The current state of the task (see below for details) | |
workerState |
object | The status of the worker responsible for task |
taskState 모양은 태스크가 현재 실행 중인지 여부에 따라 달라져요. 실행 중일 때:
| Field | Format | Optional | Description | |
|---|---|---|---|---|
taskState.status |
string | running |
||
taskState.startedAt |
string; ISO timestamp | When the current task run started | ||
taskState.timesOutAt |
string; ISO timestamp | When the current task run will time out if it does not finish before that | ||
taskState.lastRunError |
string; JSON serialized error | optional | When the task last ran, if it threw an error, this field contains it | |
taskState.lastRunEndedAt |
string; ISO timestamp | optional | When the task last ran, it ended at this time |
태스크가 유휴 상태일 때:
| Field | Format | Optional | Description | |
|---|---|---|---|---|
taskState.status |
string | idle |
||
taskState.startsAt |
string; ISO timestamp | optional | When the task is scheduled to run next; will not be set if the task uses manual scheduling | |
taskState.lastRunError |
string; JSON serialized error | optional | When the task last ran, if it threw an error, this field contains it | |
taskState.lastRunEndedAt |
string; ISO timestamp | optional | When the task last ran, it ended at this time |
workerState 모양은 다음과 같아요.
| Field | Description | |
|---|---|---|
workerState.status |
The status of the worker responsible for task; either initial-wait (right at service startup), running (task is currently running), or idle (task is not running at the moment) |
POST <pluginBaseURL>/.backstage/scheduler/v1/tasks/<taskId>/trigger
다음 예약된 시간 슬롯을 기다리지 않고, 주어진 태스크 ID를 즉시 실행하도록 예약해요.
예를 들어 특정 Catalog 태스크를 트리거하려면:
curl -X POST "https://<instance-name>/api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider:refresh/trigger"
작동하는 예시는 다음과 같아요.
curl -X POST "https://demo.backstage.io/api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider:refresh/trigger"
워커가 태스크가 완료되어 실제로 이를 수행할 시점임을 발견하기까지 추가로 작은 지연이 있을 수 있다는 점에 주의하세요. 이는 보통 1초 미만이지만 달라질 수 있어요.
요청에는 본문이 없어요.
응답은 다음과 같아요.
- 성공하면
200 OK - 이 플러그인에 그런 등록된 태스크가 없으면
404 Not Found - 태스크가 이미 실행 중 상태였다면
409 Conflict
POST <pluginBaseURL>/.backstage/scheduler/v1/tasks/<taskId>/cancel
주어진 태스크 ID로 실행 중인 태스크를 취소해요.
<taskId>는 URL에서 단일 경로 세그먼트로 유지되도록 URL 인코딩되어야 한다는 점에 주의하세요(예: JavaScript에서 encodeURIComponent를 사용하거나 표준 퍼센트 인코딩).
예를 들어 특정 Catalog 태스크를 취소하려면:
curl -X POST "https://<instance-name>/api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider%3Arefresh/cancel"
작동하는 예시는 다음과 같아요.
curl -X POST "https://demo.backstage.io/api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider%3Arefresh/cancel"
워커가 태스크가 취소되었음을 발견하기까지 추가로 작은 지연이 있을 수 있다는 점에 주의하세요. 이는 몇 초까지 걸릴 수 있어요. 또한 태스크에 전달된 abort 신호에 제대로 반응하는 것은 태스크 구현의 몫이라는 점도 유의하세요.
요청에는 본문이 없어요.
응답은 다음과 같아요.
- 성공하면
200 OK - 이 플러그인에 그런 등록된 태스크가 없으면
404 Not Found - 태스크가 실행 중 상태가 아니었다면
409 Conflict
테스트
@backstage/backend-test-utils 패키지는 scheduler 서비스의 모의 구현을 제공하는 mockServices.scheduler를 제공해요. 이 모의 구현은 기본적으로 startTestBackend에서 사용되며, 수동 실행이나 초기 지연으로 구성되지 않은 경우 시작 시점에 등록된 태스크를 즉시 실행해요.
테스트 중 더 많은 제어를 위해 전용 인스턴스를 사용할 수도 있어요.
it('should trigger a task', async () => {
const scheduler = mockServices.scheduler();
const { server } = await startTestBackend({
features: [scheduler.factory()],
});
await scheduler.triggerTask('some-task-id');
// Next verify that the plugin state is updated accordingly
// e.g. by calling the API or verifying database state
});