467 lines
10 KiB
Markdown
467 lines
10 KiB
Markdown
# Cloudflare Worker Deployment Failure Investigation
|
|
|
|
Complete troubleshooting workflow for "Script exceeds size limit" deployment failure, resolved through bundle optimization and code splitting.
|
|
|
|
## Overview
|
|
|
|
**Incident**: Worker deployment failing with size limit error
|
|
**Impact**: Production deployment blocked for 2 hours
|
|
**Root Cause**: Bundle size grew from 1.2MB to 5.2MB after adding dependencies
|
|
**Resolution**: Bundle optimization (code splitting, tree shaking) reduced size to 1.8MB
|
|
**Status**: Resolved
|
|
|
|
## Incident Timeline
|
|
|
|
| Time | Event | Action |
|
|
|------|-------|--------|
|
|
| 14:00 | Deployment initiated via CI/CD | `wrangler deploy` triggered |
|
|
| 14:02 | Deployment failed | Error: "Script exceeds 1MB size limit" |
|
|
| 14:05 | Investigation started | Check recent code changes |
|
|
| 14:15 | Root cause identified | New dependencies increased bundle size |
|
|
| 14:30 | Fix implemented | Bundle optimization applied |
|
|
| 14:45 | Fix deployed | Successful deployment to production |
|
|
| 16:00 | Monitoring complete | Confirmed stable deployment |
|
|
|
|
---
|
|
|
|
## Symptoms and Detection
|
|
|
|
### Initial Error
|
|
|
|
**Deployment Command**:
|
|
```bash
|
|
$ wrangler deploy
|
|
✘ [ERROR] Script exceeds the size limit (5.2MB > 1MB after compression)
|
|
```
|
|
|
|
**CI/CD Pipeline Failure**:
|
|
```yaml
|
|
# GitHub Actions output
|
|
Step: Deploy to Cloudflare Workers
|
|
✓ Build completed (5.2MB bundle)
|
|
✗ Deployment failed: Script size exceeds limit
|
|
Error: Workers Free plan limit is 1MB compressed
|
|
```
|
|
|
|
**Impact**:
|
|
- Production deployment blocked
|
|
- New features stuck in staging
|
|
- Team unable to deploy hotfixes
|
|
|
|
---
|
|
|
|
## Diagnosis
|
|
|
|
### Step 1: Check Bundle Size
|
|
|
|
**Before Investigation**:
|
|
```bash
|
|
# Build the worker locally
|
|
npm run build
|
|
|
|
# Check output size
|
|
ls -lh dist/
|
|
-rw-r--r-- 1 user staff 5.2M Dec 5 14:10 worker.js
|
|
```
|
|
|
|
**Analyze Bundle Composition**:
|
|
```bash
|
|
# Use webpack-bundle-analyzer
|
|
npm install --save-dev webpack-bundle-analyzer
|
|
|
|
# Add to webpack.config.js
|
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
|
|
|
module.exports = {
|
|
plugins: [
|
|
new BundleAnalyzerPlugin()
|
|
]
|
|
};
|
|
|
|
# Build and open analyzer
|
|
npm run build
|
|
# Opens http://127.0.0.1:8888 with visual bundle breakdown
|
|
```
|
|
|
|
**Bundle Analyzer Findings**:
|
|
```
|
|
Total Size: 5.2MB
|
|
|
|
Breakdown:
|
|
- @anthropic-ai/sdk: 2.1MB (40%)
|
|
- aws-sdk: 1.8MB (35%)
|
|
- lodash: 800KB (15%)
|
|
- moment: 300KB (6%)
|
|
- application code: 200KB (4%)
|
|
```
|
|
|
|
**Red Flags**:
|
|
1. Full `aws-sdk` imported (only needed S3)
|
|
2. Entire `lodash` library (only using 3 functions)
|
|
3. `moment` included (native Date API would suffice)
|
|
4. Large AI SDK (only using text generation)
|
|
|
|
---
|
|
|
|
### Step 2: Identify Recent Changes
|
|
|
|
**Git Diff**:
|
|
```bash
|
|
# Check what changed in last deploy
|
|
git diff HEAD~1 HEAD -- src/
|
|
|
|
# Key changes:
|
|
+ import { Anthropic } from '@anthropic-ai/sdk';
|
|
+ import AWS from 'aws-sdk';
|
|
+ import _ from 'lodash';
|
|
+ import moment from 'moment';
|
|
```
|
|
|
|
**PR Analysis**:
|
|
```
|
|
PR #234: Add AI content generation feature
|
|
- Added @anthropic-ai/sdk (full SDK)
|
|
- Added AWS S3 integration (full aws-sdk)
|
|
- Used lodash for data manipulation
|
|
- Used moment for date formatting
|
|
|
|
Result: Bundle size increased by 4MB
|
|
```
|
|
|
|
---
|
|
|
|
### Step 3: Cloudflare Worker Size Limits
|
|
|
|
**Plan Limits**:
|
|
```
|
|
Workers Free: 1MB compressed
|
|
Workers Paid: 10MB compressed
|
|
|
|
Current plan: Workers Free
|
|
Current size: 5.2MB (over limit)
|
|
|
|
Options:
|
|
1. Upgrade to Workers Paid ($5/month)
|
|
2. Reduce bundle size to <1MB
|
|
3. Split into multiple workers
|
|
```
|
|
|
|
**Decision**: Reduce bundle size (no budget for upgrade)
|
|
|
|
---
|
|
|
|
## Resolution
|
|
|
|
### Fix 1: Tree Shaking with Named Imports
|
|
|
|
**Before** (imports entire libraries):
|
|
```typescript
|
|
// ❌ BAD: Imports full library
|
|
import _ from 'lodash';
|
|
import moment from 'moment';
|
|
import AWS from 'aws-sdk';
|
|
|
|
// Usage:
|
|
const unique = _.uniq(array);
|
|
const date = moment().format('YYYY-MM-DD');
|
|
const s3 = new AWS.S3();
|
|
```
|
|
|
|
**After** (imports only needed functions):
|
|
```typescript
|
|
// ✅ GOOD: Named imports enable tree shaking
|
|
import { uniq, map, filter } from 'lodash-es';
|
|
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
|
|
// ✅ BETTER: Native alternatives
|
|
const unique = [...new Set(array)];
|
|
const date = new Date().toISOString().split('T')[0];
|
|
|
|
// S3 client (v3 - modular)
|
|
const s3 = new S3Client({ region: 'us-east-1' });
|
|
```
|
|
|
|
**Size Reduction**:
|
|
```
|
|
Before:
|
|
- lodash: 800KB → lodash-es tree-shaken: 50KB (94% reduction)
|
|
- moment: 300KB → native Date: 0KB (100% reduction)
|
|
- aws-sdk: 1.8MB → @aws-sdk/client-s3: 200KB (89% reduction)
|
|
```
|
|
|
|
---
|
|
|
|
### Fix 2: External Dependencies (Don't Bundle Large SDKs)
|
|
|
|
**Before**:
|
|
```typescript
|
|
// worker.ts - bundled @anthropic-ai/sdk (2.1MB)
|
|
import { Anthropic } from '@anthropic-ai/sdk';
|
|
|
|
const client = new Anthropic({
|
|
apiKey: env.ANTHROPIC_API_KEY
|
|
});
|
|
```
|
|
|
|
**After** (use fetch directly):
|
|
```typescript
|
|
// worker.ts - use native fetch (0KB)
|
|
async function callAnthropic(prompt: string, env: Env) {
|
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-api-key': env.ANTHROPIC_API_KEY,
|
|
'anthropic-version': '2023-06-01'
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'claude-3-sonnet-20240229',
|
|
max_tokens: 1024,
|
|
messages: [
|
|
{ role: 'user', content: prompt }
|
|
]
|
|
})
|
|
});
|
|
|
|
return response.json();
|
|
}
|
|
```
|
|
|
|
**Size Reduction**:
|
|
```
|
|
Before: @anthropic-ai/sdk: 2.1MB
|
|
After: Native fetch: 0KB
|
|
Savings: 2.1MB (100% reduction)
|
|
```
|
|
|
|
---
|
|
|
|
### Fix 3: Code Splitting (Async Imports)
|
|
|
|
**Before** (everything bundled):
|
|
```typescript
|
|
// worker.ts
|
|
import { expensiveFunction } from './expensive-module';
|
|
|
|
export default {
|
|
async fetch(request: Request, env: Env) {
|
|
// Even if not used, expensive-module is in bundle
|
|
if (request.url.includes('/special')) {
|
|
return expensiveFunction(request);
|
|
}
|
|
return new Response('OK');
|
|
}
|
|
};
|
|
```
|
|
|
|
**After** (lazy load):
|
|
```typescript
|
|
// worker.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env) {
|
|
if (request.url.includes('/special')) {
|
|
// Only load when needed (separate chunk)
|
|
const { expensiveFunction } = await import('./expensive-module');
|
|
return expensiveFunction(request);
|
|
}
|
|
return new Response('OK');
|
|
}
|
|
};
|
|
```
|
|
|
|
**Size Reduction**:
|
|
```
|
|
Main bundle: 1.8MB → 500KB (72% reduction)
|
|
expensive-module chunk: Loaded on-demand (lazy)
|
|
```
|
|
|
|
---
|
|
|
|
### Fix 4: Webpack Configuration Optimization
|
|
|
|
**Updated webpack.config.js**:
|
|
```javascript
|
|
const webpack = require('webpack');
|
|
const path = require('path');
|
|
|
|
module.exports = {
|
|
entry: './src/worker.ts',
|
|
target: 'webworker',
|
|
mode: 'production',
|
|
optimization: {
|
|
minimize: true,
|
|
usedExports: true, // Tree shaking
|
|
sideEffects: false,
|
|
},
|
|
resolve: {
|
|
extensions: ['.ts', '.js'],
|
|
alias: {
|
|
// Replace heavy libraries with lighter alternatives
|
|
'moment': 'date-fns',
|
|
'lodash': 'lodash-es'
|
|
}
|
|
},
|
|
module: {
|
|
rules: [
|
|
{
|
|
test: /\.ts$/,
|
|
use: {
|
|
loader: 'ts-loader',
|
|
options: {
|
|
transpileOnly: true,
|
|
compilerOptions: {
|
|
module: 'esnext', // Enable tree shaking
|
|
moduleResolution: 'node'
|
|
}
|
|
}
|
|
},
|
|
exclude: /node_modules/
|
|
}
|
|
]
|
|
},
|
|
plugins: [
|
|
new webpack.DefinePlugin({
|
|
'process.env.NODE_ENV': JSON.stringify('production')
|
|
})
|
|
],
|
|
output: {
|
|
filename: 'worker.js',
|
|
path: path.resolve(__dirname, 'dist'),
|
|
libraryTarget: 'commonjs2'
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Results
|
|
|
|
### Bundle Size Comparison
|
|
|
|
| Category | Before | After | Reduction |
|
|
|----------|--------|-------|-----------|
|
|
| **@anthropic-ai/sdk** | 2.1MB | 0KB (fetch) | -100% |
|
|
| **aws-sdk** | 1.8MB | 200KB (v3) | -89% |
|
|
| **lodash** | 800KB | 50KB (tree-shaken) | -94% |
|
|
| **moment** | 300KB | 0KB (native Date) | -100% |
|
|
| **Application code** | 200KB | 200KB | 0% |
|
|
| **TOTAL** | **5.2MB** | **450KB** | **-91%** |
|
|
|
|
**Compressed Size**:
|
|
- Before: 5.2MB → 1.8MB compressed (over 1MB limit)
|
|
- After: 450KB → 180KB compressed (under 1MB limit)
|
|
|
|
---
|
|
|
|
### Deployment Verification
|
|
|
|
**Successful Deployment**:
|
|
```bash
|
|
$ wrangler deploy
|
|
✔ Building...
|
|
✔ Validating...
|
|
Bundle size: 450KB (180KB compressed)
|
|
✔ Uploading...
|
|
✔ Deployed to production
|
|
|
|
Production URL: https://api.greyhaven.io
|
|
Worker ID: worker-abc123
|
|
```
|
|
|
|
**Load Testing**:
|
|
```bash
|
|
# Before optimization (would fail deployment)
|
|
# Bundle: 5.2MB, deploy: FAIL
|
|
|
|
# After optimization
|
|
$ ab -n 1000 -c 10 https://api.greyhaven.io/
|
|
Requests per second: 1250 [#/sec]
|
|
Time per request: 8ms [mean]
|
|
Successful requests: 1000 (100%)
|
|
Bundle size: 450KB ✓
|
|
```
|
|
|
|
---
|
|
|
|
## Prevention Measures
|
|
|
|
### 1. CI/CD Bundle Size Check
|
|
|
|
```yaml
|
|
# .github/workflows/deploy.yml - Add size validation
|
|
steps:
|
|
- run: npm ci && npm run build
|
|
- name: Check bundle size
|
|
run: |
|
|
SIZE_MB=$(stat -f%z dist/worker.js | awk '{print $1/1048576}')
|
|
if (( $(echo "$SIZE_MB > 1.0" | bc -l) )); then
|
|
echo "❌ Bundle exceeds 1MB"; exit 1
|
|
fi
|
|
- run: npx wrangler deploy
|
|
```
|
|
|
|
### 2. Pre-commit Hook
|
|
|
|
```bash
|
|
# .git/hooks/pre-commit
|
|
SIZE_MB=$(stat -f%z dist/worker.js | awk '{print $1/1048576}')
|
|
[ "$SIZE_MB" -lt "1.0" ] || { echo "❌ Bundle >1MB"; exit 1; }
|
|
```
|
|
|
|
### 3. PR Template
|
|
|
|
```markdown
|
|
## Bundle Impact
|
|
- [ ] Bundle size <800KB
|
|
- [ ] Tree shaking verified
|
|
Size: [Before → After]
|
|
```
|
|
|
|
### 4. Automated Analysis
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"analyze": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Lessons Learned
|
|
|
|
### What Went Well
|
|
|
|
✅ Identified root cause quickly (bundle analyzer)
|
|
✅ Multiple optimization strategies applied
|
|
✅ Achieved 91% bundle size reduction
|
|
✅ Added automated checks to prevent recurrence
|
|
|
|
### What Could Be Improved
|
|
|
|
❌ No bundle size monitoring before incident
|
|
❌ Dependencies added without size consideration
|
|
❌ No pre-commit checks for bundle size
|
|
|
|
### Key Takeaways
|
|
|
|
1. **Always check bundle size** when adding dependencies
|
|
2. **Use native APIs** instead of libraries when possible
|
|
3. **Tree shaking** requires named imports (not default)
|
|
4. **Code splitting** for rarely-used features
|
|
5. **External API calls** are lighter than bundling SDKs
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- **PlanetScale Issues**: [planetscale-connection-issues.md](planetscale-connection-issues.md)
|
|
- **Network Debugging**: [distributed-system-debugging.md](distributed-system-debugging.md)
|
|
- **Performance**: [performance-degradation-analysis.md](performance-degradation-analysis.md)
|
|
- **Runbooks**: [../reference/troubleshooting-runbooks.md](../reference/troubleshooting-runbooks.md)
|
|
|
|
---
|
|
|
|
Return to [examples index](INDEX.md)
|