Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:08 +08:00
commit 7c90a3ac2b
18 changed files with 4579 additions and 0 deletions

View File

@@ -0,0 +1,593 @@
# Pricing and Limits Reference
Complete breakdown of Cloudflare Browser Rendering pricing, limits, and cost optimization strategies.
---
## Pricing Overview
Browser Rendering is billed on **two metrics**:
1. **Duration** - Total browser hours used
2. **Concurrency** - Monthly average of concurrent browsers (Workers Bindings only)
---
## Free Tier (Workers Free Plan)
| Feature | Limit |
|---------|-------|
| **Browser Duration** | 10 minutes per day |
| **Concurrent Browsers** | 3 per account |
| **New Browsers per Minute** | 3 per minute |
| **REST API Requests** | 6 per minute |
| **Browser Timeout (Idle)** | 60 seconds |
| **Max Session Duration** | No hard limit (closes on idle timeout) |
### Free Tier Use Cases
**Good for:**
- Development and testing
- Personal projects
- Low-traffic screenshot services (<100 requests/day)
- Learning and experimentation
**Not suitable for:**
- Production applications
- High-traffic services
- Long-running scraping jobs
- Batch operations (>3 concurrent browsers)
---
## Paid Tier (Workers Paid Plan)
### Included Limits
| Feature | Included |
|---------|----------|
| **Browser Duration** | 10 hours per month |
| **Concurrent Browsers** | 10 (monthly average) |
| **New Browsers per Minute** | 30 per minute |
| **REST API Requests** | 180 per minute |
| **Max Concurrent Browsers** | 30 per account |
| **Browser Timeout** | 60 seconds (extendable to 10 minutes with keep_alive) |
### Beyond Included Limits
| Metric | Price |
|--------|-------|
| **Additional Browser Hours** | $0.09 per hour |
| **Additional Concurrent Browsers** | $2.00 per browser (monthly average) |
### Requesting Higher Limits
If you need more than:
- 30 concurrent browsers
- 30 new browsers per minute
- 180 REST API requests per minute
**Request higher limits**: https://forms.gle/CdueDKvb26mTaepa9
---
## Rate Limits
### Per-Second Enforcement
Rate limits are enforced **per-second**, not per-minute.
**Example**: 180 requests per minute = 3 requests per second
**This means:**
- ❌ Cannot send all 180 requests at once
- ✅ Must spread evenly over the minute (3/second)
**Implementation:**
```typescript
async function rateLimitedLaunch(env: Env): Promise<Browser> {
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
const delay = limits.timeUntilNextAllowedBrowserAcquisition;
await new Promise(resolve => setTimeout(resolve, delay));
}
return await puppeteer.launch(env.MYBROWSER);
}
```
### Free Tier Rate Limits
- **Concurrent browsers**: 3
- **New browsers/minute**: 3 (= 1 every 20 seconds)
- **REST API requests/minute**: 6 (= 1 every 10 seconds)
### Paid Tier Rate Limits
- **Concurrent browsers**: 30 (default, can request higher)
- **New browsers/minute**: 30 (= 1 every 2 seconds)
- **REST API requests/minute**: 180 (= 3 per second)
---
## Duration Billing
### How It Works
1. **Daily Totals**: Cloudflare sums all browser usage each day (in seconds)
2. **Monthly Total**: Sum of all daily totals
3. **Rounded to Hours**: Total rounded to nearest hour
4. **Billed**: Total hours minus 10 included hours
**Example:**
- Day 1: 60 seconds (1 minute)
- Day 2: 120 seconds (2 minutes)
- ...
- Day 30: 90 seconds (1.5 minutes)
- **Monthly Total**: 45 minutes = 0.75 hours (rounded to 1 hour)
- **Billable**: 1 hour - 10 included = 0 hours (still within free allowance)
### Failed Requests
**Failed requests are NOT billed** if they fail with `waitForTimeout` error.
**Example:**
```typescript
try {
await page.goto(url, { timeout: 30000 });
} catch (error) {
// If this times out, browser time is NOT charged
console.log("Navigation timeout - not billed");
}
```
### Duration Optimization
**Minimize browser time:**
1. **Close browsers promptly**
```typescript
await browser.close(); // Don't leave hanging
```
2. **Use session reuse**
```typescript
// Reuse session instead of launching new browser
const browser = await puppeteer.connect(env.MYBROWSER, sessionId);
```
3. **Timeout management**
```typescript
// Set appropriate timeouts (don't wait forever)
await page.goto(url, { timeout: 30000 });
```
4. **Cache aggressively**
```typescript
// Cache screenshots in KV to avoid re-rendering
const cached = await env.KV.get(url, { type: "arrayBuffer" });
if (cached) return new Response(cached);
```
---
## Concurrency Billing
### How It Works
1. **Daily Peak**: Cloudflare records highest concurrent browsers each day
2. **Monthly Average**: Average of all daily peaks
3. **Billed**: Average - 10 included browsers
**Formula:**
```
monthly_average = sum(daily_peaks) / days_in_month
billable = max(0, monthly_average - 10)
cost = billable * $2.00
```
**Example:**
- Days 1-15: 10 concurrent browsers (daily peak)
- Days 16-30: 20 concurrent browsers (daily peak)
- Monthly average: ((10 × 15) + (20 × 15)) / 30 = 15 browsers
- Billable: 15 - 10 = 5 browsers
- **Cost**: 5 × $2.00 = **$10.00**
### Concurrency vs Duration
| Scenario | Concurrency Impact | Duration Impact |
|----------|-------------------|-----------------|
| 1 browser for 10 hours | 1 concurrent browser | 10 browser hours |
| 10 browsers for 1 hour | 10 concurrent browsers | 10 browser hours |
| 100 browsers for 6 minutes | 100 concurrent browsers (!!) | 10 browser hours |
**Key Insight**: Short bursts of high concurrency are EXPENSIVE.
### Concurrency Optimization
**Minimize concurrent browsers:**
1. **Use multiple tabs**
```typescript
// ❌ Bad: 10 browsers
for (const url of urls) {
const browser = await puppeteer.launch(env.MYBROWSER);
// ...
}
// ✅ Good: 1 browser, 10 tabs
const browser = await puppeteer.launch(env.MYBROWSER);
await Promise.all(urls.map(async url => {
const page = await browser.newPage();
// ...
}));
```
2. **Session reuse**
```typescript
// Maintain pool of warm browsers
// Reuse instead of launching new ones
```
3. **Queue requests**
```typescript
// Limit concurrent operations
const queue = new PQueue({ concurrency: 3 });
await Promise.all(urls.map(url => queue.add(() => process(url))));
```
4. **Incognito contexts**
```typescript
// Share browser, isolate sessions
const context1 = await browser.createBrowserContext();
const context2 = await browser.createBrowserContext();
```
---
## Cost Examples
### Example 1: Screenshot Service
**Scenario:**
- 10,000 screenshots per month
- 3 second average per screenshot
- No caching, no session reuse
**Duration:**
- 10,000 × 3 seconds = 30,000 seconds = 8.33 hours
- Billable: 8.33 - 10 = 0 hours (within free allowance)
- **Duration Cost**: $0.00
**Concurrency:**
- Assume 100 requests/hour during peak (9am-5pm weekdays)
- 100 requests/hour ÷ 3600 seconds = 0.028 browsers/second
- Peak: ~3 concurrent browsers
- Daily peak (weekdays): 3 browsers
- Daily peak (weekends): 1 browser
- Monthly average: ((3 × 22) + (1 × 8)) / 30 = 2.5 browsers
- Billable: 2.5 - 10 = 0 (within free allowance)
- **Concurrency Cost**: $0.00
**Total: $0.00** (within free tier!)
---
### Example 2: Heavy Scraping
**Scenario:**
- 1,000 URLs per day
- 10 seconds average per URL
- Batch processing (10 concurrent browsers)
**Duration:**
- 1,000 × 10 seconds × 30 days = 300,000 seconds = 83.33 hours
- Billable: 83.33 - 10 = 73.33 hours
- **Duration Cost**: 73.33 × $0.09 = **$6.60**
**Concurrency:**
- Daily peak: 10 concurrent browsers (every day)
- Monthly average: 10 browsers
- Billable: 10 - 10 = 0 (within free allowance)
- **Concurrency Cost**: $0.00
**Total: $6.60/month**
---
### Example 3: Burst Traffic
**Scenario:**
- Newsletter sent monthly with screenshot links
- 10,000 screenshots in 1 hour
- Each screenshot: 3 seconds
**Duration:**
- 10,000 × 3 seconds = 30,000 seconds = 8.33 hours
- Billable: 8.33 - 10 = 0 hours
- **Duration Cost**: $0.00
**Concurrency:**
- 10,000 screenshots in 1 hour = 166 requests/minute
- At 3 seconds each: ~8.3 concurrent browsers
- But limited to 30 max, so likely queueing
- Daily peak: 30 browsers (rate limit)
- Monthly average: (30 × 1 day + 1 × 29 days) / 30 = 1.97 browsers
- Billable: 1.97 - 10 = 0
- **Concurrency Cost**: $0.00
**Total: $0.00**
**Note**: Would hit rate limits. Better to spread over longer period or request higher limits.
---
### Example 4: Production API (Optimized)
**Scenario:**
- 100,000 screenshots per month
- Session reuse + KV caching (90% cache hit rate)
- 10,000 actual browser renderings
- 5 seconds average per render
- Maintain pool of 5 warm browsers
**Duration:**
- 10,000 × 5 seconds = 50,000 seconds = 13.89 hours
- Billable: 13.89 - 10 = 3.89 hours
- **Duration Cost**: 3.89 × $0.09 = **$0.35**
**Concurrency:**
- Maintain pool of 5 browsers (keep_alive)
- Daily peak: 5 browsers
- Monthly average: 5 browsers
- Billable: 5 - 10 = 0
- **Concurrency Cost**: $0.00
**Total: $0.35/month** for 100k requests!
**ROI**: $0.0000035 per screenshot
---
## Cost Optimization Strategies
### 1. Aggressive Caching
**Strategy**: Cache screenshots/PDFs in KV or R2
**Impact**:
- Reduces browser hours by 80-95%
- Reduces concurrency needs
- Faster response times
**Implementation**:
```typescript
// Check cache first
const cached = await env.KV.get(url, { type: "arrayBuffer" });
if (cached) return new Response(cached);
// Generate and cache
const screenshot = await generateScreenshot(url);
await env.KV.put(url, screenshot, { expirationTtl: 86400 });
```
**Cost Savings**: 80-95% reduction
---
### 2. Session Reuse
**Strategy**: Maintain pool of warm browsers, reuse sessions
**Impact**:
- Reduces cold start time
- Lower concurrency charges
- Better throughput
**Implementation**: See `session-reuse.ts` template
**Cost Savings**: 30-50% reduction
---
### 3. Multiple Tabs
**Strategy**: Use tabs instead of multiple browsers
**Impact**:
- 10-50x reduction in concurrency
- Minimal duration increase
- Much cheaper
**Implementation**:
```typescript
const browser = await puppeteer.launch(env.MYBROWSER);
await Promise.all(urls.map(async url => {
const page = await browser.newPage();
// process
await page.close();
}));
await browser.close();
```
**Cost Savings**: 90%+ reduction in concurrency charges
---
### 4. Appropriate Timeouts
**Strategy**: Set reasonable timeouts, don't wait forever
**Impact**:
- Prevents hanging browsers
- Reduces wasted duration
- Better error handling
**Implementation**:
```typescript
await page.goto(url, {
timeout: 30000, // 30 second max
waitUntil: "networkidle0"
});
```
**Cost Savings**: 20-40% reduction
---
### 5. Request Queueing
**Strategy**: Limit concurrent operations to stay within limits
**Impact**:
- Avoid rate limit errors
- Predictable costs
- Better resource utilization
**Implementation**:
```typescript
import PQueue from "p-queue";
const queue = new PQueue({ concurrency: 5 });
await Promise.all(urls.map(url =>
queue.add(() => processUrl(url))
));
```
**Cost Savings**: Avoids rate limit charges
---
## Monitoring Usage
### Dashboard
View usage in Cloudflare Dashboard:
https://dash.cloudflare.com/?to=/:account/workers/browser-rendering
**Metrics available:**
- Total browser hours used
- REST API requests
- Concurrent browsers (graph)
- Cost estimates
### Response Headers
REST API returns browser time used:
```
X-Browser-Ms-Used: 2340
```
(Browser time in milliseconds for that request)
### Custom Tracking
```typescript
interface UsageMetrics {
date: string;
browserHours: number;
peakConcurrency: number;
requests: number;
cacheHitRate: number;
}
// Track in D1 or Analytics Engine
await env.ANALYTICS.writeDataPoint({
indexes: [date],
blobs: ["browser_usage"],
doubles: [browserHours, peakConcurrency, requests]
});
```
---
## Cost Alerts
### Set Up Alerts
1. **Monitor daily peaks**
```typescript
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.activeSessions.length > 15) {
console.warn("High concurrency detected:", limits.activeSessions.length);
}
```
2. **Track hourly usage**
```typescript
const usage = await getHourlyUsage();
if (usage.browserHours > 1) {
console.warn("High browser usage this hour:", usage.browserHours);
}
```
3. **Set budget limits**
```typescript
const monthlyBudget = 50; // $50/month
const currentCost = await estimateCurrentCost();
if (currentCost > monthlyBudget * 0.8) {
console.warn("Approaching monthly budget:", currentCost);
}
```
---
## Best Practices Summary
1. **Always cache** screenshots/PDFs in KV or R2
2. **Reuse sessions** instead of launching new browsers
3. **Use multiple tabs** instead of multiple browsers
4. **Set appropriate timeouts** to prevent hanging
5. **Monitor usage** in dashboard and logs
6. **Queue requests** to stay within rate limits
7. **Test caching** to optimize hit rate
8. **Profile operations** to identify slow requests
9. **Use incognito contexts** for session isolation
10. **Request higher limits** if needed for production
---
## Common Questions
### Q: Are failed requests billed?
**A**: No. Requests that fail with `waitForTimeout` error are NOT billed.
### Q: How is concurrency calculated?
**A**: Monthly average of daily peak concurrent browsers.
### Q: Can I reduce my bill?
**A**: Yes! Use caching, session reuse, and multiple tabs. See optimization strategies above.
### Q: What if I hit limits?
**A**: Implement queueing, or request higher limits: https://forms.gle/CdueDKvb26mTaepa9
### Q: Is there a free tier?
**A**: Yes! 10 minutes/day browser time, 3 concurrent browsers.
### Q: How do I estimate costs?
**A**: Monitor usage in dashboard, then calculate:
- Duration: (hours - 10) × $0.09
- Concurrency: (avg - 10) × $2.00
---
## References
- **Official Pricing Docs**: https://developers.cloudflare.com/browser-rendering/platform/pricing/
- **Limits Docs**: https://developers.cloudflare.com/browser-rendering/platform/limits/
- **Dashboard**: https://dash.cloudflare.com/?to=/:account/workers/browser-rendering
- **Request Higher Limits**: https://forms.gle/CdueDKvb26mTaepa9
---
**Last Updated**: 2025-10-22