コンテンツにスキップ
コンソール →
公式サイト →
AI に聞くなら、この URL を貼る https://keelson.dev/ja/llms.txt

定期実行ジョブ

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

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

毎朝 9 時に売上を集計する定期実行ジョブを追加してください。実行結果をログで確認できるようにしてください。

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

slug: my-app
runtime: python-slim
command: "python app.py"
db:
mode: libsql
crons:
- name: daily-report
schedule: "0 9 * * *" # 毎日 9:00
command: "python report.py"
timeout: 120 # 1 回の実行を最大 120 秒にする
項目意味
nameジョブの名前。小文字英数字とハイフンで指定します
schedule実行する時刻・間隔を表す cron 式
command1 回分の処理を実行し、終了するコマンド
timeout1 回の実行時間の上限(秒)。上限に達すると停止します
enabledfalse にすると定期実行を止めます。省略時は true です

nameschedulecommand は必須です。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 まで記録されます。超えた分は省略されます。

実際に開始した実行を数えます。成功・失敗・「今すぐ実行」は含み、スキップされた回は含みません。プランの上限に達すると当月の残りの実行はスキップされ、翌月にリセットされます。

本数・実行時間・月間実行回数の上限はプランと制限を参照してください。

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

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

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

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

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

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

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

たとえば、日付が一意の reports テーブルに集計結果を保存するなら、同じ日付のデータがあれば更新します。

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

受け付けた処理を後で実行する

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 回あたりの呼び出し回数を減らしてください。

salesreports テーブルが作成済みの例です。集計対象の日付はアプリ側で決めます。スケジュールのタイムゾーンと、コードで日付を求める際のタイムゾーンは区別してください。

report.py
import os, datetime, libsql
conn = libsql.connect(
database=os.environ["KEELSON_DB_URL"],
auth_token=os.environ["KEELSON_DB_AUTH_TOKEN"],
)
today = datetime.date.today().isoformat()
row = conn.execute(
"SELECT COUNT(*), COALESCE(SUM(amount), 0) FROM sales WHERE date = ?", (today,)
).fetchone()
conn.execute(
"INSERT INTO reports (date, count, total) VALUES (?, ?, ?) "
"ON CONFLICT(date) DO UPDATE SET count = excluded.count, total = excluded.total",
(today, row[0], row[1]),
)
conn.commit()
print(f"集計完了: {today} {row[0]} 件 / {row[1]}")
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 秒を超える値、またはプラン上限を超える値を指定した上限以下にし、必要なら処理を分割する
スケジュールがプランの最小間隔より短い間隔を広げる
ジョブの本数がプラン上限を超えている処理をまとめるか、不要なジョブを削除する
type: webcrons を併用している定期実行だけなら type を外す。静的サイトに処理を追加する場合は、サーバーを持つ構成への変更も確認する
廃止された workers: が残っている削除し、処理に応じて crons かリクエスト処理に変更する
  • イベント発生後、すぐに開始する必要がある処理。定期実行では次の予定時刻まで待ちます。
  • 分割できず、1 回の実行時間の上限を超える処理。
  • ジョブの完了を待って別のジョブを開始するなどの、ワークフロー制御。