api-server / PERFORMANCE_ANALYSIS.md
XWX-AI's picture
fix(widget): DOCXๅ›พ่กจfallbackๆˆๆ•ฐๆฎ่กจๆ ผๆ นๅ› ไฟฎๅค โ€” widgetๆธฒๆŸ“ๆต่งˆๅ™จๆฑ +CDPๆœ‰้™่ถ…ๆ—ถ+ๆธฒๆŸ“็กฌ่ถ…ๆ—ถ+้˜Ÿๅˆ—่ถ…ๆ—ถๅ–ๆถˆ+BrowserPool่กฅไฝbug+body-parser JSONๅ…œๅบ•, bump v2.1.11
24a2ddf
|
Raw
History Blame Contribute Delete
7.95 kB

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:

  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:

# 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)