コンテンツにスキップ
コンソール →
公式サイト →
URL を貼り付けて AI に質問 https://keelson.dev/ja/llms.txt

定期実行ジョブ

毎朝のレポート作成や、1 時間ごとのデータ同期など、決まった時刻・間隔で処理を動かせます。この処理を「定期実行ジョブ」と呼びます。Web アプリへのアクセスがなくても実行されます。

通常は AI エージェントに、実行したい処理とスケジュールを伝えると、keelson.yaml の crons に設定します。

ワークスペースのタイムゾーンで毎朝 9 時に、前日分の売上を集計する定期実行ジョブを追加してください。実行結果をログで確認できるようにしてください。

Web アプリに、毎日 9 時に report.py を実行するジョブを追加した例です。

slug: my-app
runtime: python-slim
command: "python app.py"
db:
mode: libsql
crons:
- name: daily-report
description: "前日の売上レポートをメールで送る"
schedule: "0 9 * * *" # 毎日 9:00
command: "python report.py"
timeout: 120 # 1 回の実行を最大 120 秒にする
項目意味
nameジョブの名前。小文字英数字とハイフンで指定します
schedule実行する時刻・間隔を表す cron 式
command1 回分の処理を実行し、終了するコマンド
timeout1 回の実行時間の上限(秒)。上限に達すると停止します
enabledfalse にすると定期実行を止めます。省略時は true です
description任意。ジョブが何をするかの 1 行説明。コンソールの定期実行タブの「説明」列に表示されます。前後の空白は除かれ、空なら未指定と同じです。最大 100 文字。表示専用で、実行内容には影響しません

name、schedule、command は必須です。timeout は 1〜600 秒の範囲で、プランの上限以下を指定してください。省略時は 300 秒ですが、プランの上限がそれより短ければ、その上限が適用されます。上限を超える値を明示すると、デプロイは拒否されます。

Web アプリなしで、定期実行だけのアプリも作れます。その場合はトップレベルの command を省略します。定期実行だけのアプリは「同時に使えるアプリ数」の枠を消費しません。

schedule には、時刻や間隔を 5 つの項目で表す「cron 式」を書きます。

やりたいことcron 式
毎日 9:000 9 * * *
平日 9:000 9 * * 1-5
毎時 0 分0 * * * *
15 分ごと(Plus / Team)*/15 * * * *
5 分ごと(Team)*/5 * * * *
毎月 1 日 0:000 0 1 * *

スケジュールはワークスペースのタイムゾーンで評価されます。タイムゾーンはワークスペース作成時にブラウザから設定され、後から変更できません。実行開始は予定時刻より遅れることがあります。

最小間隔は Starter 60 分 / Plus 15 分 / Team 5 分です。それより短いスケジュールはデプロイ時に拒否されます。毎分の実行には対応していません。

cron 式の各項目
┌───────────── 分(0–59)
│ ┌─────────── 時(0–23)
│ │ ┌───────── 日(1–31)
│ │ │ ┌─────── 月(1–12)
│ │ │ │ ┌───── 曜日(0–6、0=日曜)
* * * * *

* はすべての値を表します。たとえば 0 9 * * * は「毎月・毎日・すべての曜日の 9 時 0 分」です。

コンソールのアプリ画面で、ジョブごとの実行履歴、成功・失敗、ログを確認できます。「今すぐ実行」で、次の予定時刻を待たずに動作を確認することもできます。

失敗した場合はログを確認し、必要なシークレットや設定値がそろっているか、timeout 内に処理が終わるかを調べます。ログを AI エージェントに渡して、原因の調査と修正を依頼できます。

標準出力と標準エラーのログは、1 回の実行につきそれぞれ 256 KiB まで記録されます。超えた分は省略されます。

実際に開始した実行を数えます。成功・失敗・「今すぐ実行」は含み、スキップされた回は含みません。合計実行時間は、外部 API の待ち時間などを含む経過時間で数えます。platform 起因で実行時間を確定できなかった場合、合計実行時間には数えませんが、実行回数は 1 回消費します。どちらかの月間上限に達すると当月の残りの実行はスキップされます。すでに開始した実行は完了まで継続し、上限は翌月にリセットされます。

最大本数を最短間隔で動かすと、月間上限には収まりません。本数・最短間隔・1 回の実行時間・月間実行回数・合計実行時間は、それぞれ独立した上限です。

プランごとの上限はプランと制限を参照してください。

定期実行ジョブは、1 回分の処理を行って終了します。アプリを動かし続けるための機能ではありません。

項目動作
Web アプリとの関係別の実行環境で動きます。メモリやローカルファイルは共有しません
データの保存実行環境内のファイルは次回に引き継がれません。残したいデータは Managed SQLite や Files / Media SDK に保存します
前回の実行が終わっていない場合同じジョブの次の実行をスキップします。後で実行するための予約は残りません
実行に失敗した場合自動ですぐに再試行はしません。次の予定時刻には通常どおり実行します
実行時間の上限に達した場合処理の途中でも停止します

アプリ内のタイマーやスケジューラは、アプリが停止している間には動作しません。決まった時刻の処理には crons を使ってください。レスポンスを返した後のバックグラウンド処理も、完了が保証されません。リクエスト内で完了させるか、レスポンスの後に続ける処理はバックグラウンドタスクに投入します。

詳しくはアプリが動くタイミングを参照してください。

再実行してもデータが重複しないようにする

Section titled “再実行してもデータが重複しないようにする”

途中で停止した処理を次回やり直したり、「今すぐ実行」で同じ処理を繰り返したりしても、登録や送信が重複しないようにします。この性質を「冪等性」と呼びます。

