convex-cron-jobs
バックグラウンドタスク向けのスケジュール実行パターンを提供するスキルで、インターバルスケジューリングやcron式の設定、ジョブ監視、リトライ戦略、長時間タスクのベストプラクティスに対応します。定期的な自動処理やバッチ実行をConvex上で実装する際に活用できます。
description の原文を見る
Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for long-running tasks
SKILL.md 本文
Convex Cron Jobs
Convex アプリケーション内で、バックグラウンドタスク、クリーンアップジョブ、データ同期、自動ワークフローを実行するための定期関数をスケジュールします。
ドキュメンテーションソース
実装する前に、推測せず最新のドキュメントを確認してください:
- プライマリ: https://docs.convex.dev/scheduling/cron-jobs
- スケジューリング概要: https://docs.convex.dev/scheduling
- スケジュール済み関数: https://docs.convex.dev/scheduling/scheduled-functions
- より広いコンテキストについて: https://docs.convex.dev/llms.txt
説明
Cron ジョブの概要
Convex cron ジョブを使用すると、定期的なインターバルまたは特定の時刻に関数を実行するようスケジュールできます。主な機能:
- 固定スケジュールで関数を実行
- インターバルベースおよび cron 式スケジューリングに対応
- 失敗時の自動リトライ
- Convex ダッシュボード経由での監視
基本的な Cron セットアップ
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// 1 時間ごとに実行
crons.interval(
"cleanup expired sessions",
{ hours: 1 },
internal.tasks.cleanupExpiredSessions,
{}
);
// 毎日午前 0 時 UTC に実行
crons.cron(
"daily report",
"0 0 * * *",
internal.reports.generateDailyReport,
{}
);
export default crons;
インターバルベースのスケジューリング
シンプルな定期タスクには crons.interval を使用します:
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// 5 分ごと
crons.interval(
"sync external data",
{ minutes: 5 },
internal.sync.fetchExternalData,
{}
);
// 2 時間ごと
crons.interval(
"cleanup temp files",
{ hours: 2 },
internal.files.cleanupTempFiles,
{}
);
// 30 秒ごと (最小インターバル)
crons.interval(
"health check",
{ seconds: 30 },
internal.monitoring.healthCheck,
{}
);
export default crons;
Cron 式スケジューリング
cron 式を使用した正確なスケジューリングには crons.cron を使用します:
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// 毎日午前 9 時 UTC
crons.cron(
"morning notifications",
"0 9 * * *",
internal.notifications.sendMorningDigest,
{}
);
// 毎週月曜日午前 8 時 UTC
crons.cron(
"weekly summary",
"0 8 * * 1",
internal.reports.generateWeeklySummary,
{}
);
// 毎月 1 日午前 0 時
crons.cron(
"monthly billing",
"0 0 1 * *",
internal.billing.processMonthlyBilling,
{}
);
// 15 分ごと
crons.cron(
"frequent sync",
"*/15 * * * *",
internal.sync.syncData,
{}
);
export default crons;
Cron 式リファレンス
┌───────────── 分 (0-59)
│ ┌───────────── 時 (0-23)
│ │ ┌───────────── 日付 (1-31)
│ │ │ ┌───────────── 月 (1-12)
│ │ │ │ ┌───────────── 曜日 (0-6, 日曜日=0)
│ │ │ │ │
* * * * *
一般的なパターン:
* * * * *- 毎分0 * * * *- 毎時間0 0 * * *- 毎日午前 0 時0 0 * * 0- 毎週日曜日午前 0 時0 0 1 * *- 毎月 1 日*/5 * * * *- 5 分ごと0 9-17 * * 1-5- 月曜日~金曜日の午前 9 時~午後 5 時の毎時間
Cron 用の内部関数
Cron ジョブはセキュリティのために内部関数を呼び出す必要があります:
// convex/tasks.ts
import { internalMutation, internalQuery } from "./_generated/server";
import { v } from "convex/values";
// 期限切れセッションのクリーンアップ
export const cleanupExpiredSessions = internalMutation({
args: {},
returns: v.number(),
handler: async (ctx) => {
const oneHourAgo = Date.now() - 60 * 60 * 1000;
const expiredSessions = await ctx.db
.query("sessions")
.withIndex("by_lastActive")
.filter((q) => q.lt(q.field("lastActive"), oneHourAgo))
.collect();
for (const session of expiredSessions) {
await ctx.db.delete(session._id);
}
return expiredSessions.length;
},
});
// 保留中のタスク処理
export const processPendingTasks = internalMutation({
args: {},
returns: v.null(),
handler: async (ctx) => {
const pendingTasks = await ctx.db
.query("tasks")
.withIndex("by_status", (q) => q.eq("status", "pending"))
.take(100);
for (const task of pendingTasks) {
await ctx.db.patch(task._id, {
status: "processing",
startedAt: Date.now(),
});
// 実際の処理をスケジュール
await ctx.scheduler.runAfter(0, internal.tasks.processTask, {
taskId: task._id,
});
}
return null;
},
});
引数付き Cron ジョブ
Cron ジョブに静的な引数を渡します:
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// 異なるタイプに対して異なるクリーンアップインターバル
crons.interval(
"cleanup temp files",
{ hours: 1 },
internal.cleanup.cleanupByType,
{ fileType: "temp", maxAge: 3600000 }
);
crons.interval(
"cleanup cache files",
{ hours: 24 },
internal.cleanup.cleanupByType,
{ fileType: "cache", maxAge: 86400000 }
);
export default crons;
// convex/cleanup.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";
export const cleanupByType = internalMutation({
args: {
fileType: v.string(),
maxAge: v.number(),
},
returns: v.number(),
handler: async (ctx, args) => {
const cutoff = Date.now() - args.maxAge;
const oldFiles = await ctx.db
.query("files")
.withIndex("by_type_and_created", (q) =>
q.eq("type", args.fileType).lt("createdAt", cutoff)
)
.collect();
for (const file of oldFiles) {
await ctx.storage.delete(file.storageId);
await ctx.db.delete(file._id);
}
return oldFiles.length;
},
});
監視とログ
Cron ジョブの実行を追跡するためのログを追加します:
// convex/tasks.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";
export const cleanupWithLogging = internalMutation({
args: {},
returns: v.null(),
handler: async (ctx) => {
const startTime = Date.now();
let processedCount = 0;
let errorCount = 0;
try {
const expiredItems = await ctx.db
.query("items")
.withIndex("by_expiresAt")
.filter((q) => q.lt(q.field("expiresAt"), Date.now()))
.collect();
for (const item of expiredItems) {
try {
await ctx.db.delete(item._id);
processedCount++;
} catch (error) {
errorCount++;
console.error(`Failed to delete item ${item._id}:`, error);
}
}
// ジョブ完了のログ
await ctx.db.insert("cronLogs", {
jobName: "cleanup",
startTime,
endTime: Date.now(),
duration: Date.now() - startTime,
processedCount,
errorCount,
status: errorCount === 0 ? "success" : "partial",
});
} catch (error) {
// ジョブ失敗のログ
await ctx.db.insert("cronLogs", {
jobName: "cleanup",
startTime,
endTime: Date.now(),
duration: Date.now() - startTime,
processedCount,
errorCount,
status: "failed",
error: String(error),
});
throw error;
}
return null;
},
});
大規模データセットのバッチ処理
タイムアウトを避けるため、大規模データセットをバッチで処理します:
// convex/tasks.ts
import { internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
const BATCH_SIZE = 100;
export const processBatch = internalMutation({
args: {
cursor: v.optional(v.string()),
},
returns: v.null(),
handler: async (ctx, args) => {
const result = await ctx.db
.query("items")
.withIndex("by_status", (q) => q.eq("status", "pending"))
.paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null });
for (const item of result.page) {
await ctx.db.patch(item._id, {
status: "processed",
processedAt: Date.now(),
});
}
// 更多項目がある場合は次のバッチをスケジュール
if (!result.isDone) {
await ctx.scheduler.runAfter(0, internal.tasks.processBatch, {
cursor: result.continueCursor,
});
}
return null;
},
});
Cron での外部 API 呼び出し
外部 API 呼び出しには action を使用します:
// convex/sync.ts
"use node";
import { internalAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
export const syncExternalData = internalAction({
args: {},
returns: v.null(),
handler: async (ctx) => {
// 外部 API からフェッチ
const response = await fetch("https://api.example.com/data", {
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
},
});
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
const data = await response.json();
// ミューテーションを使用してデータを保存
await ctx.runMutation(internal.sync.storeExternalData, {
data,
syncedAt: Date.now(),
});
return null;
},
});
export const storeExternalData = internalMutation({
args: {
data: v.any(),
syncedAt: v.number(),
},
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.insert("externalData", {
data: args.data,
syncedAt: args.syncedAt,
});
return null;
},
});
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.interval(
"sync external data",
{ minutes: 15 },
internal.sync.syncExternalData,
{}
);
export default crons;
サンプル
Cron ジョブログのスキーマ
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
cronLogs: defineTable({
jobName: v.string(),
startTime: v.number(),
endTime: v.number(),
duration: v.number(),
processedCount: v.number(),
errorCount: v.number(),
status: v.union(
v.literal("success"),
v.literal("partial"),
v.literal("failed")
),
error: v.optional(v.string()),
})
.index("by_job", ["jobName"])
.index("by_status", ["status"])
.index("by_startTime", ["startTime"]),
sessions: defineTable({
userId: v.id("users"),
token: v.string(),
lastActive: v.number(),
expiresAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_lastActive", ["lastActive"])
.index("by_expiresAt", ["expiresAt"]),
tasks: defineTable({
type: v.string(),
status: v.union(
v.literal("pending"),
v.literal("processing"),
v.literal("completed"),
v.literal("failed")
),
data: v.any(),
createdAt: v.number(),
startedAt: v.optional(v.number()),
completedAt: v.optional(v.number()),
})
.index("by_status", ["status"])
.index("by_type_and_status", ["type", "status"]),
});
完全な Cron 設定例
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// クリーンアップジョブ
crons.interval(
"cleanup expired sessions",
{ hours: 1 },
internal.cleanup.expiredSessions,
{}
);
crons.interval(
"cleanup old logs",
{ hours: 24 },
internal.cleanup.oldLogs,
{ maxAgeDays: 30 }
);
// 同期ジョブ
crons.interval(
"sync user data",
{ minutes: 15 },
internal.sync.userData,
{}
);
// レポートジョブ
crons.cron(
"daily analytics",
"0 1 * * *",
internal.reports.dailyAnalytics,
{}
);
crons.cron(
"weekly summary",
"0 9 * * 1",
internal.reports.weeklySummary,
{}
);
// ヘルスチェック
crons.interval(
"service health check",
{ minutes: 5 },
internal.monitoring.healthCheck,
{}
);
export default crons;
ベストプラクティス
- 明確に指示されない限り
npx convex deployを実行しないこと - 明確に指示されない限り git コマンドを実行しないこと
crons.intervalまたはcrons.cronメソッドのみを使用し、非推奨のヘルパーは使用しないこと- セキュリティのため Cron ジョブから内部関数を呼び出すこと
- 同じファイル内の関数でも
_generated/apiからinternalをインポートすること - 本番環境の Cron ジョブにはログと監視を追加すること
- 大規模データセットを処理する操作にはバッチ処理を使用すること
- ジョブ全体の失敗を防ぐため、エラーを適切に処理すること
- ダッシュボード表示のために意味のあるジョブ名を使用すること
- cron 式を使用する際はタイムゾーンを考慮すること (Convex は UTC を使用)
よくある落とし穴
- パブリック関数の使用 - Cron ジョブは内部関数のみを呼び出すべき
- 長時間実行されるミューテーション - 大規模な操作はバッチに分割すること
- エラーハンドリングの欠落 - ハンドルされないエラーはジョブ全体を失敗させる
- タイムゾーンの忘却 - すべての cron 式は UTC を使用する
- 非推奨ヘルパーの使用 -
crons.hourly,crons.dailyなどは避けること - 実行ログの欠落 - 本番環境の問題のデバッグを困難にする
参考資料
- Convex ドキュメント: https://docs.convex.dev/
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
- Cron ジョブ: https://docs.convex.dev/scheduling/cron-jobs
- スケジューリング概要: https://docs.convex.dev/scheduling
- スケジュール済み関数: https://docs.convex.dev/scheduling/scheduled-functions
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- waynesutton
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/waynesutton/convexskills / ライセンス: Apache-2.0
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。