How Kaneo Handles Scheduled Jobs: Distributed Cron Architecture Explained
TLDR: Kaneo runs all recurring background tasks through an embedded scheduler in the API service that leverages PostgreSQL-based distributed locks to guarantee exactly-once execution across multi-instance deployments, coupled with Sentry cron monitoring for observability.
Scheduled jobs in Kaneo operate without separate worker processes or external queue systems. The open-source project management platform implements a self-contained scheduling framework within apps/api/src/scheduler that handles everything from due-date reminders to seat reconciliation directly inside the API service. This design reduces infrastructure overhead while maintaining strict execution guarantees through database-backed leader election.
Scheduler Architecture in Kaneo
The scheduling system consists of three core layers: a registration bootstrap, a distributed locking mechanism, and domain-specific job implementations.
Core Components
All scheduling logic resides in the apps/api/src/scheduler package. The entry point at apps/api/src/scheduler/index.ts registers four distinct cron jobs and initializes the execution loop. Each job handler wraps its logic with withJobLease, a distributed lock utility defined in apps/api/src/scheduler/leader-lock.ts that prevents concurrent execution across scaled API pods. The lock implementation supports PostgreSQL as the default store with optional Redis backing for high-throughput scenarios.
Background Job Types
Kaneo currently maintains four production scheduled jobs, each mapped to a specific module:
- Due-date reminders –
apps/api/src/scheduler/reminder-timing.tsscans for tasks approaching deadlines and dispatches notifications every five minutes. - Trial reminders –
apps/api/src/scheduler/trial-reminders.tsmonitors trial periods and sends expiration warnings on an hourly cadence. - Seat reconciliation –
apps/api/src/scheduler/seat-reconciliation.tsperforms daily workspace billing calculations to synchronize seat usage with subscription limits. - Project webhook reminders – Handles webhook delivery retries and failure notifications (referenced in Sentry monitoring configuration).
Distributed Locking for Exactly-Once Execution
Kaneo guarantees exactly-once execution through a lease-based concurrency control system. When the API service scales to multiple instances, the withJobLease function ensures only one instance processes a given job at a time.
The lock implementation writes a lease row containing job_id and expires_at timestamps into the scheduler_leases table. If an instance successfully acquires the lease, it executes the job callback; otherwise, the job skips for that cycle. This mechanism prevents duplicate work without requiring external coordination services like ZooKeeper or Consul.
// apps/api/src/scheduler/seat-reconciliation.ts
import { withJobLease } from "./leader-lock";
export async function reconcileWorkspaceSeats() {
await withJobLease("seat-reconciliation", async () => {
// Fetches workspaces, calculates seat usage, updates database records
});
}
The integration test at tests/api-integration/leader-lock.test.ts validates this behavior under race conditions.
Job Execution Flow
The scheduler follows a four-phase lifecycle from startup to completion:
- Registration – During API server bootstrap,
apps/api/src/scheduler/index.tsregisters job handlers with their respective cron intervals (5 minutes for due-date checks, 1 hour for trial notifications, daily for seat reconciliation). - Leader Election – Each job invocation attempts to acquire a distributed lease via
withJobLease. Failed acquisitions result in immediate termination for that instance. - Domain Execution – The job-specific callback executes business logic, such as querying overdue tasks or calculating billing metrics.
- Health Reporting – Upon successful completion, the job reports status to Sentry using stable slugs (
due-date-reminders,trial-reminders,seat-reconciliation,project-webhook-reminders).
Sentry Monitoring Integration
Kaneo integrates Sentry Cron Monitoring to track job health and alert on missed executions. The scheduler calls Sentry.captureCheckIn with the job's unique slug after each successful run.
// apps/api/src/scheduler/index.ts
import * as Sentry from "@sentry/node";
export function startScheduler() {
setInterval(async () => {
await sendDueDateReminders();
Sentry.captureCheckIn("due-date-reminders", { status: "ok" });
await sendTrialReminders();
Sentry.captureCheckIn("trial-reminders", { status: "ok" });
await reconcileWorkspaceSeats();
Sentry.captureCheckIn("seat-reconciliation", { status: "ok" });
}, 5 * 60 * 1000);
}
Sentry auto-provisions monitors on the first check-in and triggers "cron-missed" alerts if a job fails to report within its expected window. Configuration details and alert thresholds reside in sentry/README.md and sentry/alerts.json.
Testing Scheduled Job Logic
The repository includes comprehensive tests for timing-sensitive logic. Unit tests in tests/api/scheduler/reminder-timing.test.ts validate delivery windows without requiring full integration suites.
import { isReminderDue } from "../../../apps/api/src/scheduler/reminder-timing";
it("accepts scheduler runs within the ten-minute delivery window", () => {
const now = new Date("2024-01-01T10:05:00Z");
const reminder = { dueDate: new Date("2024-01-01T10:00:00Z") };
expect(isReminderDue(reminder, now)).toBe(true);
});
Summary
- Kaneo implements scheduled jobs inside the API service at
apps/api/src/scheduler, eliminating the need for separate worker processes. - Distributed locking via
withJobLeaseinleader-lock.tsensures exactly-once execution across multiple API instances using PostgreSQL or Redis. - Four primary jobs handle due-date reminders, trial notifications, seat reconciliation, and webhook deliveries on varying intervals from five minutes to daily.
- Sentry integration provides automatic monitoring and alerting through
captureCheckIncalls with stable job slugs. - The architecture supports horizontal scaling without job duplication or external coordination services.
Frequently Asked Questions
How does Kaneo prevent duplicate job execution across multiple API instances?
Kaneo uses a distributed lock mechanism called withJobLease implemented in apps/api/src/scheduler/leader-lock.ts. Before executing any scheduled task, the scheduler attempts to write a lease record to the scheduler_leases database table. Only the instance that successfully acquires the lease executes the job; others skip the cycle. This guarantees exactly-once execution even when scaling the API service to multiple pods or servers.
What types of scheduled jobs does Kaneo run?
The platform runs four recurring background jobs: due-date reminders (every 5 minutes), trial reminders (hourly), seat reconciliation (daily), and project webhook reminders. Each job has a dedicated implementation file in apps/api/src/scheduler/ and handles specific domain concerns like notifying users of impending deadlines or synchronizing billing state with subscription limits.
How does Kaneo monitor scheduled job failures?
Kaneo integrates with Sentry Cron Monitoring through the captureCheckIn API. After each successful job run, the scheduler reports a check-in using stable slugs defined in apps/api/src/scheduler/index.ts. Sentry automatically creates monitors on first execution and triggers alerts if check-ins are missed or if jobs report error statuses. Alert configurations are stored in sentry/alerts.json with documentation in sentry/README.md.
Where are the scheduled job intervals defined in Kaneo?
Job intervals are defined directly in apps/api/src/scheduler/index.ts where the startScheduler function registers setInterval calls. Due-date reminders execute every 5 minutes, trial reminders run hourly, and seat reconciliation triggers daily. These intervals are hardcoded in the scheduler bootstrap rather than external configuration files, ensuring version-controlled consistency across deployments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →