Spaces:
Running
Claude PDF Export Performance Analysis
Problem: Performance Regression After CDN Localization
Test Results
| Test ID | Time | Widget Render | Description |
|---|---|---|---|
| 17:02:05 | 22,923ms | 17,063ms | Before optimization |
| 18:08:55 | 25,469ms | 19,612ms | After browser pool fix |
| 20:35:52 | 27,282ms | 19,410ms | After CDN localization |
Result: CDN localization made performance WORSE (+2,347ms)
Root Cause Analysis
What the CDN Interception Does
From backend logs:
[WIDGET] Chart.js CDN interception: ENABLED (local version: 200.3KB)
[WIDGET] Intercepting Chart.js request: https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js
[WIDGET] Chart.js served from local file (200.3KB)
[WIDGET-PERF] setContent + networkidle0: 1957ms
[WIDGET-PERF] render: 1105ms
Why It Made Things Worse
1. Browser Request Interception Has Overhead
The code uses BrowserContext.setServerInterception() which:
- Adds overhead per network request (interception check + response handling)
- Each widget makes multiple requests (HTML + Chart.js + other resources)
- Total: 15 widgets ร multiple requests ร interception overhead
2. Chart.js CDN Already Fast
Cloudflare CDN caches Chart.js aggressively:
- Chart.js (200KB) likely loads in <100ms from CDN
- Local file serving has similar or worse overhead (filesystem I/O + interception handling)
3. Real Bottleneck is Chart.js EXECUTION
Backend timing breakdown per widget:
setContent + networkidle0: 600-2000ms (includes CDN + interception)
render: 2000-2500ms โ CHART.JS EXECUTION IS THE BOTTLENECK
screenshot: 40-220ms
The render phase (2-2.5 seconds) is where Chart.js:
- Parses the JavaScript code
- Initializes the Chart object
- Computes chart layout
- Draws bars/lines/pies to canvas
- Applies animations and styling
CDN localization only saves the download time, not the execution time.
Concurrency Analysis
With MAX_CONCURRENT=3 and 15 widgets:
Batch 1: widgets 0-2 (~4 seconds)
Batch 2: widgets 3-5 (~4 seconds)
Batch 3: widgets 6-8 (~4 seconds)
Batch 4: widgets 9-11 (~4 seconds)
Batch 5: widgets 12-14 (~4 seconds)
Total: ~20 seconds
This matches the actual test time (19.4 seconds), confirming the bottleneck is per-widget rendering time, not network.
Why CDN Localization Failed
Initial Hypothesis (Wrong)
CDN download (2-3s) โ Widget render (1s)
Local file (0s) + Widget render (1s)
Expected savings: 2-3s per widget
Actual Behavior (Reality)
CDN download (0.1s) + interception overhead (0.2s) + render (2.5s) = 2.8s
Local file (0.1s) + interception overhead (0.2s) + render (2.5s) = 2.8s
Savings: 0s, but added interception overhead = WORSE
Cloudflare CDN already caches Chart.js efficiently, so the download time was already minimal.
What the Interception Actually Does
From server.js:
// BrowserContext-level request interception
await browserContext.setServerInterception({
urlPattern: '**/Chart.js/**/*.js',
handler: async (route) => {
const chartJsContent = fs.readFileSync('/app/lib/chart.umd.js', 'utf8');
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: chartJsContent
});
}
});
The interception handler:
- Catches the Chart.js request
- Reads local file (filesystem I/O)
- Returns response to browser
Each step adds latency, and with 15 widgets making these requests, the overhead compounds.
Performance Bottleneck Breakdown
Current Bottleneck Distribution
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Widget Render Time: ~4 seconds per widget โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ setContent + networkidle: 600-2000ms (network + DOM) โ
โ Chart.js DOWNLOAD: 100-200ms (already fast) โ
โ Chart.js EXECUTION: 2000-2500ms โ BOTTLENECK โ
โ Screenshot: 40-220ms โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Where the Time Goes
Chart.js Initialization (~500ms)
- Parse JavaScript code
- Set up Chart namespace and utilities
- Configure defaults and helpers
Chart Computation (~1000ms)
- Calculate scales and axes
- Compute bar/line/pie positions
- Apply data transformations
Canvas Drawing (~1000ms)
- Draw grid lines and labels
- Render bars/lines/pies
- Apply colors and gradients
- Draw legends and tooltips
What Doesn't Work
โ CDN Localization
Reason: Cloudflare CDN already fast; interception overhead negates benefit
โ Increasing MAX_CONCURRENT
Reason: Each widget already takes CPU time; more concurrency = more CPU contention
โ Disabling Chart.js Animations
Reason: Already disabled in current implementation
What Could Work (Future Optimization)
Option 1: Chart.js Worker Pool
Pre-render charts in web workers to avoid blocking:
- Complex to implement
- Limited benefit (still need to wait for rendering)
Option 2: Chart.js Caching
Cache rendered chart images to avoid re-rendering identical charts:
- Cache key: (chart type, data JSON, dimensions)
- Benefit: Repeated charts render instantly
- Drawback: First render still slow
Option 3: Use Lightweight Chart Libraries
Replace Chart.js with lighter alternatives:
- D3.js: More flexible but similarly complex
- Lightweight charting libraries: May not support all chart types
- Custom canvas rendering: Most work, most control
Option 4: Accept Current Performance
Rationale: 19 seconds for 15 charts is reasonable
- Each chart takes ~1.3 seconds average
- CDN overhead with interception: 0ms (interception overhead negates benefit)
- Actual per-widget time: ~1.3 seconds
Recommendation
Do not deploy CDN localization to production.
Instead:
- Keep current CDN approach: Cloudflare CDN is already efficient
- Document current performance: 19 seconds for 15 charts is acceptable
- Monitor for regression: Track performance over time
- Consider user experience: Add progress indicator if not already present
Technical Details
Files Modified
backend-service/Dockerfile- Added Chart.js librarybackend-service/server.js- Added CDN interception handler
Rollback Steps
To revert to pre-optimization state:
# Remove Chart.js library
rm backend-service/lib/chart.umd.js
# Remove Dockerfile COPY line
# Remove server.js interception handler
# Rebuild and restart
docker build -t backend-service .
docker restart pdf-test
Performance Metrics
| Metric | Before | After | Change |
|---|---|---|---|
| Total time | 22.9s | 27.3s | +4.4s (worse) |
| Widget render | 17.1s | 19.4s | +2.3s (worse) |
| Per-widget avg | ~1.1s | ~1.3s | +0.2s (worse) |
Conclusion
The CDN localization optimization was based on the incorrect assumption that Chart.js download time was the bottleneck. In reality:
- Cloudflare CDN already serves Chart.js quickly (<100ms)
- Chart.js execution is the real bottleneck (2-2.5 seconds)
- Request interception adds overhead that negates any benefit
The optimization should be reverted, and future optimization efforts should focus on:
- Chart.js execution optimization (difficult)
- Caching rendered charts (complex)
- Accepting current performance (practical)
Analysis Date: 2026-06-23 Analyst: AI Assistant (Claude Code)