Skip to content

Cloudflare Workers'a WASM Görsel Yeniden-Boyutlama Modülü Yüklemek

Rust + WASM ile yazılmış bir görsel yeniden-boyutlama handler'ının Cloudflare Workers'ın binary-size, bellek ve CPU tavanlarına sığıp sığmayacağını POC öncesi inceleyen bir keşif yazısı.

Body'den JPEG alıp yeniden boyutlandırılmış JPEG döndüren bir POST /resize?w=800&q=85 handler'ı, Cloudflare Workers + WASM'ın bir görsel hattı sunucusu olarak en sade, oyuncak olmayan stres testidir. Tasarım üç edge-platform limitine aynı anda yüklenir: derlenmiş binary boyutu, isolate başına bellek ve istek başına CPU süresi. Bu yazı POC çalışmadan önce hangi limitin önce, hangi sırayla ve hangi sinyalle ısıracağını adlandırır.

İş

Bir görsel yeniden-boyutlama modülü, üç sert limiti birden zorladığı için CF Workers + WASM'ın dürüst stres testidir: derlenmiş binary boyutu, istek başına bellek ve istek başına CPU. Bu yazı kurulumu gezer ve POC çalışmadan önce her limitin nerede görüneceğini adlandırır. Okuyucu, kendi hattı için Worker mı, Lambda mı, yoksa yönetilen bir görsel ürünü mü seçeceğini bilerek ayrılır.

Bu yazı WebAssembly 101'in 3. bahsinin içinde çalışır: WASM'ın edge'de taşınabilir hesaplama primitifi olması. O çerçeve burada bozulmuyor; yeniden türetmeyeceğim. Konu tersi yöndedir: o primitife gerçek bir görsel codec'i yüklediğinizde ve body-in, body-out trafik gönderdiğinizde ne olur.

Edge PoP, TLS'i sonlandırır ve bir V8 isolate'ına yönlendirir. WASM örneğini WASI çalışma ortamı değil, isolate barındırır. İstek body'sinin, çözülmüş piksel buffer'ın ve kodlanmış yanıtın her baytı, isolate'ın eşzamanlı çağrılarla paylaştığı tek bir 128 MB bütçesinin içinde oturur.

Dokümanların Vaadi

Binary boyutu. Cloudflare, Free tier için 3 MB, Paid tier için 10 MB gzip sonrası limit yayınlar; her ikisinde de sıkıştırmadan önce 64 MB tavan vardır. Deploy anında önemli olan sayı sıkıştırılmış olandır ve wrangler deploy --outdir bundled/ --dry-run bunu Total Upload: N KiB / gzip: M KiB olarak yazdırır. Bundle içindeki sıkıştırılmamış wasm dosyası gzip raporunun düşündürdüğünden belirgin biçimde büyük olabilir. Worker size limits sayfasına bakın.

Isolate başına bellek. Her isolate, JavaScript heap'i ve WebAssembly ayırmaları dahil 128 MB'a kadar kullanabilir. Cloudflare bunu çağrı başına değil, isolate başına olarak belgeler; tek bir isolate birçok eşzamanlı isteği işler ve bu isteklerin tüm ayırmaları aynı 128 MB zarfında oturur. Eşzamanlı iki 12-megapikselli çözme, tek senkron bir çözmenin yapmayacağından çok daha erken tahliye riski taşır. Bkz. Memory per isolate.

İstek başına CPU süresi. Free tier, HTTP isteği başına 10 ms CPU verir; bu ilk piksel kodlanmadan önce önemsiz olmayan her yeniden-boyutlamayı diskalifiye eder. Paid tier bunu 30 saniyelik varsayılana taşır ve 5 dakikaya kadar yapılandırılabilir. CPU süresi ağ beklemesini kapsamaz; yani tüm yeniden-boyutlama maliyeti doğrudan bu bütçeye düşer. Bkz. CPU time.

Başlangıç süresi. Bir Worker, üst-düzey kodunu 1 saniye içinde ayrıştırıp yürütmek zorundadır; aksi halde wrangler deploy'u 10021 hatasıyla reddeder. Büyük WASM bundle'ları bu bütçeyi yer çünkü ayrıştırıcı başlangıçta tüm modülü gezmek zorundadır. Bu, çağrı başına bir çalışma zamanı maliyeti değil, deploy anı doğrulamasıdır. Bkz. Worker startup time.

