Cloudflare Workers、Python で記述可能に
(blog.cloudflare.com)- Cloudflare Workers が オープンベータ として Python での記述をサポートし、Pyodide をオープンソースの Workers ランタイムである workerd に直接統合することで、JavaScript 中心のモデルを拡張
- Python Workers は Vectorize、Workers AI、R2、Durable Objects など既存の バインディング を初日からサポートし、FastAPI・Langchain・Numpy など一部の Python パッケージを import できる
- 実装の基盤は CPython の WebAssembly ポートである Pyodide であり、JavaScript と Python 間の FFI によって Request、Response、Fetch API、Cloudflare リソースのバインディングを Python コードから扱う
- Cloudflare はデプロイ時に import を実行し、WebAssembly 線形メモリのスナップショット を作成することで、Pyodide とパッケージの初期化コストを削減し、基本的な Python Worker のコールドスタートを 1 秒未満まで短縮
- Python のバージョンと Pyodide・パッケージの更新は Compatibility Dates と Compatibility Flags で管理され、5 年のサポート期間が終了した Python リリースにとどまる Worker は、次に古い Python リリースへ自動的に移行される
Python Workers オープンベータ
- Cloudflare Workers で Python Workers をオープンベータとして利用可能
- 従来の JavaScript 以外の言語サポートとは異なり、Python 実装を workerd ランタイムに直接統合
- 初日からサポートされる Cloudflare バインディングには次が含まれる
- Python Workers は FastAPI、Langchain、Numpy など一部の人気 Python パッケージを import できる
- 別途 ビルドステップ や外部ツールチェーンは不要
WebAssembly コンパイルだけでは足りなかった点
- Workers は 2018 年から WebAssembly をサポートしており、各 Worker は Chrome と同じ JavaScript エンジンである V8 isolate で実行される
- 原理上、Python を含む複数の言語は、まず WebAssembly または JavaScript にコンパイルすれば Workers 上で実行できた
- 実際のアプリケーションには “hello world” の実行以上のものが必要であり、開発者が慣れ親しんで使う パッケージエコシステム のサポートが重要
- Python Workers は、Workers で JavaScript 以外の言語を第一級でサポートするための初期形態
Python Worker の実行フロー
- Pyodide が workerd に組み込まれ、Python Worker は
on_fetchハンドラーでリクエストを処理できる wrangler.tomlでは.pyファイルをmainに指定し、compatibility_flags = ["python_workers"]を設定するnpx wrangler@latest dev実行時に Workers ランタイムが次を処理する- compatibility date を基準に必要な Pyodide バージョンを決定
- Worker 用の isolate を作成し、Pyodide を自動注入
- Pyodide に Python コードを渡す
- Python 実行環境は内部で処理され、JavaScript Workers と同様にプラットフォームが提供する
Pyodide が Workers に適している理由
- Pyodide は CPython を WebAssembly に移植した実装であり、Python コードを別形式に事前コンパイルせずに解釈する
- Python 標準ライブラリの大半を提供し、JavaScript API を Python から呼び出せる 外部関数インターフェース(FFI) を提供する
- コアインタープリタと各ネイティブ Python モジュールを個別の WebAssembly モジュールとしてビルドし、ランタイムで 動的リンク できるよう設計されている
- 同じマシン上で動作する複数の Workers がモジュールのコードフットプリントを共有できるため、1 台あたり数千の Workers を実行する Cloudflare 環境では重要
- WebAssembly を対象とする大半の言語は、まだ動的リンクをサポートしておらず、各アプリケーションが独自の言語ランタイムのコピーを含むことが多い
Pyodide と WebAssembly 動的リンク
- WebAssembly はホストランタイムと分離されたサンドボックス環境であるため、ファイル読み込みのような純粋計算以外の処理はランタイム環境が提供し、モジュール側が import する必要がある
- LLVM の WebAssembly ターゲットは 3 種類に分かれる
wasm32-unknown-unknown: C 標準ライブラリやシステムコールインターフェースを提供しないwasm32-wasi: WASI ランタイムが実装する標準システムインターフェースを使うwasm32-unknown-emscripten: 必要な import を定義し、それを実装する JavaScript ライブラリもあわせて出力する
- Pyodide は Emscripten を用いて、CPython インタープリタ、Python-JavaScript FFI、WebAssembly にコンパイルされたサードパーティー Python パッケージを提供する
- これらのターゲットのうち、現在動的リンクをサポートしているのは Emscripten だけ
- WASI は、CPython が使う
dlopen/dlsymの動的リンク抽象化をまだサポートしていない
Python と JavaScript をつなぐ FFI
- Python Worker の例では、
from js import Responseによって JavaScript のResponseを取り込む - Pyodide の FFI は Python からあらゆる JavaScript 機能にアクセスできるようにし、Python Workers が機能面で JavaScript Workers に劣後する問題を減らす
- 文字列や数値のような不変型は両言語間で透過的に変換され、可変オブジェクトは適切なプロキシでラップされる
- JavaScript オブジェクトが Python に渡されると、Pyodide はそのオブジェクトがサポートする JavaScript プロトコルを確認し、対応する Python プロトコルを実装するクラスを動的に構成する
- JavaScript の反復プロトコルをサポートしていれば、Python の反復プロトコルもサポートする
- Promise や thenable であれば、Python では awaitable オブジェクトになる
- リクエスト処理フローでも、受信した JavaScript
Requestオブジェクトは Python コードからアクセス可能なJsProxyでラップされ、Python ハンドラーの戻り値は JavaScriptResponseオブジェクトへ変換される
動的リンクと Python パッケージ
- 多くの Python パッケージは C FFI を通じてネイティブライブラリを読み込み、Workers ランタイムで動作させるには、これらのライブラリを WebAssembly にコンパイルする必要がある
- Pyodide は Emscripten でビルドされており、Python の C FFI をオーバーライドして、パッケージがネイティブライブラリを読み込もうとすると Workers ランタイムが提供する WebAssembly モジュールをロードする
- 動的リンク によって、ネイティブライブラリ依存を持つ複数の Python パッケージを Pyodide がサポートできるようになる
- 静的リンクではバイナリ実行前に必要なコードをすべて読み込む必要があるが、動的リンクでは必要な時点でコストを払う方式となる
- Workers は各 Worker ごとに、ランタイム上で Python ディストリビューションのように見えるファイルシステムを生成しつつ、基盤ファイルは Workers 間で共有する
- 現時点ではファイルを Workers 間で共有しつつ新しい isolate ごとにコピーしているが、将来的には copy-on-write 手法によって underlying resource をさらに多く共有できると見ている
HTTPクライアントとサーバーライブラリのサポート
- Python には httpx、urllib3、requests のような HTTP クライアントライブラリが数多くあるが、Pyodide ではデフォルトでは動作しない
- これらのライブラリは生のソケットを使用するが、ブラウザのセキュリティモデルと CORS によりこれは許可されないため、Workers ランタイムでは別の方法が必要になる
-
非同期クライアント
-
同期クライアントと制約
- 多くの Python API は 同期式 であり、この場合は非同期の Fetch API を直接利用できない
- urllib3 には、Pyodide のブラウザサポート向けに
Atomics.wait()と fetch worker thread、または同期 XMLHttpRequest を使うコントリビューションが入っている - Cloudflare Workers は現在 worker threads と同期
XMLHttpRequestをサポートしていないため、この 2 つのアプローチは Python Workers では動作しない - 現時点では同期リクエストはサポートされていない
-
WebAssembly Stack Switching
- WebAssembly にはスタック切り替えを追加する stage 3 の提案があり、V8 には実装がある
- Pyodide のコントリビューターは 2022 年 9 月から stack switching のサポートを追加しており、ほぼ準備が整っている
- このサポートが入れば、Pyodide の
run_syncが awaitable の完了を待つようにブロックできるため、同期リクエストをサポートする道が開ける
FastAPI と ASGI
- FastAPI は Python サーバー定義で広く使われるライブラリで、ASGI プロトコルを使用する
- FastAPI アプリケーションはソケットを直接読み書きせず、通常は uvicorn のような ASGI サーバーが生のソケット処理を担う
- この構造により、FastAPI 自体にパッチを当てたり変更したりせずに Cloudflare Workers 上で動作できる
- Workers 内で実行可能な ASGI サーバーで uvicorn を置き換えればよい
- 初期実装は workerd の asgi.py にあり、Cloudflare が保守する Pyodide fork に含まれている
- Cloudflare は機能をさらに充実させ、テストカバレッジを追加したうえで Pyodide に upstream する計画だ
Python パッケージのインポート
- Python Workers は、Pyodide が直接提供する一部の Python パッケージをサポートする
- サポートされるパッケージには numpy、httpx、FastAPI、Langchain などが含まれる
- パッケージをインポートするには、
requirements.txtにバージョン番号なしでパッケージ名を追加する - 特定のパッケージバージョンは Pyodide が直接提供する
- 現時点ではローカル開発でパッケージを利用でき、数週間以内に
requirements.txtの依存関係を定義した Workers のデプロイが可能になる予定だ - Cloudflare は独自の Pyodide fork を保守して Workers ランタイム専用のパッチを提供し、変更内容を Pyodide upstream に反映していくとしている
コールドスタートとメモリスナップショット
- Pyodide 自体のサイズは 6.4MB で、Python パッケージも大きくなることがある
- Pyodide をそのまま Worker に入れて Cloudflare にアップロードすると、新しい isolate のロードコストが大きくなり、コールドスタートが遅くなる
- 高速なコンピュータと良好なネットワーク環境でも、Pyodide はブラウザでの初期化に約 2 秒かかり、ネットワーク 1 秒と CPU 1 秒を要する
npx wrangler@latest deploy時、デプロイプロセスは次のように動作する- Wrangler が Python コードと
requirements.txtを Workers API にアップロードする - Workers ランタイムが Python コードと依存関係を検証する
- 新しい isolate を作成し、Pyodide と指定されたパッケージを自動注入する
- Worker コードの import 文をスキャンして実行した後、Worker の WebAssembly 線形メモリのスナップショットを生成する
- このスナップショットと Python コードを Cloudflare ネットワークにデプロイする
- JavaScript Worker と同様に top-level scope を実行する
- Wrangler が Python コードと
- リクエストが入るとこのスナップショットをロードして isolate 内で Worker をブートストラップするため、高コストな初期化を回避できる
- 基本的な Python Worker のコールドスタートは 1 秒未満 まで短縮される
-
スナップショットの再利用
- 現在、Python Worker のアップロード時に生成されるメモリスナップショットはその Worker 専用であり、他の Python Workers と大部分が同じでも共有できない
- Cloudflare は、事前に単一の共有スナップショットを作成し、Pyodide ランタイムがすでにロードされた pre-warmed isolate プールにあらかじめ配置できると見ている
- この方式なら、Python Worker も JavaScript Worker のようにランタイムが on-demand で提供されるモデルに近づく
- Cloudflare は 2024 年の残り期間において、コールドスタートをさらに減らす最大の手段としてスナップショット再利用を見ている
Compatibility Dates による Python バージョン管理
- Cloudflare Workers は、一度デプロイした Worker が更新なしでも継続して実行されることを前提とするモデルを持っている
- この安定性は Compatibility Dates と Compatibility Flags によって提供される
- Python では Pyodide と CPython にそれぞれバージョンがあり、新しいバージョンには breaking change が含まれる可能性がある
- 新しい Python バージョンは毎年 8 月にリリースされ、新しい Pyodide バージョンはその 6 か月後にリリースされる
- 新しい Pyodide バージョンは Workers に追加される際、Compatibility Flag の後ろに配置され、指定された Compatibility Date 以降にのみ有効化される
- Python リリースは 5 年のサポート期間 を持ち、サポート期間を過ぎた Python バージョンにはセキュリティパッチが適用されない
- 5 年が過ぎても古い Python リリースにとどまる Python Worker は、次に古い Python リリースへ自動的に移行される
- Cloudflare は、ほとんどの場合 Python Worker が問題なく継続して動作すると見込んでいるが、サポート期間内にとどまるよう compatibility date を定期的に更新することを推奨している
- Python リリース間でもパッケージは同じ opt-in 方式で更新・追加される予定で、例のフラグは
python_3.17_packages_2025_03_01形式となる
Python Workers のバインディング
- Pyodide FFI により、Python から JavaScript のオブジェクト、メソッド、関数に直接アクセスできる
- この構造により、Cloudflare リソース向けのすべての binding API が初日から Python Workers でサポートされる
- Python ハンドラーの
envオブジェクトは JavaScript オブジェクトであり、Pyodide が言語間の型変換を処理するプロキシ API を提供する - KV namespace については、
env.FOO.put()とenv.FOO.get()を Python から await して値の書き込みと読み取りができる - Web API も同じ方法で利用でき、
Responseのような JavaScript global をjsモジュールから import できる
より Python らしい API の計画
- Cloudflare は、
from js import Responseのような形が Python らしくないことを認識しており、Python Workers により idiomatic な API を提供する予定である - 2021 年に公開した workers-rs は、Workers の各 JavaScript API に対して Rust らしいバインディングを提供していた
- Python Workers でも同じ方向を計画しており、まずは Workers AI と Vectorize のバインディングから始める
- Rust の workers-rs は外部依存関係を利用して更新する必要があるが、Python Workers の API は Workers ランタイムに直接組み込まれる予定である
- compatibility date を更新すれば、最新の Python らしい API を利用できる
- Workers の JavaScript
connect()API を基に、Python 標準ライブラリの raw socket API の一部を提供する可能性もある - Cloudflare は、JavaScript と同等の機能を提供しつつ、Python 開発者が簡単に使える標準化された serverless API を作る取り組みを始められることを期待している
今後の方向性
- 新しいプログラミング言語を本格的にサポートするには、「hello world」以上の大きな投資が必要である
- Python は Stack Overflow 2023 調査によると、JavaScript に次いで多く使われている言語である
- Cloudflare は、Python Workers の性能を継続的に改善し、Python パッケージのサポート範囲を広げていくとしている
- フィードバックチャネルは、Cloudflare Developers Discord の Python Workers チャンネルと workerd GitHub discussion である
1件のコメント
Hacker News のコメント
Cloudflare が Edge で WebAssembly によって Python を実行することにより注力しているのは喜ばしい
Wasmer で Edge における WebAssembly ベースの Python 実行に取り組んできた立場からまとめると、Cloudflare Workers は Emscripten で WebAssembly にコンパイルされた Python である Pyodide を使い、Edge で Python を有効化している
Pyodide を Workerd に組み込み、V8 スナップショットで起動時間を短縮しようとする構成で、最良の場合でも Python のコールドスタートは約 1 秒
ただし現在の方式では、Workerd に組み込まれた Python/Pyodide のバージョンに縛られ、パッケージ解決も Workerd に強く結合されているため、ランタイムでは事前コンパイル済みのネイティブパッケージだけが許可される可能性が高い。たとえば特定バージョンの numpy を使うのは難しいかもしれない
構造的に JS/V8 の世界に縛られているため、現在のアーキテクチャで 100ms 未満の起動時間を達成するのは難しそうに見える
それでもこのリリースは歓迎しており、人々が素晴らしいアプリを作ることを期待している
https://pyodide.org/
https://github.com/cloudflare/workerd/blob/main/docs/pyodide...
https://github.com/cloudflare/workerd/pull/1875
追記: Cloudflare チームの説明を反映し、「概念実証」を「リリース」に修正した
langchain や言及されている numpy のようなパッケージは、かなり頻繁に更新していく予定だ
V8 がなぜ制約要因になると考えているのか、もう少し説明してほしい。V8 は強力な WebAssembly ランタイムであり、計画中の最適化の大半は下位エンジンに大きく依存しない
また、これは概念実証ではなく、継続的に改善して GA まで進める ベータだ
https://wasmer.io/templates?language=python
Cloudflare にはホスティングやデータベース周りで良いものが多いが、開発者プラットフォームとしてのマーケティングがうまくなく、Vercel や Netlify がかなりの認知シェアを持っていったように思う
それとは別に、Cloudflare が Google Cloud Run のような言語非依存のコンテナホスティングサービスを提供しているのか気になる
価格と製品が素晴らしい
まったく動かない機能もあれば、部分的にしかサポートされない機能もあった
貴重な開発時間をこうした問題解決に費やさなければならない状況なら、Vercel、Netlify、Deno Deploy のような代替プラットフォームのほうがチームの要件にはよりスムーズで、インフラ問題より開発に集中しやすかった
帯域幅コストは、ほとんどのクラウドプロバイダーより Vercel と Netlify が 40〜50 倍高く、Cloudflare では帯域幅はほとんどコストにならない
Edge 関数の呼び出しも、Vercel と Netlify は Cloudflare より 6 倍高い。Cloudflare では無料のコンピュート時間のコストは除いた数字だ
Vercel が人気であるほぼ唯一の理由は、NextJS をホスティングするのに最適な場所だからで、そのため NextJS をほかの場所へデプロイしにくくしているのかもしれない
Workers を本番環境で 4 年ほど使ってきて気に入っているが、ほとんどのアプリはいまも コンテナで動かしている
Cloudflare の前段に置いたサイトで JS Workers を使ってみたが、使いやすく非常に速かった。サイト裏側の Django アプリ全体を D1 データベースまで含めて移行したい
トラフィックが少ないので無料枠に収まっているが、作るのがとても簡単なので喜んでお金を払うつもりもある
JS Workerとの性能比較があると助かりそう。興味深いが、複数のレイヤーが絡んでいるので、潜在的に遅い可能性もありそうに見える
同等の性能を期待しているわけではないが、大まかなトレードオフが分かるとよい
現在のPythonのコールドスタートは、同じサイズのJavaScript Workerより遅い。JavaScriptで書いた基本的な「Hello World」Workerはコールドスタート時間がほぼゼロに近いが、Python Workerは1秒未満
リクエストが来たときにPyodideをWorkerへオンデマンドでロードする必要があるためで、ブログ記事ではこれを減らすためにPyodideを事前に利用可能にしておく取り組みを説明している
ただしPython Workerがコールドスタートを終えた後は、差は周辺的なものに近くなり、リクエスト中に何が起きるかによって数ミリ秒程度になることもある
JavaScriptとWebAssemblyの間の「ブリッジ」を渡るとき、たとえば入出力や非同期処理を行うときには多少のコストがある。ただしミリ秒ではなくマイクロ秒単位と考えればよく、一般的には非常に小さい
性能に敏感なWorkerを使う人たちは、すでにRustで書いていることもある: https://github.com/cloudflare/workers-rs これもJavaScriptとWebAssembly間のブリッジに依存している
Pyodideが提供するWebAssemblyベースのPythonインタプリタは、V8でJavaScriptを高速にするために長年積み重ねられてきた最適化ほど速くはない。それでもPyodideはV8のJSエンジンに比べるとまだ初期段階で、大きな性能向上が可能そうな部分がある。性能改善をアップストリームに反映したいし、役立つWebAssembly提案もある
分離を示すためにlzmaを選んだのが意図的なのか、それとも先週の技術ニュースのせいで偶然そうなったのか気になる
https://news.ycombinator.com/item?id=39865810
CloudflareでAI関連の処理を動かすうえで、かなりゲームチェンジャーになりそう。かなり長く待っていた
まだ見ていなければ、今日出た他の2つの発表も確認してみる価値がある
“Leveling up Workers AI: General Availability and more new capabilities”
https://blog.cloudflare.com/workers-ai-ga-huggingface-loras-...
“Running fine-tuned models on Workers AI with LoRAs”
https://blog.cloudflare.com/fine-tuned-inference-with-loras
今日使ってみたが良かったし、非常に速く実行まで持っていけた
ただし、ローカル開発環境にCFWのPython実装に組み込まれたライブラリを理解させるにはどうすればよいのか気になる
たとえば
asgiライブラリがあるが、リンターがこれを不明なものとして表示しないようにしたい。しかしこのライブラリはon_fetchハンドラのランタイムにしか存在せず、ローカル開発マシンには実際にはないので、解決方法を見つけられなかった静的サイトにCF Pagesを使って良い結果を得ており、オープンソースLLMをサービスのように提供するCloudflare製品にも興味がある
Cloudflareでもっと作れない主な理由はPythonサポートの不足だったので、今回の機能を試すのが楽しみ
すばやく何かを動かしやすく、インフラをあまり気にしなくて済む
Pythonの追加は非常にうれしいし、Goもファーストクラスサポートに入るとよい
Pyodideパッケージだけを使うという制約が、些細ではないビルドでどう機能するのか気になる
純粋なPythonではないコードが多いので、実質的なプロダクションアプリをサポートするには手動で再ビルドしなければならないものが多そう
Cloudflareの採用がより多くのパッケージを引き込む助けになるかもしれないし、ここで80/20の法則が当てはまるなら十分に問題ないかもしれない
ブログ記事でも触れたように、最大の問題はサーバーやリクエスト関連パッケージのサポートだ。Cloudflare Workersでは明らかに有用だが、raw socketや何らかの形の並行性をよく使うため、移植が難しい
CloudFlareが、JS Workersに縛られない汎用APIとともに、WASMをファーストクラスの対象とするWorkersを実装してくれるとよい
今でもWASMコードをデプロイできるので事実上どんな言語でも使えるが、ネイティブではなくJSコンテキスト内で実行される
デプロイには多少のオーバーヘッドとぎこちなさがある
最終的にすべてのサービスはコンテナではなく、セキュア化されたWASMランタイムへ直接デプロイされるようになると思う。イメージからコンテナへ移行した流れに似ている
現在Cloudflare EdgeでRustのようなものを使おうとする利点は大きくない。性能上の利点のかなりの部分が、オーバーヘッドと起動時間で相殺されるため
例: https://github.com/WasmEdge/WasmEdge