ब्लॉग

Cloudflare Workers में PDF generation धीमा है? 5 मिनट में bottleneck खोजें

Workers बहुत तेज हैं, जब तक आप उन पर long-lived servers के लिए बना PDF stack नहीं रख देते। Edge पर PDF धीमा होने की असली वजहें और उनसे बचने के तरीके।

आपने invoice, label या receipt service को Cloudflare Workers पर इसलिए शिफ्ट किया क्योंकि बाकी stack भी वहीं है। Latency की गणित शानदार लगती है: nearest colo तक 5 ms, 1 ms CPU, और request खत्म।

फिर PDF generation आता है। p99 800 ms हो जाता है, 50 MB worker bundle warnings दिखती हैं, और लगता है जैसे runtime गलत चुन लिया गया। अधिकतर मामलों में समस्या Workers नहीं है; समस्या वह PDF stack है जो long-lived server के लिए बना था, isolate runtime के लिए नहीं।

Workers Lambda नहीं हैं, और यह फर्क जरूरी है

Cloudflare Workers serverless containers नहीं हैं। वे V8 isolates हैं, जिनकी सीमाएं अलग हैं:

  • CPU time limit: Free plan पर 50 ms per request, Workers Paid Bundled पर 30 seconds, Unbound पर 5 minutes. Wall time unbounded हो सकता है, लेकिन billable है।
  • Memory cap: 128 MB per isolate.
  • Bundle size: Free plan पर 1 MB, paid पर 10 MB.
  • Filesystem नहीं। fs.readFileSync नहीं है; सब memory या fetch से आता है।
  • Native binaries नहीं। केवल pure JavaScript / WebAssembly; node-canvas, native zlib या Ghostscript shell-out नहीं।
  • Cold start लगभग 5 ms। बेहद तेज, लेकिन इसलिए कि boot करने के लिए भारी चीज नहीं है।

“Workers में PDF धीमा है” वाली अधिकतर समस्याएं इन्हीं constraints में से किसी एक को तोड़ती हैं, खासकर CPU या bundle size।

सच में धीमी करने वाली पांच चीजें

1. Chromium-based renderer को Workers में लाना

यह रास्ता बंद है। Puppeteer को लगभग 250 MB Chromium और real OS चाहिए। Cloudflare Browser Rendering API या Browserless जैसे services काम कर सकते हैं, लेकिन वे Workers नहीं हैं; Worker उनसे call करता है, और आपको करीब 500 ms round-trip + render time चुकाना पड़ता है।

अगर आपका “Worker PDF” असल में “Worker remote browser service को call करता है” है, तो latency floor करीब 500 ms है। यह Workers की समस्या नहीं; यह browser tax है।

Diagnosis: code में fetch("https://browser-rendering.cloudflare.com/...") या ऐसा कुछ खोजें। अगर है, तो आप upstream latency माप रहे हैं, Worker latency नहीं।

2. JavaScript में layout करना

अगर आपने box positions और text wrapping खुद JS में compute करने वाला layout engine लिखा है, तो CPU limit लगेगी। JS तेज है, लेकिन 30+ elements और wrapping के साथ layout Workers Free पर आसानी से 50 ms से ऊपर चला जाता है, और Bundled पर 100-300 ms भी हो सकता है।

यह pipeline महंगी है:

JSON → JS layout pass → SVG generation → SVG-to-PDF library → emit

एक ही data पर चार CPU-bound passes, हर pass में GC pressure और intermediate trees।

Diagnosis: किसी render का wrangler tail देखें। अगर किसी I/O से पहले CPU 50 ms से ऊपर है, तो bottleneck compute है।

3. हर request पर fonts load करना

Fonts 50-250 KB के होते हैं। अगर renderer हर render में KV / R2 से font पढ़ता है, तो हर font के लिए network round-trip लगेगा। पांच fonts मतलब render शुरू होने से पहले 50-150 ms।

Diagnosis: font-loading code में timing जोड़ें। अगर वह p50 खा रहा है, समस्या यही है।

Fix: font को module init पर एक बार load करें, Worker file के top level पर, request handler के अंदर नहीं। isolate अपने जीवनकाल में bytes cache रखता है।

// Module-init: runs ONCE per isolate
import notoSans from "./fonts/noto-sans.woff2";
const FONT_BYTES = new Uint8Array(notoSans);

