定期実行ジョブ
毎朝のレポート作成や、1 時間ごとのデータ同期など、決まった時刻・間隔で処理を動かせます。この処理を「定期実行ジョブ」と呼びます。Web アプリへのアクセスがなくても実行されます。
通常は AI エージェントに、実行したい処理とスケジュールを伝えると、keelson.yaml の crons に設定します。
毎朝 9 時に売上を集計する定期実行ジョブを追加してください。実行結果をログで確認できるようにしてください。
Web アプリに、毎日 9 時に report.py を実行するジョブを追加した例です。
slug: my-appruntime: python-slimcommand: "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 式 |
command | 1 回分の処理を実行し、終了するコマンド |
timeout | 1 回の実行時間の上限(秒)。上限に達すると停止します |
enabled | false にすると定期実行を止めます。省略時は true です |
name、schedule、command は必須です。timeout は 1〜600 秒の範囲で、プランの上限以下を指定してください。省略時は 300 秒ですが、プランの上限がそれより短ければ、その上限が適用されます。上限を超える値を明示すると、デプロイは拒否されます。
Web アプリなしで、定期実行だけのアプリも作れます。その場合はトップレベルの command を省略します。定期実行だけのアプリは「同時に使えるアプリ数」の枠を消費しません。
スケジュールの書き方
Section titled “スケジュールの書き方”schedule には、時刻や間隔を 5 つの項目で表す「cron 式」を書きます。
| やりたいこと | cron 式 |
|---|---|
| 毎日 9:00 | 0 9 * * * |
| 平日 9:00 | 0 9 * * 1-5 |
| 毎時 0 分 | 0 * * * * |
| 15 分ごと(Plus / Team) | */15 * * * * |
| 5 分ごと(Team) | */5 * * * * |
| 毎月 1 日 0:00 | 0 0 1 * * |
スケジュールはワークスペースのタイムゾーンで評価されます。タイムゾーンはワークスペース作成時にブラウザから設定され、後から変更できません。実行開始は予定時刻より遅れることがあります。
最小間隔は Starter 60 分 / Plus 15 分 / Team 5 分です。それより短いスケジュールはデプロイ時に拒否されます。毎分の実行には対応していません。
cron 式の各項目
┌───────────── 分(0–59)│ ┌─────────── 時(0–23)│ │ ┌───────── 日(1–31)│ │ │ ┌─────── 月(1–12)│ │ │ │ ┌───── 曜日(0–6、0=日曜)* * * * ** はすべての値を表します。たとえば 0 9 * * * は「毎月・毎日・すべての曜日の 9 時 0 分」です。
実行結果とログ
Section titled “実行結果とログ”コンソールのアプリ画面で、ジョブごとの実行履歴、成功・失敗、ログを確認できます。「今すぐ実行」で、次の予定時刻を待たずに動作を確認することもできます。
失敗した場合はログを確認し、必要なシークレットや設定値がそろっているか、timeout 内に処理が終わるかを調べます。ログを AI エージェントに渡して、原因の調査と修正を依頼できます。
標準出力・標準エラーのログは、1 回あたり 256 KiB まで記録されます。超えた分は省略されます。
月間実行回数
Section titled “月間実行回数”実際に開始した実行を数えます。成功・失敗・「今すぐ実行」は含み、スキップされた回は含みません。プランの上限に達すると当月の残りの実行はスキップされ、翌月にリセットされます。
本数・実行時間・月間実行回数の上限はプランと制限を参照してください。
定期実行ジョブは、1 回分の処理を行って終了します。アプリを動かし続けるための機能ではありません。
| 項目 | 動作 |
|---|---|
| Web アプリとの関係 | 別の実行環境で動きます。メモリやローカルファイルは共有しません |
| データの保存 | 実行環境内のファイルは次回に引き継がれません。残したいデータは Managed SQLite や Files / Media SDK に保存します |
| 前回の実行が終わっていない場合 | 同じジョブの次の実行をスキップします。後で実行するための予約は残りません |
| 実行に失敗した場合 | 自動ですぐに再試行はしません。次の予定時刻には通常どおり実行します |
| 実行時間の上限に達した場合 | 処理の途中でも停止します |
アプリ内のタイマーやスケジューラは、アプリが停止している間には動作しません。決まった時刻の処理には crons を使ってください。レスポンスを返した後のバックグラウンド処理も、完了が保証されません。リクエスト内で完了させるか、後述の方法で定期実行に移します。
詳しくはアプリが動くタイミングを参照してください。
設計のルール
Section titled “設計のルール”再実行してもデータが重複しないようにする
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 回で処理する量を決める
Section titled “1 回で処理する量を決める”大量のデータを処理するときは、1 回の件数に上限を設け、残りは次回に処理します。実行時間の上限を超える処理は、複数回に分けてください。
外部 API を呼ぶ場合は、相手のサービスが定める呼び出し回数の制限にも合わせます。間隔を広げるか、1 回あたりの呼び出し回数を減らしてください。
毎朝の集計(Python)
Section titled “毎朝の集計(Python)”sales と reports テーブルが作成済みの例です。集計対象の日付はアプリ側で決めます。スケジュールのタイムゾーンと、コードで日付を求める際のタイムゾーンは区別してください。
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]}")外部 API との同期(Node.js)
Section titled “外部 API との同期(Node.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} 件`);Slack 通知
Section titled “Slack 通知”通知処理を notify.py に実装した場合の設定例です。Webhook URL はシークレットで渡します。
slug: daily-notifierruntime: python-slimdb: mode: nonesecrets: 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デプロイが拒否されたとき
Section titled “デプロイが拒否されたとき”| 状況 | 対処 |
|---|---|
timeout に 600 秒を超える値、またはプラン上限を超える値を指定した | 上限以下にし、必要なら処理を分割する |
| スケジュールがプランの最小間隔より短い | 間隔を広げる |
| ジョブの本数がプラン上限を超えている | 処理をまとめるか、不要なジョブを削除する |
type: web と crons を併用している | 定期実行だけなら type を外す。静的サイトに処理を追加する場合は、サーバーを持つ構成への変更も確認する |
廃止された workers: が残っている | 削除し、処理に応じて crons かリクエスト処理に変更する |
向いていないこと
Section titled “向いていないこと”- イベント発生後、すぐに開始する必要がある処理。定期実行では次の予定時刻まで待ちます。
- 分割できず、1 回の実行時間の上限を超える処理。
- ジョブの完了を待って別のジョブを開始するなどの、ワークフロー制御。