32 comments

[ 290 ms ] story [ 1538 ms ] thread
``` BREAKING CHANGE The following packages are removed from the Pyodide distribution because of the build issues. We will try to fix them in the future: arro3-compute arro3-core arro3-io Cartopy duckdb geopandas ... polars pyarrow pygame-ce pyproj zarr ```

https://pyodide.org/en/stable/project/changelog.html#version...

Bummer, looks like a lot of useful geo/data tools got removed from the Pyodide distribution recently. Being able to use some of these tools in a Worker in combination with R2 would unlock some powerful server-side workflows. I hope they can get added back. I'd love to adopt CF more widely for some of my projects, and seems like support for some of this stuff would make adoption by startups easier.

Do you happen to know what build issues they're referring to?
I don't. I did a brief search through their github issues and didn't turn up anything IIRC, but honestly wasn't trying that hard.
The comparison with AWS Lambda seems to ignore the AWS memory snapshot option called "SnapStart for Python". I'd be interested in seeing the timing comparison extended to include SnapStart.
"SnapStart for Python" costs extra though. If we are paying then you can even have prewarmed Python lambdas with no cold start on AWS (Provisioned Concurrency).
Unless I misunderstand, AWS SnapStart and their memory snapshots are the same feature (taking memory snapshots to speed up cold start). It doesn't seem a fair comparison to ignore this and my assumption is because AWS Lambda SnapStart is faster.
In the linked detailed benchmark results they include Lambda SnapStart which seems to be faster than Cloudflare:

> AWS Lambda (No SnapStart) Mean Cold Start: 2.513s Data Points: 1008 > AWS Lambda (SnapStart) Mean Cold Start: 0.855s Data Points: 17 > Google Cloud Run Mean Cold Start: 3.030s Data Points: 394 > Cloudflare Workers Mean Cold Start: 1.004s Data Points: 981

https://cold.edgeworker.net

Anybody using it for something serious ? I can't see a use case beyond I need a quick script running that is not worth setting up a vps.
The most interesting bit here is not the “2.4x faster than Lambda” part, it is the constraints they quietly codify to make snapshots safe. The post describes how they run your top-level Python code once at deploy, snapshot the entire Pyodide heap, then effectively forbid PRNG use during that phase and reseed after restore. That means a bunch of familiar CPython patterns at import time (reading entropy, doing I/O, starting background threads, even some “random”-driven config) are now treated as bugs and turned into deployment failures rather than “it works on my laptop.”

In practice, Workers + Pyodide is forcing a much sharper line between init-time and request-time state than most Python codebases have today. If you lean into that model, you get very cheap isolates and global deploys with fast cold starts. If your app depends on the broader CPython/C-extension ecosystem behaving like a mutable Unix process, you are still in container land for now. My hunch is the long-term story here will be less about the benchmark numbers and more about how much of “normal” Python can be nudged into these snapshot-friendly constraints.

I'm betting against wasm and going with containers instead.

I have warm pool of lightweight containers that can be reused between runs. And that's the crucial detail that makes or breaks it. The good news is that you can lock it down with seccomp while still allowing normal execution. This will give you 10-30ms starts with pre-compiled python packages inside container. Cold start is as fast as spinning new container 200-ish ms. If you run this setup close to your data, you can get fast access to your files which is huge for data related tasks.

But this is not suitable for type of deployment Cloudflare is doing. The question is whether you even want that global availability because you will trade it for performance. At the end of the day, they are trying to reuse their isolates infra which is very smart and opens doors to other wasm-based deployments.

Very interesting but the limitation on the libraries you can use is very strong.

I wonder if they plan to invest seriously into this?

If anyone from cloudflare comes here - it's not possible to create D1 databases on the fly and interact them because databases must be mentioned in the worker bindings.

This hampers the per user databases workflow.

Would be awesome if a fix lands.

Try Durable Objects. D1 is actually just a thin layer over Durable Objects. In the past D1 provided a lot of DX benefits like better observability, but those are increasingly being merged back into DO directly.

