当社は、2017年にCloudflare Workers® をリリースしました。このリリースにあたり、急進的なビジョンがありました。ネットワークエッジでコードを実行すれば、パフォーマンスを向上させるだけでなく、単一のデータセンターでコードを実行するよりも簡単にデプロイでき、安価に実行できると考えたのです。このビジョンの意味するところは、Workersはエッジコンピューティング以上のものだということです。当社はアプリケーションの構築方法を考え直しているのです。
「サーバーレス」アプローチを使用すれば、導入がシンプルになり、分離テクノロジーを使用すれば、サーバーレスをより安価に、他社プロバイダーとは異なり、長いコールドスタートなしで提供できます。当社では使いやすく、最終的には一貫性のあるエッジストレージを、Workers KVと共にプラットフォームに追加しました。
しかし、今日まで、一貫性のある状態を管理したり、複数のクライアント間でリアルタイムで調整したりするなど、すべてをエッジで行うことは不可能でした。したがって、アプリケーションのこれらの部分は別の場所でホストされる必要がありました。
Durable Objects は、ストレージとステートに対して真にサーバーレスなアプローチを提供します。一貫性、低レイテンシー、分散型でありながら、メンテナンスと拡張が容易です。また、クライアント間の調整も簡単に行えます。たとえば、特定のチャットルームにいるユーザー、特定のドキュメントの編集者、特定のスマートホーム内の IoT デバイスなどです。Workersスタックの欠けている部分を補うことで、アプリケーション全体をエッジ上で完全に実行でき、一元化された「オリジンサーバー」は全く必要なくなります。
今日から当社は、Durable Objectsの非公開ベータ版の提供をはじめます。
Request a beta invite »「Durable Object」とは?
正直に言うと、この製品の名前を考えることは困難でした。なぜなら、今日広く使用されている他のクラウドテクノロジーとは異なるものだからです。些細なことに議論は白熱しましたが、最終的には「Unique Durable Objects」または短いバージョンの「Durable Objects」に決まりました。単語の意味を一つずつ、説明したいと思います。
Objects(オブジェクト):Durable Objectsのオブジェクトは、オブジェクト指向プログラミングの意味でのオブジェクトです。Durable Object は、クラスのインスタンスです。ここでのクラスとは、文字通り、JavaScript(または選択した言語 )で書かれたクラスの定義にあるクラスを指します。クラスには、パブリックインターフェイスを定義するメソッドがあります。オブジェクトは、このクラスのインスタンスであり、コードをいくつかのプライベートステートと組み合わせたものです。
Unique(ユニーク):各オブジェクトには、世界に1つだけの識別子があります。そのオブジェクトは、世界で一度に1 つの場所にだけ存在します。オブジェクトの ID を知っていれば、実行中のWorkersが世界中どこにいても、そのオブジェクトにメッセージを送信できます。これらのメッセージはすべて同じ場所に配信されることになります。
Durable(耐久性):JavaScript の通常のオブジェクトとは異なり、Durable Objectsは永続的な状態をディスクに保存できます。各オブジェクトの永続的な状態はプライベートです。つまり、ストレージへのアクセスが高速であるだけでなく、オブジェクトはメモリ内の状態の一貫したコピーを安全に維持し、ゼロレイテンシーで操作することができます。メモリ内オブジェクトは、アイドリング時にシャットダウンされ、後でオンデマンドで再作成されます。
何ができる?
Durable Objectsには主要な能力が2つあります。
ストレージ:各オブジェクトには耐久性のあるストレージが付属します。このストレージは特定のオブジェクトに対してプライベートであるため、ストレージは常にオブジェクトと共に配置されます。つまり、ストレージは、強力なトランザクションの一貫性を提供しながら、非常に高速に稼働できるのです。Durable Objects は、サーバレスの理念をストレージに適用し、従来の大規模なモノリシックデータベースを多数の小さなロジックユニットに分割します。そうすることで、お客様は期待通りサーバーレスのメリットを享受できます。つまり、メンテナンスの負担なしで簡単に拡張できるのです。
調整:従来、Workersの場合、各リクエストは Worker インスタンスにランダムに負荷分散されます。リクエストを受信するインスタンスを制御する方法がなかったため、2 つのクライアントが同じWorkersと通信するように強制する方法はありませんでした。そのため、クライアントが Workers を通じて調整する方法もありませんでした。Durable Objectsでは、同じトピックに関連するリクエストを同じオブジェクトに転送し、ストレージに触れることなく、オブジェクト間で調整できるようになります。たとえば、リアルタイムチャット、共同編集、ビデオ会議、パブ/サブメッセージキュー、ゲームセッションなどを円滑にするために使用できます。
賢明な読者は、多くの調整のユースケースがWebSocketを必要とすることに気づくかもしれません。実際、逆に、ほとんどのWebSocketユースケースでは調整が必要です。この補完的な関係により、Durable Objects ベータ版とともに、Workers に WebSocket サポートも追加されました。詳細については、以下のQ&Aを参照してください。
地域(リージョン):地球
Durable Objectsを使用する場合、Cloudflareは、各オブジェクトのCloudflare データセンターを自動的に判断し、必要に応じてロケーション間でオブジェクトを透過的に移行できます。
従来のデータベースやステートフルなインフラストラクチャでは、通常、地理的な「地域」について考える必要がありました。データの使用場所に近い場所にデータを格納できるようにするためです。特にアプリケーションは本質的には地理的なものではないので、地域について考えることが不自然な負担になることはよくあります。
Durable Objects では、アプリケーションの論理データモデルに合わせてストレージモデルを設計します。たとえば、ドキュメントエディターにはドキュメントごとにオブジェクトがあり、チャットアプリにはチャットごとにオブジェクトがあります。各オブジェクトのオーバーヘッドは最小限であるため、数百万または数十億のオブジェクトを作成しても問題がありません。
キラーアプリ:リアルタイムの共同文書編集
たとえば、スプレッドシートエディタアプリケーション、またはユーザーが複雑なドキュメントを編集できるアプリケーション(どんな種類でも可)があるとします。これは、1 人のユーザーにとっては効果的に機能している状態ですが、今後、複数のユーザーが同時に編集できるようにしたいと考えています。これをどのように可能にしますか?
標準の Web アプリケーションスタックでは、困難です。従来のデータベースは、リアルタイムに対応できるようには設計されていないからです。アリスとボブが同じスプレッドシートを編集している場合、アリスのキーストロークがすべてボブの画面に即座に表示され、その逆も同様になるようにしたいとします。しかし、キーストロークをデータベースに保存するだけで、ユーザーに新規更新のためにデータベースを繰り返しポーリングさせても、せいぜいアプリケーションの遅延が短くなるだけで、最悪の場合、世界の反対側のユーザーが同じコンテンツを編集しようとするとデータベーストランザクションが繰り返し失敗する可能性もあります。
この問題を解決する秘訣は、ライブ調整ポイントを持つことです。アリスとボブは、通常WebSocketを使用して、同じコーディネーターに接続します。コーディネーターは、ストレージレイヤーを通過することなく、アリスのキーストロークをボブに、ボブのキーストロークをアリスに転送します。アリスとボブが同じコンテンツを同時に編集すると、コーディネーターは即座に競合を解決します。コーディネーターは、ストレージ内のドキュメントの更新について責任をもって行うことができますが、ドキュメントのライブコピーをメモリ内に保持するため、ストレージへの書き込みは非同期的に行われます。
有名なリアルタイムコラボレーションドキュメントエディタはすべてこのように動作します。しかし、多くのWeb開発者、特にサーバーレスインフラストラクチャ上に構築している開発者にとって、この種のソリューションは長い間手が届かないものでした。標準的なサーバーレスインフラストラクチャでは、(より一般的にはクラウドインフラストラクチャでも)、これらの調整ポイントの割り当てやサーバーの同じインスタンスとの通信をユーザーに指示することは簡単ではないのです。
Durable Ocjectsがこれを簡単にします。Durable Objectsは、簡単に調整ポイントを割り当てるだけでなく、Cloudflareは、それを使用するユーザーの近くにコーディネーターを自動的に作成し、必要に応じて移行し、遅延を最小限に抑えます。ローカルで耐久性のあるストレージを利用できるため、最終的な長期保存までのペースが遅い場合でも、ドキュメントへの変更を瞬時に確実に保存できます。または、ドキュメント全体をエッジに格納して、データベースを完全に放棄することもできます。
バリアを少なくなるので、リアルタイムコラボレーションがWeb全体で標準になることが期待されます。ユーザーに更新させる理由はもうありません。
例:原子カウンター
export class Counter {
// Constructor called by the system when the object is needed to
// handle requests.
constructor(controller, env) {
// `controller.storage` is an interface to access the object's
// on-disk durable storage.
this.storage = controller.storage
}
// Private helper method called from fetch(), below.
async initialize() {
let stored = await this.storage.get("value");
this.value = stored || 0;
}
// Handle HTTP requests from clients.
//
// The system calls this method when an HTTP request is sent to
// the object. Note that these requests strictly come from other
// parts of your Worker, not from the public internet.
async fetch(request) {
// Make sure we're fully initialized from storage.
if (!this.initializePromise) {
this.initializePromise = this.initialize();
}
await this.initializePromise;
// Apply requested action.
let url = new URL(request.url);
switch (url.pathname) {
case "/increment":
++this.value;
await this.storage.put("value", this.value);
break;
case "/decrement":
--this.value;
await this.storage.put("value", this.value);
break;
case "/":
// Just serve the current value. No storage calls needed!
break;
default:
return new Response("Not found", {status: 404});
}
// Return current value.
return new Response(this.value);
}
}
ここでは、Durable Objectsのとてもシンプルな例で、その増減をHTTPを介して読み取ることができるものをご紹介します。このカウンターは、複数のクライアントから同時にリクエストを受信する場合でも一貫性があり 、増分や減少は失われません。同時に、読み取りはメモリから完全に処理され、ディスクアクセスは必要ありません。
// Derive the ID for the counter object named "my-counter".
// This name is associated with exactly one instance in the
// whole world.
let id = COUNTER_NAMESPACE.idFromName("my-counter");
// Send a request to it.
let response = await COUNTER_NAMESPACE.get(id).fetch(request);
クラスが一度Durable Objectのネームスペースにつながると、Counterの特定のインスタンスは、次のようなコードを使用して、世界中どこからでもアクセスできます。
デモ:チャット
チャットは間違いなく最も純粋な形のリアルタイムコラボレーションです。そのため、Durable Objectsを使用してエッジで完全に動作するデモオープンソースチャットアプリを構築しました。
ライブデモをお試しください »GitHubでソースコードを確認する »
このチャットアプリでは、Durable Objectを使用して各チャットルームをコントロールします。ユーザーは WebSocket を使用してオブジェクトに接続します。あるユーザーからのメッセージは、他のすべてのユーザーに配信されます。チャットの履歴も耐久性のあるストレージに保存されますが、これは履歴のみです。リアルタイムメッセージは、ストレージレイヤーを経由することなく、あるユーザーから別のユーザーに直接伝わります。
さらに、このデモでは、Durable Objectsを「特定のIPからのメッセージにレート制限を適用する」という2番目の目的のために使用しています。各IPには、最近のリクエスト頻度を追跡するDurble Objectが割り当てられます。これにより、送信するメッセージが多すぎるユーザーは、複数のチャットルームにまたがっている場合でも、一時的にブロックされます。興味深いことに、これらのオブジェクトは実際には耐久性のあるステートをまったく保存しません。なぜなら、大切なのはごく最近の履歴だけだからです。また、レートリミッターが時折ランダムにリセットしても、それは大したことではありません。したがって、これらのレートリミッターオブジェクトは、ストレージのない純粋な調整オブジェクトの例です。
このチャットアプリはほんの数百行のコードです。展開設定は、ほんの数行です。しかし、それはCloudflareで利用可能なリソースによってのみ制限され、任意の数のチャットルームにシームレスに拡張されます。もちろん、各オブジェクトはシングルスレッドであるため、個々のチャットルームのスケーラビリティには制限があります。しかし、その限界は、人間の参加者が追いつくことができる数をはるかに超えています。
その他のユースケース
Durable Objectsには無限の用途があります。上記以外の例を少しご紹介します。
ショッピングカート:オンラインショップでは、オブジェクト内のユーザーのショッピングカートを追跡できます。オンラインショップの残りの部分は、完全に静的なWebサイトとして機能できます。Cloudflareは自動的にカートオブジェクトをエンドユーザーの近くでホストし、遅延を最小限に抑えます。
ゲームサーバー:マルチプレイヤーゲームは、プレイヤーに近いエッジでホストされているオブジェクト内のマッチの状態を追跡できます。
IoTコーディネーション:家庭内に置かれたデバイスは、オブジェクトを介して調整でき、遠く離れたサーバーと通信する必要がありません。
ソーシャルフィード:各ユーザーは、サブスクリプションを集約するDurable Objectを持つことができます。
コメント/チャットウィジェット:静的コンテンツで構成されているWebサイトでは、各アイテムにコメントウィジェットやライブチャットウィジェットを追加することができます。それぞれが、調整するために別々のDurable Objectを使用します。このようにして、オリジンサーバーは静的コンテンツにのみ焦点を当てることができます。
未来:真のエッジデータベース
Durable Objects は、分散システムの構築において、さらに開発が必要な基本形だと考えています。上述したように、一部のアプリケーションは、オブジェクトを直接使用してコーディネーションレイヤーを実装したり、単一のストレージレイヤーとしても使用できます。
しかし、現在のDurable Objectsは、完全なデータベースソリューションではありません。各オブジェクトは、それ自身のデータのみを見ることができます。複数のオブジェクト間でクエリーまたはトランザクションを実行するには、アプリケーションはいくつかの余分な作業を行う必要があります。
つまり、すべての大規模な分散型データベースは、(リレーショナル、ドキュメント、グラフなどの種類を問わず)、ある低い水準では、全データの一つを格納する「チャンク」または「シャード」で構成されています。分散データベースの仕事は、チャンク間の調整です。
エッジデータベースは将来的には、「チャンク」をDurable Obejectとして格納することになるだろうと当社では考えています。その結果、完全にエッジ上で稼働するデータベースを構築することが可能になり、地域やホームロケーションに関係なく完全に分散することが可能になるのです。これらのデータベースの構築は、Cloudflareが構築する必要はありません。将来的には誰もがDurable Objectsの上にデータベースを構築できるようになるでしょう。Durable Obejectsはエッジストレージの最初の一歩にすぎないのです。
ベータ版に参加する
データの保存には、大きな責任が伴い、当社がこれを軽んじることはありません。正しく行うことの重要性を理解しているので、私たちはデータの保存を慎重に行います。Durable Objects は、今後数か月で徐々に使用できるようにする予定です。
Request a beta invite »他のベータ版と同様に、この製品は進行中であり、この記事に記載されている内容の一部はまだ完全に有効化されていません。ベータ版の制限の詳細については、ドキュメントを参照してください。
今すぐDurable Objectsをお試しになりたい場合は、ユースケースについて当社までご連絡ください。早期アクセスについては、もっとも興味深いユースケースを当社で選考する予定です。
Q&A
Durable ObjectsはWebSocketでも使えますか?
はい。
Durable Objects ベータ版の一部として、Workersが WebSocket エンドポイントとして機能できるようにしました。クライアントまたはサーバーとしても使用することもできます。これまでWorkersではバックエンドサーバーに WebSocket 接続をプロキシできましたが、プロトコルに直接問い合わせることができませんでした。
技術的には、どのWorkerもこの方法でWebSocketに問い合わせることができますが、WebSocketを最も効果的に利用できるのは、Durable Objectsと組み合わせたときです。クライアントが WebSocket を使用してアプリケーションに接続する場合、サーバーが生成したイベントを既存のソケット接続に送り返す方法が必要です。Durable Objectsがなければ、WebSocketを保有する特定のWorkersにイベントを送信する方法がありません。Durable Objectsを使用すれば、WebSocket をオブジェクトに転送できるのです。メッセージは一意のID によってそのオブジェクトを宛先に指定でき、オブジェクトは WebSocket からクライアントにこれらのメッセージを転送できます。
上記のチャットアプリのデモでは、WebSockets を使用しています。 ソースコードをチェックして、仕組みを確認してください。
Workers KVとはどのように比較できますか?
2 年前、当社はグローバルキーバリューデータストアである Workers KV を発表しました。KVは、非常にシンプルなグローバルデータストアで、特定の目的のためにはうまく機能しますが、すべての人が利用できるものではありませんでした。KVはゆくゆくは一貫性を成すものです。つまり、ある場所で行われた書き込みは、他の場所ではすぐに認識できない可能性があります。さらに、「最後の書き込みが残る」セマンティックス(データの意味)を実装しています。つまり、ひとつのキーが世界中の複数の場所から一度に変更された場合、それらの書き込みが互いに上書きするのは簡単です。KV は、データが頻繁に変更されない、遅延の少ない読み込みをサポートするために、このように設計されています。しかし、これらの設計上の決定により、頻繁に変更が加えられる状態や、変更を世界中ですぐに確認する必要がある場合には、KVは適切ではありません。
一方、Durable Objects は、本来ストレージ製品ではありません。Durable Objectsの多くのユースケースでは、実際は耐久性のあるストレージを利用しません。ストレージを提供する範囲に関して、Durable Objects は、KV からのストレージスペクトラムとは対極に位置します。Durable Objectsは、トランザクション保証と即時の一貫性を必要とするワークロードに非常に適しています。ただし、トランザクションは本質的に単一の場所で調整する必要があるため、その場所から世界の反対側にあるクライアントは中程度の遅延を経験します。光の速さにも生得的な限界があるためです。Durable Objectsは、使用場所の近くに自動的に移動することで、この問題に対処します。
要するに、Workers KV は、静的コンテンツ、構成、およびその他のまれに変化するデータを世界中で提供するための最良の方法です。一方、Durable Objects は、動的な状態と調整を管理する場合に優れています。
今後は、Workers KV自体の実装にDurable Objectsを活用し、さらに優れたパフォーマンスを実現していく予定です。
CRDTを使わないのはなぜですか?
Durable Objects上にCRDベースのストレージを構築できますが、Durable ObjectsではCRDTを使用する必要はありません。
Confrict-free Replicated Data Types (CRDT) 、またはそれと同類の操作変換 (OT) は、データを同期したり、損失したりすることなく、世界中の複数の場所から同時にデータを編集できるテクノロジーです。たとえば、これらのテクノロジーは、リアルタイムコラボレーションドキュメントエディタの実装で一般的に使用され、ユーザーのkeypressがドキュメントのローカルコピーにリアルタイムに表示されます。他の誰かがドキュメントの別の部分を最初に編集したかどうかを確認するために待つ必要もありません。詳細は省いて、これらのテクニックをリアルタイムバージョンの「git fork」と「git merge」のように考えることができます。マージの競合は決定論的な方法で自動的に解決されるため、最終的に全員が同じ状態になります。
CRDTは強力な技術ですが、正しく適用することが難しいこともあります。自動競合解決には、データ損失が簡単に発生しないように、特定の種類のデータ構造のみが有効です。gitに精通している開発者は誰もが問題に気づきます。恣意的な競合解決は難しく、競合解決のために自動化されたアルゴリズムは時々間違う可能性があるのです。アルゴリズムが任意の順序でマージを処理し、それでも同じ答えを得なければならない場合、競合解決はより困難になります。
ほとんどのアプリケーションでは、CRDTは過度に複雑であり、努力しても対価が見合わないと我々は考えています。さらに悪いことに、アプリケーションの数に対して、CRDTとして表現できるデータ構造のセットがあまりにも制限されています。通常、ドキュメントごとに権限のある調整ポイントをひとつ割り当てる方がはるかに簡単です。これは、まさにDurable Objects で達成できることです。
とはいっても、CRDTはDurable Objectsに加えて使うこともできます。オブジェクトのステートが CRDT 処理の対象となる場合、アプリケーションはそのオブジェクトを違う地域の複数のオブジェクトに複製し、その後、CRDT を介してステートを同期させることができます。このことから、アプリケーションが、CRDTが労力に見合うと判断した場合、最適化として実装することは理にかなっています。
最後に:ステートが「サーバーレス」になるとはどういう意味ですか?
従来、サーバーレスはステートレスコンピューティングに重点を置いてきました。サーバーレスアーキテクチャでは、コンピューティングの論理ユニットは何か細かいものに分けられます。たとえば、HTTP リクエストなどの 1 つのイベントがそうです。これは特にうまくのですが、その理由は、イベントが、サーバーアプリケーションの設計時に考える作業の論理単位であるためです。自分たちのビジネスロジックについて「サーバー」や「コンテナ」や「プロセス」単位で考える人はいません。 イベントについて考えるのです。この意味的な連携により、サーバーレスは、サーバーを維持するロジスティックな負担を、開発者からクラウドプロバイダーへと移行させることに成功しています。
しかし、サーバーレスアーキテクチャは従来、ステートレスでした。各イベントは独立して実行されます。データを格納する場合は、従来のデータベースに接続する必要がありました。リクエスト間で調整したい場合は、その機能を提供する他のサービスに接続する必要がありました。これらの外部サービスは、サーバーレスが回避しようとしていた運用上の懸念を再び持ち込む傾向があります。開発者やサービス事業者は、増加する負荷を処理するためにデータベースをスケーリングすることだけでなく、グローバルトラフィックを効果的に処理するためにデータベースを「リージョン」(地域)に分割する方法についても心配しなければなりません。後者について考えることは特に面倒です。
では、サーバーレスの考え方をステートにどう適用できますか?サーバーレスコンピューティングは、コンピューティングを細かいピースに分割することであるように、サーバーレスステートはステートを細かいピースに分割することです。ここでも、私たちはアプリケーションの論理ユニットに対応するステートの単位を見つけようとします。アプリケーション内のステートの論理ユニットは、「テーブル」、「コレクション」または「グラフ」ではありません。それはアプリケーションによって異なります。チャットアプリのステートの論理単位はチャットルームです。オンラインスプレッドシートエディタのステートの論理単位は、スプレッドシートです。オンラインストアのステートの論理単位はショッピングカートです。ストレージ層によって提供されるストレージの物理的な単位を、アプリケーションに固有のステートの論理単位に一致させることによって、以前開発者が担わされていた、たくさんの物流上の懸念(スケーラビリティや地域性も含む)を、基盤となるストレージプロバイダー(Cloudflare)に委ねることができます。
これがDurable Objectsの仕組みです。