আপনি invoice, label বা receipt service Cloudflare Workers-এ এনেছেন, কারণ বাকি stack-ও edge-এ চলে। Latency হিসাব সুন্দর: nearest colo 5 ms, CPU 1 ms, request শেষ।
তারপর PDF generation আসে। p99 800 ms, worker bundle warning 50 MB, আর মনে হয় runtime বোধহয় ভুল। বেশিরভাগ সময় সমস্যা Workers নয়; সমস্যা হলো server-style PDF stack-কে isolate runtime-এ বসানো।
Workers Lambda নয়, তাই diagnosis আলাদা
Cloudflare Workers serverless container নয়। এগুলো V8 isolates, যার সীমা স্পষ্ট:
- CPU time limit: Free plan-এ request প্রতি 50 ms, Workers Paid Bundled-এ 30 seconds, Unbound-এ 5 minutes. Wall time দীর্ঘ হতে পারে, কিন্তু billable।
- Memory cap: isolate প্রতি 128 MB.
- Bundle size: Free plan-এ 1 MB, paid-এ 10 MB.
- Filesystem নেই।
fs.readFileSyncনেই; সব memory বা fetch। - Native binary নেই। Pure JavaScript / WebAssembly only;
node-canvas, native zlib, Ghostscript shell-out নেই। - Cold start প্রায় 5 ms। এত দ্রুত কারণ boot করার মতো ভারী কিছু নেই।
“Workers-এ PDF slow” সমস্যার বেশিরভাগই এই constraint ভাঙে, সাধারণত CPU বা bundle size।
সত্যিকারের ধীর করার পাঁচ কারণ
1. Chromium renderer Workers-এ টানা
এটা চলে না। Puppeteer-এর প্রায় 250 MB Chromium এবং real OS দরকার। Cloudflare Browser Rendering API বা Browserless কাজ করে, কিন্তু সেগুলো Workers নয়; Worker শুধু remote service call করে, যেখানে প্রায় 500 ms round-trip + render time লাগে।
আপনার “Worker PDF” যদি আসলে “Worker remote browser API call করছে” হয়, latency floor প্রায় 500 ms। এটা Worker সমস্যা নয়; browser tax।
Diagnosis: code-এ fetch("https://browser-rendering.cloudflare.com/...") বা মিল আছে কি দেখুন। থাকলে আপনি upstream latency মাপছেন।
2. JavaScript-এ layout করা
নিজের JS layout engine লিখে box position আর text wrapping করলে CPU limit-এ ধাক্কা লাগবে। JS দ্রুত, কিন্তু 30+ element ও 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 pass, সব JS-এ, সবখানে GC pressure এবং intermediate tree allocation।
Diagnosis: একটি render-এর wrangler tail দেখুন। I/O শুরু হওয়ার আগে CPU 50 ms ছাড়ালে bottleneck compute।
3. প্রতিটি request-এ font load করা
Font সাধারণত 50-250 KB। renderer যদি প্রতিটি render-এ KV / R2 থেকে font পড়ে, প্রতিটি font-এর জন্য network round-trip লাগে। পাঁচটি font মানে render শুরুর আগেই 50-150 ms।
Diagnosis: font-loading code-এ timing দিন। p50 সেখানে গেলে সমস্যা এখানেই।
Fix: Worker file-এর top level-এ module init সময় font একবার load করুন, 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. Workers-এর জন্য না বানানো JS PDF library ব্যবহার
pdfkit, pdf-lib, jsPDF Workers-এ চলে, কিন্তু cost আছে:
pdfkitNodeBuffershims চায়। সম্ভব, কিন্তু প্রায় 500 KB যোগ করে এবং compute প্রায় 30% ধীর করে।pdf-libexisting PDFs edit করার জন্য ভালো; scratch থেকে emit করলে abstraction প্রতি page-এ প্রায় 10 ms overhead যোগ করতে পারে।jsPDFbrowser-first; Buffer issue আছে এবং API surface বড়, tree-shake করা কঠিন।
যদি pipeline মূলত “JSON পড়ে PDF bytes লেখা” হয়, সরাসরি PDF emit করা purpose-built engine সাধারণত 5-20 গুণ দ্রুত। Rust বা C++ থেকে WebAssembly tight loop-এ ভালো কাজ করে।
5. Bundle গোপনে 4 MB হয়ে যাওয়া
Workers Free bundle limit 1 MB, Workers Bundled 10 MB. অনেক team wrangler deploy-এর “Script exceeds size limit” error-এ প্রথম বুঝতে পারে। কেউ কেউ আগেই cold start slow দেখে, কারণ 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 -- --analyzetreemap দেয়।
Workers-এ দ্রুত PDF দেখতে কেমন
Structured documents-এর জন্য edge-native renderer, যেমন gPdf, সাধারণত এমন:
| 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 call করে” path: p50 500-1000 ms, অন্য service-এ 1-2 GB browser memory, upstream cost প্রায় $0.001/render।
দ্রুত triage
এখনই slow PDF হলে আগে এই checklist চালান:
- সময় কোথায় যাচ্ছে?
wrangler tail-এ timestamps দিন। CPU, upstream fetch, cold start আলাদা করুন। - JS layout চলছে? হলে সেটাই CPU-এর বড় অংশ। same-isolate renderer ব্যবহার করুন।
- Font প্রতি request load হচ্ছে? module init-এ সরান।
- External browser call করছেন? latency floor সেই service. same-isolate rendering-এ যান।
- Bundle 1 MB-এর বেশি? cold start bundle size-এর সঙ্গে বাড়ে। unused deps কাটুন।
Workers-এ দ্রুততম PDF হলো যেখানে document data থেকে PDF bytes তৈরি হয় এক fetch handler call-এর মধ্যে, কোনো internal fetch() নেই, heavy JS layout নেই।
সংক্ষিপ্ত কথা
Cloudflare Workers single-digit milliseconds-এ PDF render করতে পারে, যদি renderer isolate-shaped runtime-এর জন্য বানানো হয়। Node-ভিত্তিক JS PDF libs, remote browser services, per-request font loading এবং JavaScript layout p50-কে origin-এর থেকেও ধীর করতে পারে।
নিজে এই engineering করতে না চাইলে gPdf Playground দেখুন। এটি একই ধরনের edge runtime-এ চলে। Render PDF চাপুন, Network tab দেখুন; pipeline runtime-এর সঙ্গে লড়াই না করলে Worker এই গতিই দিতে পারে।