What is a Durable Object? It's just a Worker that has a name, so you can route messages specifically to it from other Workers. Each one also has its own SQLite database attached. In fact, the SQLite database is local, so you can query it synchronously (no awaits), which makes a lot of stuff faster and easier. You can easily create millions of Durable Objects.

(I am the lead engineer for Workers.)

I wish they would contribute stuff like this memory snappshotting to CPython.
It’s 2025 and choosing a region for your resources is still an enterprise feature on cloudflare.

In contrast, AWS provides this as the base thing, you choose where your services run. In a world where you can’t do anything without 100s of compliance and a lot of compliances require geolocation based access control or data retention, this is absurd.

(comment deleted)
I hope Cloudflare improve Next.js support on Workers.

Currently pagespeed.web.dev score drops by around 20 than self hosted version. One of the best features of Next.js, Image optimization doesn't have out of the box support. You need separate image optimization service that also did not work for me for local images (images in the bundle).

One of my biggest points of criticism of Python is its slow cold start time. I especially notice this when I use it as a scripting language for CLIs. The startup time of a simple .py script can easily be in the 100 to 300 ms range, whereas a C, Rust, or Go program with the same functionality can start in under 10 ms. This becomes even more frustrating when piping several scripts together, because the accumulated startup latency adds up quickly.
Are you comparing the startup time of an interpreted language with the startup time of a compiled language? or you mean that `time python hello.py` > `( time gcc -O2 -o hello hello.c ) && ( time ./hello )` ?
Yes, that is also my feeling. But comparing an interpreted language with a compiled one is not really fair.

Here is my quick benchmark. I refrain from using Python for most scripting/prototyping task but really like Janet [0] - here is a comparison for printing the current time in Unix epoch:

    $ hyperfine --shell=none --warmup 2  "python3 -c 'import time;print(time.time())'" "janet -e '(print (os/time))'"
  Benchmark 1: python3 -c 'import time;print(time.time())'
  Time (mean ± σ):      22.3 ms ±   0.9 ms    [User: 12.1 ms, System: 4.2 ms]
  Range (min … max):    20.8 ms …  25.6 ms    126 runs

  Benchmark 2: janet -e '(print (os/time))'
  Time (mean ± σ):       3.9 ms ±   0.2 ms    [User: 1.2 ms, System: 0.5 ms]
  Range (min … max):     3.6 ms …   5.1 ms    699 runs

  Summary
  'janet -e '(print (os/time))'' ran
    5.75 ± 0.39 times faster than 'python3 -c 'import time;print(time.time())''
[0]: https://janet-lang.org/
Completely agree on this.

Regarding cold-starts, I strongly believe V8 snapshots are perhaps not the best way to achieve fast cold starts with Python (they may be if you are tied to using V8, though!), and will have wide side effects if you go out of the standards packages included on the Pyodide bundle.