たとえば、reports.date に主キーまたは UNIQUE 制約があるテーブルなら、同じ日付のデータがあれば更新できます。

conn.execute("""
INSERT INTO reports (date, total) VALUES (?, ?)
ON CONFLICT(date) DO UPDATE SET total = excluded.total
""", (today, total))
conn.commit()

タスクを使わずに受け付けた処理を後で実行する

Section titled “タスクを使わずに受け付けた処理を後で実行する”

受け付けた処理をリクエストの後すぐに実行するなら、まずバックグラウンドタスクを使ってください。タスクを使わずに組む場合や、次の実行までの待ち時間を許せる場合は、次の方法を使います。

Web アプリで受け付けた処理を後でまとめて実行する場合は、未処理の内容を Managed SQLite に保存します。定期実行ジョブが未処理のデータを読み取り、処理してから完了を記録します。

# Team プランで 5 分ごとに実行する例
crons:
- name: process-pending
schedule: "*/5 * * * *"
command: "python process_pending.py"
timeout: 120
  • 開始までの待ち時間を考慮します。 次のジョブ実行まで待つため、すぐに開始する必要がある用途には適していません。利用者にも待ち時間を案内してください。
  • 同じ処理が繰り返される場合に備えます。 メール送信などが成功しても、完了を DB に記録する前にジョブが止まることがあります。
  • 失敗の回数と理由を記録します。 再試行の上限を設け、繰り返し失敗するデータをどう扱うか決めてください。Keelson が個々の未処理データを自動で再試行・隔離するわけではありません。

リクエスト内で完了できる短い処理は、レスポンスを返す前に済ませてください。

大量のデータを処理するときは、1 回の件数に上限を設け、残りは次回に処理します。実行時間の上限を超える処理は、複数回に分けてください。

外部 API を呼ぶ場合は、相手のサービスが定める呼び出し回数の制限にも合わせます。間隔を広げるか、1 回あたりの呼び出し回数を減らしてください。

sales と reports テーブルが作成済みで、reports.date に主キーまたは UNIQUE 制約がある例です。依存パッケージに libsql を追加します。

この例では、日本時間で前日分を集計します。sales.date には同じタイムゾーンで求めた日付を YYYY-MM-DD 形式で保存している前提です。Asia/Tokyo は集計に使うタイムゾーンに合わせて変更してください。ワークスペースのスケジュール設定が、コードで日付を求める際のタイムゾーンを自動で変更するわけではありません。

report.py
import os
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import libsql
conn = libsql.connect(
database=os.environ["KEELSON_DB_URL"],
auth_token=os.environ["KEELSON_DB_AUTH_TOKEN"],
)
report_date = (datetime.now(ZoneInfo("Asia/Tokyo")).date() - timedelta(days=1)).isoformat()
row = conn.execute(
"SELECT COUNT(*), COALESCE(SUM(amount), 0) FROM sales WHERE date = ?", (report_date,)
).fetchone()
conn.execute(
"INSERT INTO reports (date, count, total) VALUES (?, ?, ?) "
"ON CONFLICT(date) DO UPDATE SET count = excluded.count, total = excluded.total",
(report_date, row[0], row[1]),
)
conn.commit()
print(f"集計完了: {report_date} {row[0]} 件 / {row[1]}")

依存パッケージに @libsql/client を追加し、SYNC_API_URL を環境変数に設定します。synced_items テーブルが作成済みで、id に主キーまたは UNIQUE 制約があり、同期先の API が id を持つオブジェクトの配列を返す例です。

sync.js
import { createClient } from "@libsql/client";
const db = createClient({
url: process.env.KEELSON_DB_URL,
authToken: process.env.KEELSON_DB_AUTH_TOKEN,
});
const res = await fetch(process.env.SYNC_API_URL);
if (!res.ok) throw new Error(`同期先 API のエラー: ${res.status}`);
const items = await res.json();
for (const item of items) {
await db.execute({
sql: `INSERT INTO synced_items (id, data) VALUES (?, ?)
ON CONFLICT(id) DO UPDATE SET data = excluded.data`,
args: [item.id, JSON.stringify(item)],
});
}
console.log(`同期完了: ${items.length} 件`);

通知処理を notify.py に実装した場合の設定例です。Webhook URL はシークレットで渡します。

slug: daily-notifier
runtime: python-slim
db:
mode: none
secrets:
items:
- name: SLACK_WEBHOOK_URL
description: "通知先の Slack Incoming Webhook"
required:
- all_of: [SLACK_WEBHOOK_URL]
crons:
- name: daily-notify
schedule: "0 9 * * 1-5"
command: "python notify.py"
timeout: 30
状況対処
timeout に 600 秒を超える値、またはプラン上限を超える値を指定した上限以下にし、必要なら処理を分割する
スケジュールがプランの最小間隔より短い間隔を広げる
ジョブの本数がプラン上限を超えている処理をまとめるか、不要なジョブを削除する
description が 100 文字を超えている(cron_description_too_long)100 文字以内の 1 行に縮める。自動では切り詰めない
type: web と crons を併用している定期実行だけなら type を外す。静的サイトに処理を追加する場合は、サーバーを持つ構成への変更も確認する
廃止された workers: が残っている削除し、処理に応じて crons かリクエスト処理に変更する
  • イベント発生後、すぐに開始する必要がある処理。定期実行では次の予定時刻まで待ちます。バックグラウンドタスクを使ってください。
  • 分割できず、1 回の実行時間の上限を超える処理。
  • ジョブの完了を待って別のジョブを開始するなどの、ワークフロー制御。