Files
Portfolio/blog-posts/56-cron-job-scheduling.md
T
damjan_savicandClaude Opus 4.5 43484c5023 Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:42:11 +01:00

515 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cron Jobs & Task Scheduling in Node.js
**Meta-Description:** Task Scheduling für Node.js. Cron Syntax, node-cron, BullMQ Repeatable Jobs und Serverless Scheduling.
**Keywords:** Cron Jobs, Task Scheduling, Node.js, node-cron, BullMQ, Scheduled Tasks, Background Jobs
---
## Einführung
Automatisierte Aufgaben sind das Rückgrat jeder Anwendung: **Backups, Reports, Cleanup, Sync-Jobs**. Mit Cron Jobs laufen sie zuverlässig zu definierten Zeiten ohne manuellen Eingriff.
---
## Cron Syntax
```
┌─────────────────────────────────────────────────────────────┐
│ CRON EXPRESSION │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────── Minute (0-59) │
│ │ ┌───────────── Stunde (0-23) │
│ │ │ ┌───────────── Tag des Monats (1-31) │
│ │ │ │ ┌───────────── Monat (1-12) │
│ │ │ │ │ ┌───────────── Wochentag (0-7, 0 und 7 = Sonntag)│
│ │ │ │ │ │ │
│ * * * * * │
│ │
│ Beispiele: │
│ ├── * * * * * → Jede Minute │
│ ├── 0 * * * * → Jede volle Stunde │
│ ├── 0 0 * * * → Täglich um Mitternacht │
│ ├── 0 8 * * 1-5 → Mo-Fr um 8:00 │
│ ├── 0 0 1 * * → Am 1. jeden Monats │
│ ├── */15 * * * * → Alle 15 Minuten │
│ └── 0 9,18 * * * → Um 9:00 und 18:00 │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## node-cron Setup
```bash
npm install node-cron
npm install -D @types/node-cron
```
```typescript
import cron from 'node-cron';
// Basic Scheduled Task
cron.schedule('* * * * *', () => {
console.log('Runs every minute');
});
// Mit Timezone
cron.schedule('0 8 * * *', () => {
console.log('Daily at 8:00 AM Berlin time');
}, {
timezone: 'Europe/Berlin'
});
// Task mit Start/Stop
const task = cron.schedule('*/5 * * * *', () => {
console.log('Every 5 minutes');
}, {
scheduled: false // Nicht automatisch starten
});
// Manuell starten/stoppen
task.start();
// task.stop();
// Validation
const isValid = cron.validate('* * * * *'); // true
```
---
## Praktische Scheduler-Klasse
```typescript
import cron, { ScheduledTask } from 'node-cron';
interface Job {
name: string;
schedule: string;
handler: () => Promise<void>;
enabled: boolean;
timezone?: string;
}
class TaskScheduler {
private tasks: Map<string, ScheduledTask> = new Map();
private jobs: Map<string, Job> = new Map();
register(job: Job) {
this.jobs.set(job.name, job);
if (job.enabled) {
this.startJob(job.name);
}
}
startJob(name: string) {
const job = this.jobs.get(name);
if (!job) throw new Error(`Job ${name} not found`);
if (this.tasks.has(name)) {
console.log(`Job ${name} already running`);
return;
}
const task = cron.schedule(job.schedule, async () => {
const startTime = Date.now();
console.log(`[${name}] Starting...`);
try {
await job.handler();
console.log(`[${name}] Completed in ${Date.now() - startTime}ms`);
} catch (error) {
console.error(`[${name}] Failed:`, error);
// Alert senden
}
}, {
timezone: job.timezone || 'Europe/Berlin'
});
this.tasks.set(name, task);
console.log(`[${name}] Scheduled: ${job.schedule}`);
}
stopJob(name: string) {
const task = this.tasks.get(name);
if (task) {
task.stop();
this.tasks.delete(name);
console.log(`[${name}] Stopped`);
}
}
stopAll() {
for (const [name, task] of this.tasks) {
task.stop();
console.log(`[${name}] Stopped`);
}
this.tasks.clear();
}
getStatus(): Array<{ name: string; schedule: string; running: boolean }> {
return Array.from(this.jobs.values()).map(job => ({
name: job.name,
schedule: job.schedule,
running: this.tasks.has(job.name)
}));
}
}
// Verwendung
const scheduler = new TaskScheduler();
scheduler.register({
name: 'daily-backup',
schedule: '0 2 * * *',
handler: async () => {
await performDatabaseBackup();
},
enabled: true
});
scheduler.register({
name: 'hourly-sync',
schedule: '0 * * * *',
handler: async () => {
await syncExternalData();
},
enabled: true
});
scheduler.register({
name: 'weekly-report',
schedule: '0 9 * * 1', // Montag 9:00
handler: async () => {
await generateWeeklyReport();
await sendReportEmail();
},
enabled: true
});
// Graceful Shutdown
process.on('SIGTERM', () => {
scheduler.stopAll();
process.exit(0);
});
```
---
## BullMQ Repeatable Jobs
```typescript
import { Queue, Worker } from 'bullmq';
import IORedis from 'ioredis';
const connection = new IORedis(process.env.REDIS_URL!);
// Queue für Scheduled Jobs
const scheduledQueue = new Queue('scheduled-tasks', { connection });
// Repeatable Jobs registrieren
async function setupScheduledJobs() {
// Täglich um 2:00 UTC
await scheduledQueue.add('database-backup', {}, {
repeat: {
pattern: '0 2 * * *',
tz: 'Europe/Berlin'
},
jobId: 'daily-backup' // Verhindert Duplikate
});
// Alle 5 Minuten
await scheduledQueue.add('health-check', {}, {
repeat: {
every: 5 * 60 * 1000 // Millisekunden
},
jobId: 'health-check'
});
// Wöchentlich Montag 9:00
await scheduledQueue.add('weekly-report', { type: 'weekly' }, {
repeat: {
pattern: '0 9 * * 1',
tz: 'Europe/Berlin'
},
jobId: 'weekly-report'
});
// Monatlich am 1.
await scheduledQueue.add('monthly-cleanup', {}, {
repeat: {
pattern: '0 3 1 * *',
tz: 'Europe/Berlin'
},
jobId: 'monthly-cleanup'
});
}
// Worker für Scheduled Jobs
const worker = new Worker('scheduled-tasks', async (job) => {
console.log(`Processing ${job.name}`);
switch (job.name) {
case 'database-backup':
await performDatabaseBackup();
break;
case 'health-check':
await runHealthChecks();
break;
case 'weekly-report':
await generateAndSendReport('weekly');
break;
case 'monthly-cleanup':
await cleanupOldData();
break;
default:
console.warn(`Unknown job: ${job.name}`);
}
return { completed: true };
}, { connection });
worker.on('completed', (job, result) => {
console.log(`Job ${job.name} completed`);
});
worker.on('failed', (job, error) => {
console.error(`Job ${job?.name} failed:`, error);
// Alert senden
});
// Repeatable Jobs verwalten
async function listScheduledJobs() {
const jobs = await scheduledQueue.getRepeatableJobs();
return jobs.map(job => ({
name: job.name,
pattern: job.pattern || `every ${job.every}ms`,
next: new Date(job.next).toISOString()
}));
}
async function removeScheduledJob(jobId: string) {
const jobs = await scheduledQueue.getRepeatableJobs();
const job = jobs.find(j => j.id === jobId);
if (job) {
await scheduledQueue.removeRepeatableByKey(job.key);
console.log(`Removed job: ${jobId}`);
}
}
```
---
## Serverless Cron (Vercel/AWS)
```typescript
// Vercel Cron Jobs (vercel.json)
{
"crons": [
{
"path": "/api/cron/daily-backup",
"schedule": "0 2 * * *"
},
{
"path": "/api/cron/hourly-sync",
"schedule": "0 * * * *"
}
]
}
// app/api/cron/daily-backup/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge';
export const maxDuration = 60; // 60 Sekunden max
export async function GET(request: NextRequest) {
// Verify Cron Secret (Security)
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
await performBackup();
return NextResponse.json({
success: true,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Backup failed:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
// AWS EventBridge + Lambda
// serverless.yml
// functions:
// dailyBackup:
// handler: src/handlers/backup.handler
// events:
// - schedule: cron(0 2 * * ? *)
```
---
## Job Locking (Prevent Duplicates)
```typescript
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
async function withJobLock<T>(
jobName: string,
ttlSeconds: number,
handler: () => Promise<T>
): Promise<T | null> {
const lockKey = `job-lock:${jobName}`;
// Versuche Lock zu erwerben
const acquired = await redis.set(lockKey, Date.now().toString(), 'EX', ttlSeconds, 'NX');
if (!acquired) {
console.log(`Job ${jobName} already running, skipping`);
return null;
}
try {
return await handler();
} finally {
// Lock freigeben
await redis.del(lockKey);
}
}
// Verwendung
cron.schedule('*/5 * * * *', async () => {
await withJobLock('sync-products', 300, async () => {
// Dieser Code läuft garantiert nur einmal
await syncProducts();
});
});
```
---
## Monitoring & Alerting
```typescript
interface JobExecution {
jobName: string;
startedAt: Date;
completedAt?: Date;
status: 'running' | 'completed' | 'failed';
error?: string;
duration?: number;
}
class JobMonitor {
private executions: Map<string, JobExecution> = new Map();
async trackExecution<T>(
jobName: string,
handler: () => Promise<T>
): Promise<T> {
const executionId = `${jobName}-${Date.now()}`;
this.executions.set(executionId, {
jobName,
startedAt: new Date(),
status: 'running'
});
try {
const result = await handler();
const execution = this.executions.get(executionId)!;
execution.completedAt = new Date();
execution.status = 'completed';
execution.duration = execution.completedAt.getTime() - execution.startedAt.getTime();
// Log to database
await this.saveExecution(execution);
return result;
} catch (error) {
const execution = this.executions.get(executionId)!;
execution.completedAt = new Date();
execution.status = 'failed';
execution.error = (error as Error).message;
execution.duration = execution.completedAt.getTime() - execution.startedAt.getTime();
await this.saveExecution(execution);
await this.sendAlert(execution);
throw error;
} finally {
// Cleanup alte Executions
setTimeout(() => this.executions.delete(executionId), 60000);
}
}
private async saveExecution(execution: JobExecution) {
await db.jobExecution.create({ data: execution });
}
private async sendAlert(execution: JobExecution) {
// Slack, Email, PagerDuty, etc.
await slack.send({
text: `❌ Job Failed: ${execution.jobName}`,
attachments: [{
color: 'danger',
fields: [
{ title: 'Error', value: execution.error || 'Unknown' },
{ title: 'Duration', value: `${execution.duration}ms` }
]
}]
});
}
}
```
---
## Fazit
Task Scheduling in Node.js:
1. **node-cron**: Einfach für Single-Server
2. **BullMQ**: Distributed, Reliable, Scalable
3. **Serverless Cron**: Vercel, AWS EventBridge
4. **Job Locking**: Verhindert Duplikate
Zuverlässige Automation für jede Anwendung.
---
## Bildprompts
1. "Clock with automated tasks running at specific times, scheduling concept"
2. "Multiple servers coordinating scheduled jobs, distributed cron"
3. "Calendar with recurring events and checkmarks, automated workflow"
---
## Quellen
- [node-cron Documentation](https://www.npmjs.com/package/node-cron)
- [BullMQ Repeatable Jobs](https://docs.bullmq.io/guide/jobs/repeatable)
- [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs)
- [Cron Expression Generator](https://crontab.guru/)