To put some perspective: V8 snapshots are storing the whole state of an application (including it's compiled modules). This means that for a Python package that is using Python (one wasm module) + Pydantic-core (one wasm module) + FastAPI... all of those will be included in one snapshot (as well as the application state). This makes sense for browsers, where you want to be able to inspect/recover everything at once.

The issue about this design is that the compiled artifacts and the application state are bundled into one piece artifact (this is not great for AOT designed runtimes, but might be the optimal design for JITs though).

Ideally, you would separate each of the compiled modules from the state of the application. When you do this, you have some advantages: you can deserialize the compiled modules in parallel, and untie the "deserialization" from recovering the state of the application. This design doesn't adapt that well into the V8 architecture (and how it compiles stuff) when JavaScript is the main driver of the execution, however it's ideal when you just use WebAssembly.

This is what we have done at Wasmer, which allows for much faster cold starts than 1 second. Because we cache each of the compiled modules separately, and recover the state of the application later, we can achieve cold-starts that are a magnitude faster than Cloudflare's state of the art (when using pydantic, fastapi and httpx).

If anyone is curious, here is a blogpost where we presented fast-cold starts for the application state (note that the deserialization technique for Wasm modules is applied automatically in Wasmer, and we don't showcase it on the blogpost): https://wasmer.io/posts/announcing-instaboot-instant-cold-st...

Note aside: congrats to the Cloudflare team on their work on Python on Workers, it's inspiring to all providers on the space... keep it up and let's keep challenging the status quo!

It might not be the fastest but I suspect something weird is happening with python resolution.

For instance `uv run` has its own fair share of overhead.

    $ hyperfine --warmup 10 -L py "uv run python,~/.local/bin/python3.14,/usr/local/bin/python3.12,~/.local/share/uv/python/pypy-3.11.13-macos-aarch64-none/bin/pypy3.11" "{py} -c 'exit(0)'"
    Benchmark 1: uv run python -c 'exit(0)'
      Time (mean ± σ):      58.4 ms ±  19.3 ms    [User: 26.4 ms, System: 21.7 ms]
      Range (min … max):    48.2 ms … 138.0 ms    50 runs
    
    Benchmark 2: ~/.local/bin/python3.14 -c 'exit(0)'
      Time (mean ± σ):      13.3 ms ±   6.9 ms    [User: 8.0 ms, System: 2.5 ms]
      Range (min … max):     9.9 ms …  53.7 ms    174 runs
    
    Benchmark 3: /usr/local/bin/python3.12 -c 'exit(0)'
      Time (mean ± σ):      16.4 ms ±   7.6 ms    [User: 8.9 ms, System: 3.7 ms]
      Range (min … max):    12.2 ms …  65.2 ms    152 runs
    
    Benchmark 4: ~/.local/share/uv/python/pypy-3.11.13-macos-aarch64-none/bin/pypy3.11 -c 'exit(0)'
      Time (mean ± σ):      18.6 ms ±   7.4 ms    [User: 10.0 ms, System: 5.0 ms]
      Range (min … max):    14.4 ms …  63.5 ms    138 runs
    
    Summary
      ~/.local/bin/python3.14 -c 'exit(0)' ran
        1.23 ± 0.86 times faster than /usr/local/bin/python3.12 -c 'exit(0)'
        1.40 ± 0.92 times faster than ~/.local/share/uv/python/pypy-3.11.13-macos-aarch64-none/bin/pypy3.11 -c 'exit(0)'
        4.40 ± 2.72 times faster than uv run python -c 'exit(0)'
Reminds me of mercurial cvs!!
Big packages shouldn’t be imported until the cli has been parsed, and handed off to main. There’s been work to do this automatically, but it’s good hygiene to avoid it anyway.

A modern machine shouldn’t take this long, so likely something big is being imported unnecessarily at startup. If the big package itself is the issue, file it on their tracker.

Run strace on Python starting up- you will see it statting hundreds if not thousands of files. That gets much worse the slower your filesystem is.

On my linux system where all the file attributes are cached, it takes about 12ms to completely start, run a pass statement, and exit.

You can run .pyc stuff “directly” with some creativity, and there are some tools to pack “executables” that are just chunked blobs of bytecode.
I don't know why people care so much about a few hundreds of milliseconds for python scripts versus compiled languages that take just ten times less.

Real question : what would you do more with the spared time ? You are that in a hurry in your life ?

It’s not Python runtime startup, but loading all your modules.

Use lazy/dynamic imports and you will see it drop .

Pyodide is a great enabler for this kind of thing, but most of the libraries I want to use tend to be native or just weird. Still, I wonder how fast things like Pillow, Pandas and the like are these days—-benchmarks would be nice.
I still don’t get, what is the use case for cloudflare workers or lambda?

I used both for years. Nothing beats VPS/bare metal. Alright, they give lower latency, and maybe cheaper and big nightmare for managing at the same time. Hello to micro services architecture.

Checked out the Cloudflare post... they now support Pyodide-compatible packages through uv... so you can pull in whatever Python libs you need, not just a curated list.

ALSO the benchmarks show about a one second cold start when importing httpx, fastapi and pydantic... that's faster than Lambda and Cloud Run, thanks to memory snapshots and isolate-based infra.

BUT the default global deployment model raises questions about compliance when you need specific regions... and I'd love to know how well packages with native extensions are supported.