Spaces:
Running
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: | |
| ```typescript | |
| // 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: | |
| 1. Catches the Chart.js request | |
| 2. Reads local file (filesystem I/O) | |
| 3. 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 | |
| 1. **Chart.js Initialization** (~500ms) | |
| - Parse JavaScript code | |
| - Set up Chart namespace and utilities | |
| - Configure defaults and helpers | |
| 2. **Chart Computation** (~1000ms) | |
| - Calculate scales and axes | |
| - Compute bar/line/pie positions | |
| - Apply data transformations | |
| 3. **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: | |
| 1. **Keep current CDN approach**: Cloudflare CDN is already efficient | |
| 2. **Document current performance**: 19 seconds for 15 charts is acceptable | |
| 3. **Monitor for regression**: Track performance over time | |
| 4. **Consider user experience**: Add progress indicator if not already present | |
| ## Technical Details | |
| ### Files Modified | |
| 1. `backend-service/Dockerfile` - Added Chart.js library | |
| 2. `backend-service/server.js` - Added CDN interception handler | |
| ### Rollback Steps | |
| To revert to pre-optimization state: | |
| ```bash | |
| # 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: | |
| 1. **Cloudflare CDN already serves Chart.js quickly** (<100ms) | |
| 2. **Chart.js execution is the real bottleneck** (2-2.5 seconds) | |
| 3. **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) | |