Isolate modeli. Workers, Chromium ve Node.js'in kullandığı motor olan V8 üzerinde çalışır. WASM, V8 isolate'ı içinde WebAssembly.instantiate() ile yüklenir; altta bir WASI çalışma ortamı yoktur. Cloudflare kendi dokümanında WASI desteğini deneysel olarak, yalnızca bazı syscall'lar uygulanmış şekilde tanımlar. Bir görsel handler'ı için bu uygundur çünkü iş byte buffer'lar üzerinde saf hesaplamadır; dosya sistemi ya da ağ gerekmez. Bkz. How Workers works ve WebAssembly runtime APIs.

Kurulum

workers-rs yolu belgelidir ve yayın çizgisi aktiftir; v0.8.1, 17 Nisan 2026'da yayınlandı. wasm32-unknown-unknown üçlüsünü hedefleyin, şablondan üretin ve JS shim'ini worker-build'e bırakın:

bash
rustup target add wasm32-unknown-unknowncargo install cargo-generatecargo generate cloudflare/workers-rs

Görsel-işleme bağımlılıklarını ekleyin. photon-rs şu an crates.io'da 0.3.3 yayınlıyor; bu sürümü sabitleyin. image crate'i photon-rs tarafından transitif olarak çekilir — photon-rs 0.3.3 kendi Cargo.toml'unda image ^0.24.8 sabitler — dolayısıyla doğrudan bağımlılık olarak eklemek çift-sürüm bundle riski yaratır. photon-rs'in açmadığı bir encoder gerekmedikçe doğrudan pin'i düşürün:

toml
[dependencies]worker = "0.8"photon-rs = "0.3"

wrangler yapılandırması Worker'ı baştan Paid-tier davranışına sabitler. cpu_ms = 100 ayarı kasıtlıdır; maliyet zarfını 30 saniyelik varsayılanın altına saklamak yerine erken ortaya çıkarır:

toml
name = "wasm-resize"main = "build/worker/shim.mjs"compatibility_date = "2026-04-22"
[build]command = "cargo install -q worker-build && worker-build --release"
[limits]cpu_ms = 100

R2 yok, KV yok, auth yok. Kapsam body-in, body-out.

Handler

Handler, istek body'sini bir PhotonImage'a çözer, en-boy oranını korumak için width sorgu parametresinden hedef yüksekliği hesaplar, Lanczos3 çekirdeği ile yeniden boyutlandırır ve 85 kalitesinde JPEG olarak yeniden kodlar:

rust
use worker::*;use photon_rs::{    native::open_image_from_bytes,    transform::{resize, SamplingFilter},    PhotonImage,};
#[event(fetch)]pub async fn main(req: Request, env: Env, _ctx: Context) -> Result<Response> {    Router::new()        .post_async("/resize", |mut req, _ctx| async move {            let url = req.url()?;            let w: u32 = url                .query_pairs()                .find(|(k, _)| k == "w")                .and_then(|(_, v)| v.parse().ok())                .unwrap_or(800);
            let body = req.bytes().await?;
            // Çöz -> yeniden boyutlandır -> yeniden kodla. Her byte,            // eşzamanlı çağrılarla paylaşılan 128 MB isolate bütçesi içindedir.            let img: PhotonImage = open_image_from_bytes(&body)                .map_err(|e| Error::RustError(format!("decode: {e:?}")))?;            let h = ((img.get_height() as f32) * (w as f32)                / (img.get_width() as f32)) as u32;            let resized = resize(&img, w, h, SamplingFilter::Lanczos3);            let out = resized.get_bytes_jpeg(85);
            let mut headers = Headers::new();            headers.set("content-type", "image/jpeg")?;            Ok(Response::from_bytes(out)?.with_headers(headers))        })        .run(req, env)        .await}

RouteContext üzerindeki .data() ve .get_env() erişimcileri workers-rs'in daha eski sürümlerinde doğrudan alan erişimi lehine eskitildi; yukarıdaki örnek her ikisinden de kaçınır. Bu kodun üstüne POC yayınlamadan önce open_image_from_bytes, get_bytes_jpeg ve SamplingFilter::Lanczos3 imzalarını, çalışma anında Cargo.toml'da sabitli photon-rs sürümüne karşı yeniden doğrulayın. Bir imza kaymışsa uydurma shim trait'leri icat etmeyin; çağrı yerini düzeltin.

Limitlerin Isırdığı Yer

Gelecek zaman, baştan sona. Bunlar dokümanlara bağlı tahminlerdir; POC doğrulayacak ya da düzeltecektir.

Binary boyutu

