Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/queues/gotchas.md
1# Queues Gotchas & Troubleshooting23## CRITICAL: Top Production Mistakes45### 1. "Entire Batch Retried After Single Error"67**Problem:** Throwing uncaught error in queue handler retries the entire batch, not just the failed message8**Cause:** Uncaught exceptions propagate to the runtime, triggering batch-level retry9**Solution:** Always wrap individual message processing in try/catch and call `msg.retry()` explicitly1011```typescript12// ❌ BAD: Throws error, retries entire batch13async queue(batch: MessageBatch): Promise<void> {14for (const msg of batch.messages) {15await riskyOperation(msg.body); // If this throws, entire batch retries16msg.ack();17}18}1920// ✅ GOOD: Catch per message, handle individually21async queue(batch: MessageBatch): Promise<void> {22for (const msg of batch.messages) {23try {24await riskyOperation(msg.body);25msg.ack();26} catch (error) {27msg.retry({ delaySeconds: 60 });28}29}30}31```3233### 2. "Messages Retry Forever"3435**Problem:** Messages not explicitly ack'd or retry'd will auto-retry indefinitely36**Cause:** Runtime default behavior retries unhandled messages until `max_retries` reached37**Solution:** Always call `msg.ack()` or `msg.retry()` for each message. Never leave messages unhandled.3839```typescript40// ❌ BAD: Skipped messages auto-retry forever41async queue(batch: MessageBatch): Promise<void> {42for (const msg of batch.messages) {43if (shouldProcess(msg.body)) {44await process(msg.body);45msg.ack();46}47// Missing: msg.ack() for skipped messages - they will retry!48}49}5051// ✅ GOOD: Explicitly handle all messages52async queue(batch: MessageBatch): Promise<void> {53for (const msg of batch.messages) {54if (shouldProcess(msg.body)) {55await process(msg.body);56msg.ack();57} else {58msg.ack(); // Explicitly ack even if not processing59}60}61}62```6364## Common Errors6566### "Duplicate Message Processing"6768**Problem:** Same message processed multiple times69**Cause:** At-least-once delivery guarantee means duplicates are possible during retries70**Solution:** Design consumers to be idempotent by tracking processed message IDs in KV with expiration TTL7172```typescript73async queue(batch: MessageBatch, env: Env): Promise<void> {74for (const msg of batch.messages) {75const processed = await env.PROCESSED_KV.get(msg.id);76if (processed) {77msg.ack();78continue;79}8081await processMessage(msg.body);82await env.PROCESSED_KV.put(msg.id, '1', { expirationTtl: 86400 });83msg.ack();84}85}86```8788### "Pull Consumer Can't Decode Messages"8990**Problem:** Pull consumer or dashboard shows unreadable message bodies91**Cause:** Messages sent with `v8` content type are only decodable by Workers push consumers92**Solution:** Use `json` content type for pull consumers or dashboard visibility9394```typescript95// Use json for pull consumers96await env.MY_QUEUE.send(data, { contentType: 'json' });9798// Use v8 only for push consumers with complex JS types99await env.MY_QUEUE.send({ date: new Date(), tags: new Set() }, { contentType: 'v8' });100```101102### "Messages Not Being Delivered"103104**Problem:** Messages sent but consumer not processing105**Cause:** Queue paused, consumer not configured, or consumer errors106**Solution:** Check queue status with `wrangler queues list`, verify consumer configured with `wrangler queues consumer add`, and check logs with `wrangler tail`107108### "High Dead Letter Queue Rate"109110**Problem:** Many messages ending up in DLQ111**Cause:** Consumer repeatedly failing to process messages after max retries112**Solution:** Review consumer error logs, check external dependency availability, verify message format matches expectations, or increase retry delay113114## Error Classification Patterns115116Classify errors to decide whether to retry or DLQ:117118```typescript119async queue(batch: MessageBatch, env: Env): Promise<void> {120for (const msg of batch.messages) {121try {122await processMessage(msg.body);123msg.ack();124} catch (error) {125// Transient errors: retry with backoff126if (isRetryable(error)) {127const delay = Math.min(30 * (2 ** msg.attempts), 43200);128msg.retry({ delaySeconds: delay });129}130// Permanent errors: ack to avoid infinite retries131else {132console.error('Permanent error, sending to DLQ:', error);133await env.ERROR_LOG.put(msg.id, JSON.stringify({ msg: msg.body, error: String(error) }));134msg.ack(); // Prevent further retries135}136}137}138}139140function isRetryable(error: unknown): boolean {141if (error instanceof Response) {142// Retry: rate limits, timeouts, server errors143return error.status === 429 || error.status >= 500;144}145if (error instanceof Error) {146// Don't retry: validation, auth, not found147return !error.message.includes('validation') &&148!error.message.includes('unauthorized') &&149!error.message.includes('not found');150}151return false; // Unknown errors don't retry152}153```154155### "CPU Time Exceeded in Consumer"156157**Problem:** Consumer fails with CPU time limit exceeded158**Cause:** Consumer processing exceeding 30s default CPU time limit159**Solution:** Increase CPU limit in wrangler.jsonc: `{ "limits": { "cpu_ms": 300000 } }` (5 minutes max)160161## Content Type Decision Guide162163**When to use each content type:**164165| Content Type | Use When | Readable By | Supports |166|--------------|----------|-------------|----------|167| `json` (default) | Pull consumers, dashboard visibility, simple objects | All (push/pull/dashboard) | JSON-serializable types only |168| `v8` | Push consumers only, complex JS objects | Push consumers only | Date, Map, Set, BigInt, typed arrays |169| `text` | String-only payloads | All | Strings only |170| `bytes` | Binary data (images, files) | All | ArrayBuffer, Uint8Array |171172**Decision tree:**1731. Need to view in dashboard or use pull consumer? → Use `json`1742. Need Date, Map, Set, or other V8 types? → Use `v8` (push consumers only)1753. Just strings? → Use `text`1764. Binary data? → Use `bytes`177178```typescript179// Dashboard/pull: use json180await env.QUEUE.send({ id: 123, name: 'test' }, { contentType: 'json' });181182// Complex JS types (push only): use v8183await env.QUEUE.send({184created: new Date(),185tags: new Set(['a', 'b'])186}, { contentType: 'v8' });187```188189## Limits190191| Limit | Value | Notes |192|-------|-------|-------|193| Max queues | 10,000 | Per account |194| Message size | 128 KB | Maximum per message |195| Batch size (consumer) | 100 messages | Maximum messages per batch |196| Batch size (sendBatch) | 100 msgs or 256 KB | Whichever limit reached first |197| Throughput | 5,000 msgs/sec | Per queue |198| Retention | 4-14 days | Configurable retention period |199| Max backlog | 25 GB | Maximum queue backlog size |200| Max delay | 12 hours (43,200s) | Maximum message delay |201| Max retries | 100 | Maximum retry attempts |202| CPU time default | 30s | Per consumer invocation |203| CPU time max | 300s (5 min) | Configurable via `limits.cpu_ms` |204| Operations per message | 3 (write + read + delete) | Base cost per message |205| Pricing | $0.40 per 1M operations | After 1M free operations |206| Message charging | Per 64 KB chunk | Messages charged in 64 KB increments |207