export default {
  async fetch(request) {
    // Per-request: zero font I/O
    return renderPdf({ fontBytes: FONT_BYTES, ...data });
  }
};

Bundler अगर font को bytes में inline कर दे तो और अच्छा: कोई I/O नहीं।

4. ऐसी JS PDF library इस्तेमाल करना जो Workers के लिए नहीं बनी

pdfkit, pdf-lib, jsPDF Workers में चल सकते हैं, लेकिन कीमत है:

  • pdfkit को Node Buffer shims चाहिए। चल जाता है, पर लगभग 500 KB बढ़ता है और compute करीब 30% धीमा हो सकता है।
  • pdf-lib existing PDFs edit करने में अच्छा है, scratch से emit करने में नहीं; abstraction हर page पर लगभग 10 ms overhead जोड़ सकती है।
  • jsPDF browser-first है; Buffer issue और बड़ा API surface, जिसे tree-shake करना मुश्किल है।

अगर आपका काम “JSON पढ़ो, PDF bytes लिखो” है, तो direct PDF emit करने वाला purpose-built engine 5-20 गुना तेज हो सकता है। Rust या C++ से compiled WebAssembly tight loops के लिए अच्छा fit है।

5. Bundle जो चुपचाप 4 MB हो गया

Workers Free में bundle cap 1 MB है, Workers Bundled में 10 MB. कई teams को यह तब पता चलता है जब wrangler deploy “Script exceeds size limit” कहता है। कभी-कभी cold start पहले ही धीमा दिखने लगता है क्योंकि V8 को बहुत code compile करना पड़ता है।

Deploy output में wrangler bundle size दिखाता है। 500 KB से ऊपर कुछ भी जांचने लायक है:

  • Bundled fonts. उन्हें Workers Assets में रखें और module init पर एक बार fetch करें।
  • node: shim layer. Source map में __cf_KV या polyfills: दिखे तो bundler अनावश्यक Node APIs shim कर रहा है।
  • Unused dependencies. Wrangler 4+ पर npm run build -- --analyze treemap देता है।

Workers में तेज PDF कैसा दिखता है

Structured documents के लिए बना edge renderer, जैसे gPdf, आम तौर पर इस range में होता है:

Metric Typical Why
Cold-start 5-20 ms V8 isolate boot + WASM module first-load
Per-render CPU 1-4 ms WASM tight loop, no GC pressure
Per-render wall 3-8 ms CPU + a few microseconds of crypto for PDF object IDs
Bundle size 4-6 MB Renderer + bundled fonts (Latin + CJK NotoSans)
Memory peak 8-20 MB Document tree + emitted PDF buffer

इसके उलट “Worker से remote Puppeteer/browser rendering” path में p50 500-1000 ms, browser memory कहीं और 1-2 GB, और upstream cost करीब $0.001/render होती है।

Quick triage

अभी PDF slow है तो पहले यह checklist चलाएं:

  1. Time कहां जा रहा है? wrangler tail में timestamps जोड़ें। CPU, upstream fetch और cold start अलग करें।
  2. क्या JS layout चल रहा है? अगर हां, यही CPU का बड़ा हिस्सा है। Same-isolate renderer या precomputed layout अपनाएं।
  3. क्या fonts per request load हो रहे हैं? Font loading module init में ले जाएं।
  4. क्या external browser call कर रहे हैं? Latency floor वही service है। Same-isolate renderer पर आएं।
  5. Bundle 1 MB से बड़ा है? Cold start bundle size के साथ बढ़ता है। Unused deps काटें।

Workers पर सबसे तेज PDF वह है जिसमें document data से PDF bytes तक सब एक ही fetch handler call में हो, बिना internal fetch() और बिना भारी JS layout।

छोटी बात

Cloudflare Workers single-digit milliseconds में PDF render कर सकते हैं, लेकिन renderer isolate-shaped runtime के लिए बना होना चाहिए। Node-oriented JS PDF libs, remote browser services, per-request fonts और JavaScript layout passes p50 को origin से भी धीमा बना सकते हैं।

अगर आप यह engineering खुद नहीं करना चाहते, gPdf Playground उसी edge runtime पर चलता है। Render PDF दबाएं और Network tab देखें; वही Workers की असली speed है जब pipeline runtime से लड़ नहीं रही होती।