Bỏ qua

Cron Triggers

Cron Triggers là gì?

Scheduled Worker Execution

┌─────────────────────────────────────────────────────────────┐
│                    CRON TRIGGERS                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Schedule                          Worker                   │
│                                                              │
│  "0 * * * *"   ──────────────────▶  ┌──────────────┐       │
│  (every hour)                       │  scheduled() │       │
│                                      │  handler     │       │
│  "0 0 * * *"   ──────────────────▶  └──────────────┘       │
│  (daily midnight)                                           │
│                                                              │
│  Use cases:                                                  │
│  - Daily reports                                            │
│  - Cleanup old data                                         │
│  - Sync external data                                       │
│  - Health checks                                            │
│  - Scheduled notifications                                  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Configuration

wrangler.toml

name = "my-scheduled-worker"
main = "src/index.ts"
compatibility_date = "2024-01-15"

# Cron triggers
[triggers]
crons = [
  "0 * * * *",     # Every hour
  "0 0 * * *",     # Daily at midnight UTC
  "*/15 * * * *",  # Every 15 minutes
  "0 9 * * 1",     # Every Monday at 9 AM UTC
]

Cron Syntax

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *

Common Patterns

Pattern Description
* * * * * Every minute
*/15 * * * * Every 15 minutes
0 * * * * Every hour
0 0 * * * Daily at midnight
0 0 * * 0 Weekly on Sunday
0 0 1 * * Monthly on 1st
0 9 * * 1-5 Weekdays at 9 AM

Handler Implementation

Basic Handler

interface Env {
  MY_KV: KVNamespace;
  // Other bindings
}

export default {
  // HTTP handler (optional)
  async fetch(request: Request, env: Env): Promise<Response> {
    return new Response('This worker runs on schedule');
  },

  // Cron handler
  async scheduled(
    event: ScheduledEvent,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    console.log('Cron triggered at:', new Date(event.scheduledTime));
    console.log('Cron pattern:', event.cron);

    // Do scheduled work
    await performScheduledTask(env);
  },
};

async function performScheduledTask(env: Env) {
  // Your task logic
  console.log('Running scheduled task...');
}

Multiple Crons

async scheduled(
  event: ScheduledEvent,
  env: Env,
  ctx: ExecutionContext
): Promise<void> {
  switch (event.cron) {
    case '0 * * * *':
      // Hourly task
      await hourlyCleanup(env);
      break;

    case '0 0 * * *':
      // Daily task
      await dailyReport(env);
      break;

    case '0 9 * * 1':
      // Weekly Monday task
      await weeklyDigest(env);
      break;

    default:
      console.log('Unknown cron:', event.cron);
  }
}

async function hourlyCleanup(env: Env) {
  console.log('Running hourly cleanup');
  // Delete old cache entries, etc.
}

async function dailyReport(env: Env) {
  console.log('Generating daily report');
  // Generate and send report
}

async function weeklyDigest(env: Env) {
  console.log('Sending weekly digest');
  // Compile and send digest
}

Common Patterns

Data Cleanup

async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
  if (event.cron === '0 0 * * *') {
    // Daily cleanup at midnight

    // Call Supabase to delete old data
    const response = await fetch(`${env.SUPABASE_URL}/rest/v1/rpc/cleanup_old_data`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${env.SUPABASE_SERVICE_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        days_old: 30,
      }),
    });

    const result = await response.json();
    console.log('Cleanup result:', result);
  }
}

External API Sync

async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
  if (event.cron === '*/15 * * * *') {
    // Sync exchange rates every 15 minutes

    const ratesResponse = await fetch('https://api.exchangerate.host/latest');
    const rates = await ratesResponse.json();

    // Store in KV
    await env.MY_KV.put('exchange_rates', JSON.stringify(rates), {
      expirationTtl: 900,  // 15 minutes
    });

    console.log('Exchange rates updated');
  }
}

Queue Trigger Pattern

async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
  if (event.cron === '0 9 * * *') {
    // Daily at 9 AM: Queue email digests

    // Get users who want daily digest
    const response = await fetch(
      `${env.SUPABASE_URL}/rest/v1/users?digest_frequency=eq.daily`,
      {
        headers: {
          'Authorization': `Bearer ${env.SUPABASE_SERVICE_KEY}`,
        },
      }
    );

    const users = await response.json();

    // Queue emails for batch processing
    await env.EMAIL_QUEUE.sendBatch(
      users.map((user: any) => ({
        body: {
          type: 'daily_digest',
          userId: user.id,
          email: user.email,
        },
      }))
    );

    console.log(`Queued ${users.length} digest emails`);
  }
}

Testing Cron Locally

Trigger Manually

# Start dev server
wrangler dev

# In another terminal, trigger cron
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

# Or use wrangler
wrangler dev --test-scheduled

Logs

# View scheduled execution logs
wrangler tail --format pretty

Limits

Cron Limits

┌─────────────────────────────────────────────────────────────┐
│                    CRON LIMITS                               │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Free tier:                                                  │
│  - Cron triggers NOT available                              │
│                                                              │
│  Paid tier:                                                  │
│  - Up to 5 crons per Worker                                 │
│  - Minimum interval: 1 minute                               │
│  - Max execution: 30 seconds (can extend to 15 min)        │
│  - Timezone: UTC only                                       │
│                                                              │
│  Note: For complex schedules, combine with Queue            │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Best Practices

Idempotency

async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
  const runId = `${event.cron}-${event.scheduledTime}`;

  // Check if already ran
  const alreadyRan = await env.MY_KV.get(`cron:${runId}`);
  if (alreadyRan) {
    console.log('Already executed, skipping');
    return;
  }

  // Mark as running
  await env.MY_KV.put(`cron:${runId}`, 'running', { expirationTtl: 3600 });

  try {
    await doWork();
    await env.MY_KV.put(`cron:${runId}`, 'completed', { expirationTtl: 3600 });
  } catch (error) {
    await env.MY_KV.put(`cron:${runId}`, 'failed', { expirationTtl: 3600 });
    throw error;
  }
}

Tổng kết

Configuration

[triggers]
crons = ["0 * * * *", "0 0 * * *"]

Handler

async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
  // event.cron - which cron triggered
  // event.scheduledTime - timestamp
}

Best Practices

✅ Use specific cron patterns
✅ Handle multiple crons in one worker
✅ Make operations idempotent
✅ Use Queue for heavy work
✅ Log execution for monitoring

Q&A

  1. Scheduled tasks nào cần chạy?
  2. Frequency phù hợp?
  3. Timezone handling cần không?