Blog

PDF lambat di Cloudflare Workers? Diagnosis dalam 5 menit

Workers cepat sampai Anda memasukkan stack PDF untuk server long-lived. Ini bottleneck nyata di edge dan cara menghindarinya.

Anda memindahkan layanan invoice, label, atau receipt ke Cloudflare Workers karena stack lain sudah berjalan di sana. Hitungan latensinya terlihat indah: 5 ms ke colo terdekat, 1 ms CPU, request selesai.

Lalu PDF generation masuk. p99 menjadi 800 ms, worker bundle memberi warning 50 MB, dan muncul rasa bahwa runtime-nya salah. Biasanya masalahnya bukan Workers, tetapi stack PDF yang dibuat untuk server long-lived lalu dipaksa masuk ke isolate runtime.

Workers bukan Lambda

Cloudflare Workers bukan serverless containers. Mereka adalah V8 isolates dengan batas yang berbeda:

  • CPU time limit: 50 ms per request di Free plan, 30 seconds di Workers Paid Bundled, 5 minutes di Unbound. Wall time bisa lebih panjang, tetapi billable.
  • Memory cap: 128 MB per isolate.
  • Bundle size: 1 MB di Free, 10 MB di Paid.
  • Tidak ada filesystem. Tidak ada fs.readFileSync; semua di memory atau fetch.
  • Tidak ada native binaries. Hanya pure JavaScript / WebAssembly; tidak ada node-canvas, native zlib, atau Ghostscript via shell.
  • Cold start sekitar 5 ms. Sangat cepat karena tidak ada komponen besar yang perlu boot.

Sebagian besar kasus “PDF lambat di Workers” melanggar salah satu batas ini, terutama CPU atau bundle size.

Lima hal yang benar-benar lambat

1. Membawa renderer berbasis Chromium ke Workers

Ini tidak jalan. Puppeteer butuh sekitar 250 MB Chromium dan OS sungguhan. Cloudflare Browser Rendering API atau Browserless bisa dipakai, tetapi bukan Workers; itu service terpisah yang dipanggil dari Worker, dengan sekitar 500 ms round-trip plus render time.

Jika “PDF di Worker” Anda sebenarnya “Worker memanggil browser remote”, latency floor Anda sekitar 500 ms. Itu bukan masalah Worker; itu browser tax.

Diagnosis: cari fetch("https://browser-rendering.cloudflare.com/...") atau sejenisnya. Jika ada, Anda mengukur upstream latency, bukan Worker.

2. Melakukan layout di JavaScript

Kalau Anda menulis layout engine JS sendiri untuk menghitung box position dan text wrapping, Anda akan kena CPU limit. JS cepat, tetapi layout untuk 30+ element dengan wrapping mudah melewati 50 ms di Workers Free dan bisa 100-300 ms di Bundled.

Pipeline seperti ini mahal:

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

Ada empat CPU-bound pass di data yang sama, semuanya JS, dengan GC pressure dan intermediate tree allocation.

Diagnosis: lihat wrangler tail untuk satu render. Jika CPU sudah lebih dari 50 ms sebelum I/O, bottleneck-nya compute.

3. Memuat font di setiap request

Font berukuran 50-250 KB. Jika renderer membaca dari KV / R2 di tiap render, ada network round-trip per font per request. Lima font berarti 50-150 ms sebelum render mulai.

Diagnosis: tambahkan timing di font-loading code. Jika mendominasi p50, itu masalahnya.

Fix: load font sekali di module init, di top level file Worker, bukan di request handler. Isolate menyimpan bytes selama hidupnya.

// 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 });
  }
};

Kalau bundler meng-inline font sebagai bytes, lebih baik lagi: tidak ada I/O.

4. Memakai JS PDF library yang tidak dirancang untuk Workers

pdfkit, pdf-lib, dan jsPDF bisa berjalan di Workers, tetapi ada konsekuensi:

  • pdfkit butuh Node Buffer shims. Bisa, tetapi menambah sekitar 500 KB dan memperlambat compute sekitar 30%.
  • pdf-lib bagus untuk mengedit PDF yang sudah ada, kurang ideal untuk emit dari nol; abstraction layer bisa menambah sekitar 10 ms per page.
  • jsPDF browser-first, punya masalah Buffer yang sama, dan API surface besar yang sulit di-tree-shake.

Untuk pipeline “baca JSON, tulis PDF bytes”, engine khusus yang emit PDF langsung biasanya 5-20 kali lebih cepat. WebAssembly dari Rust atau C++ cocok untuk tight loops.

5. Bundle diam-diam menjadi 4 MB

Workers Free membatasi bundle 1 MB; Workers Bundled 10 MB. Banyak tim baru tahu ketika wrangler deploy menolak dengan “Script exceeds size limit”. Sebagian melihatnya lebih awal sebagai cold start lambat karena V8 harus compile terlalu banyak kode.

wrangler menampilkan bundle size di deploy output. Apa pun di atas 500 KB layak diperiksa:

  • Bundled fonts. Pindahkan ke Workers Assets dan fetch sekali di module init.
  • Layer shim node:. Jika source map menunjukkan __cf_KV atau polyfills:, bundler sedang men-shim Node APIs yang mungkin tidak Anda butuhkan.
  • Dependencies tidak terpakai. Di Wrangler 4+, npm run build -- --analyze memberi treemap.

Seperti apa PDF cepat di Workers

Renderer edge-native untuk structured documents, seperti gPdf, biasanya berada di angka ini:

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

Bandingkan dengan path “Worker memanggil Puppeteer/browser rendering remote”: p50 500-1000 ms, 1-2 GB memory browser di service lain, dan upstream cost sekitar $0.001/render.

Triage cepat

Kalau PDF Anda lambat di Workers sekarang, jalankan checklist ini dulu:

  1. Waktu habis di mana? Tambahkan timestamps di wrangler tail. Pisahkan CPU, upstream fetch, dan cold start.
  2. Apakah ada JS layout? Jika ya, itu kemungkinan besar CPU utama. Gunakan renderer di isolate yang sama.
  3. Apakah font dimuat per request? Pindahkan ke module init.
  4. Apakah memanggil browser eksternal? Maka latency floor adalah service itu. Pindah ke same-isolate rendering.
  5. Bundle di atas 1 MB? Cold start naik bersama bundle size. Pangkas unused deps.

PDF tercepat di Workers adalah ketika document data berubah menjadi PDF bytes seluruhnya dalam satu panggilan fetch handler, tanpa fetch() internal dan tanpa layout JS berat.

Versi singkat

Cloudflare Workers bisa render PDF dalam single-digit milliseconds, tetapi hanya jika renderer dibuat untuk isolate runtime. JS PDF libs untuk Node, browser service remote, font per request, dan layout di JavaScript akan membuat p50 lebih lambat dari origin.

Jika Anda tidak ingin membangun jalur itu sendiri, coba gPdf Playground. Ia berjalan di runtime edge yang sama. Tekan Render PDF, lihat tab Network, dan Anda akan melihat kemampuan Workers saat pipeline tidak melawan environment-nya.