photon-rs; image ^0.24.8, imageproc ^0.23.0, palette, rusttype, perlin2d ve rand'ı çeker. Transitif graf küçük değildir. wasm-opt -Oz ve gzip sonrası bundle muhtemelen tek haneli MB'larda oturacaktır. Free tier'daki okuyucular (3 MB) sığmayacaktır. Paid tier (10 MB) gerçekçidir ama dardır; herhangi bir kardeş crate eklerseniz özellikleri kırpmayı bekleyin. Yaklaşıma bağlanmadan önce gerçek gzip boyutunu wrangler deploy --dry-run ile doğrulayın.

İstek başına bellek

4000 x 3000 bir JPEG, yaklaşık 48 MB RGBA8'e çözülür (4000 x 3000 x 4). Yeniden-boyutlama scratch'i bunu kısa süreliğine iki katına çıkarabilir. 128 MB tavanı aynı isolate'taki eşzamanlı çağrılar arasında paylaşılır; bu yüzden eşzamanlı iki 12-megapikselli çözme, tek tek sığsa bile tahliye riski taşır. Bir kullanıcı-yükleme uç noktasında ya edge'de azami megapiksel sayısını dayatın ya da Worker'ın önüne isolate başına eşzamanlılığı kısan bir kuyruk koyun.

CPU süresi

Free tier'ın 10 ms CPU bütçesi tüm tasarımı diskalifiye eder. Çok-megapikselli bir görselin Lanczos3 yeniden-boyutlaması, hiçbir gerçekçi isolate'ta 10 ms'de bitmeyecektir. Paid'de 30 saniyelik varsayılan tek bir görsel için bol bol yeter; cpu_ms = 100 ile başlamak handler'ı dürüst tutmaya zorlar. POC çalışıp görsel başına gerçek maliyeti ortaya koyduğunda limiti bilinçli olarak yükseltin, varsayılan üzerinden değil.

Cold start

1 saniyelik başlangıç bütçesi bir deploy anı doğrulayıcısıdır. WASM bundle'ı üst-düzey ayrıştırmayı çok yavaş yaparsa wrangler deploy'u 10021 hatasıyla reddeder. Başlatmayı modül üst düzeyinde değil, handler içinde tutun; arama tablolarını ya da font atlaslarını modül başlangıcında yüklemekten kaçının.

Bu İş İçin Yanlış Araç Olduğunda

Üç şekil okuyucuyu başka yere gönderir.

Büyük kaynak görseller ya da batch işler. Girdiler rutin olarak yaklaşık 8-10 megapikseli aşarsa ya da iş bir klasör üzerinde batch tipi çalışıyorsa, 128 MB'lık paylaşılan isolate esnemez. S3 + Lambda, efemeral /tmp deposuyla tek-görsel işleri daha büyük bellek zarfında karşılar. Fargate ya da bir worker döngülü ECS batch'i karşılar. İkisi de Worker'ın kaçındığı bir cold-start maliyeti öder ama ikisinin de bellek tavanları yükü gerçekten karşılar.

Çıktı format çeşitliliği. AVIF, HEIC ve hareketli WebP encoder'ları bundle'a belirgin byte ekler. Bunları photon-rs üzerine yığmak 10 MB sıkıştırılmış tavanı patlatacaktır. Çok-dilli formatlar için yönetilen yol Cloudflare Images; kendi kendine barındırılan yol bir CDN'in arkasındaki container.

Yeniden-boyutlamanın ötesinde hat aşamaları. Görsel üzerinde yüz tanıma, OCR ya da ML inference farklı bir hesaplama profilidir. Workers AI modelleri HTTP yüzeyinin arkasında açar; daha ağır olan her şey için bir GPU task runner uygundur. CPU-bağımlı bir Worker'a inference'ı sıkıştırmaya çalışmak, WASM bundle'ının kırpılabileceğinden daha hızlı CPU bütçesi yakar.

Kapanış

Kullanıcı-yüklü fotoğraflar uzun kenarında yaklaşık 3000 px'e kadar, JPEG giriş ve JPEG çıkış, istek başına tek görsel için Cloudflare Workers + workers-rs + photon-rs, üretimde bir yeniden-boyutlama uç noktasına ucuz bir yoldur. Sınır yalnızca Paid tier'da, tek bir çıktı format ailesi için, istek başına tek görsel için tutar. Bu kısıtların dışında daha fazla Rust yazmadan önce S3 + Lambda, Fargate ya da yönetilen bir görsel ürününe uzanın.

Kaynaklar

İlgili Yazılar