Cloudflare WorkersとRedisで実現するエッジコンピューティング活用術
エッジコンピューティングは、近年もっとも注目されている技術のひとつです。CDNがファイルをユーザーの近くに配置できるようにしたのと同じように、エッジコンピューティングはアプリケーションそのものをユーザーの近くで実行できるようにします。これにより、開発者はグローバルに分散され、高いパフォーマンスを発揮するアプリケーションを構築できるようになります。
Cloudflare Workersとステートレス性の課題
現在この分野をリードしている製品がCloudflare Workersです。コールドスタートのないサーバーレス実行環境を提供し、Cloudflareのグローバルネットワークを活かすことで、アプリケーションのレイテンシを最小限に抑えられます。関数はJavaScript、Rust、C、C++で記述可能です。
しかしAWS Lambdaなどのサーバーレス関数と同様に、Cloudflare Workersはステートレスです。Cloudflareの調査によると、開発者はエッジ関数からデータベースへ接続する方法を強く求めています。残念ながら、ほとんどのデータベースはサーバーレス環境向けに設計されておらず、永続的な接続を必要とします。そこで私たちはRedis上にREST APIを開発し、サーバーレスのエッジ関数からUpstashへ、最もシンプルかつ高速な方法でアクセスできるようにしました。
Cloudflare Workers KVとの比較
Cloudflareには、エッジ関数の状態を保存できる基本的なKey-Valueストアが用意されています。Upstash Redisは以下の点でCloudflare KVを上回ります。
- 豊富なデータ構造: Cloudflare KVはget/set/deleteといった基本操作のみを提供します。一方Upstashでは、Hash、List、Sorted Set、範囲操作、append、インクリメントなど、Redisのすべてのデータ構造を利用でき、より高度な機能を構築できます。
- どこからでもアクセス可能: Cloudflare KVはCloudflareエコシステム内からの利用を想定していますが、Upstash RedisはRedisプロトコルとREST APIの両方をサポートするため、どこからでもアクセス・利用できます。エッジからRedisへデータをオフロードし、任意のRedisクライアントで処理することも可能です。
- 書き込み性能: Cloudflare KVは読み取り中心の用途に最適化されており、書き込みが他のロケーションへレプリケートされるまで最大60秒かかることがあります。Upstashの書き込みレイテンシはミリ秒単位です。
それでは、CloudflareとRedisの組み合わせの威力を示す簡単な例を作成してみましょう。
エッジでのアナリティクス(アクセス解析)
Webアナリティクスは、すべてのサイト運営者にとって共通のニーズです。Google Analyticsは強力ですが、トラフィックデータをGoogleと共有したり、ユーザーのブラウザにCookieを注入したりすることを好まない開発者も多くいます。サイトへのトラフィックはCDN経由で流れるため、エッジレイヤーで簡単に計測できます。ここでは、Cloudflare Workersでユーザートラフィックを追跡するシンプルな例を実装します。Workersでトラフィックをインターセプトし、ユーザーリクエストをUpstash Redisに保存します。さらに、リクエストを分析して指定日の以下の情報を出力する、基本的なスタンドアロンアプリケーションを作成します。
- ページビュー数
- ユニークビジター数
- 訪問者数の多い上位国
- 最も閲覧されたページ
セットアップ
まだデータベースをお持ちでない場合は、公式ガイドに従って作成してください。データベースページで「REST API」ボタンをクリックし、REST URLをコピーします。Cloudflareアカウントをお持ちならWorkers関数を作成できますし、アカウントがなくてもプレイグラウンドを利用できます。
Cloudflare Workersのコード
Cloudflare Workersの関数はrequestをパラメータとして受け取ります。UpstashのREST APIを使って、リクエストをRedis Listに記録します。キーには当日の日付を使用するため、リクエストは日ごとに別々のListに保存されます。
Upstash REST APIにはエンドポイントとトークンが必要です。データベース作成後、コンソールのREST APIボタンをクリックしてエンドポイントとトークンをコピーできます。以下のコードでエンドポイントとトークンを置き換えて、Workers関数を更新してください。
const endpoint = "REPLACE_UPSTASH_REST_ENDPOINT";
const token = "REPLACE_UPSTASH_REST_TOKEN";
async function recordRequest(request) {
let d = new Date();
let datestr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
let data = [["url", request.url], ...request.headers];
let url = endpoint + "/lpush/" + datestr;
const init = {
body: JSON.stringify(data),
method: "POST",
headers: {
Authorization: "Bearer " + token,
},
};
return await fetch(url, init);
}
async function handleRequest(request) {
recordRequest(request);
return new Response("My Awesome Website");
}
addEventListener("fetch", (event) => {
event.respondWith(
handleRequest(event.request).catch(
(err) => new Response(err.stack, { status: 500 })
)
);
});
アナリティクスツールのコード
次に、日付を引数として受け取り、アナリティクスデータを返すシンプルなコマンドラインアプリケーションを作成します。フォルダを作成してnpm initを実行し、npm install ioredisでRedisクライアントをインストールします。.env.exampleを.envとしてコピーし、Redis URL(ioredis用)を設定します。index.jsを以下のように更新してください。
const Redis = require("ioredis");
require("dotenv").config();
console.log("EDGE analytics with CloudFlare Workers and Upstash Redis.");
let redis = new Redis(process.env.REDIS_URL);
let dateArg = process.argv[2];
let datestr;
if (dateArg) {
datestr = dateArg;
} else {
let d = new Date();
datestr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
}
redis.lrange(datestr, "0", "-1", function (err, result) {
let pageview = 0;
let users = new Set();
let countries = new Map();
let paths = new Map();
if (err) {
console.log("Could not connect to Redis.");
console.error(err);
} else {
console.log(
"Upstash Redis Connection Successful. Analyzing the access logs..."
);
if (!result || result.length === 0) {
console.log("Could not find any access logs for the date:" + datestr);
} else {
for (const elem of result) {
let entries = JSON.parse(elem);
for (let entry of entries) {
if (entry[0] === "accept") {
if (entry[1].startsWith("text")) pageview++;
}
if (entry[0] === "cf-ipcountry") {
let temp = countries.get(entry[1]);
if (!temp) {
temp = 0;
}
countries.set(entry[1], temp + 1);
}
if (entry[0] === "url") {
let temp = paths.get(entry[1]);
if (!temp) {
temp = 0;
}
paths.set(entry[1], temp + 1);
}
if (entry[0] === "x-real-ip") {
users.add(entry[1]);
}
}
}
countries = new Map([...countries.entries()].sort((a, b) => b[1] - a[1]));
paths = new Map([...paths.entries()].sort((a, b) => b[1] - a[1]));
console.log("\nDATE: " + datestr);
console.log("\nPAGE VIEWS: " + pageview);
console.log("\nUNIQUE VISITORS: " + users.size);
logMap("TOP COUNTRIES", countries);
logMap("TOP PAGES", paths);
}
}
});
function logMap(title, data) {
console.log("\n" + title);
console.log("----------------");
console.log(mapEntriesToString(data));
}
function mapEntriesToString(entries) {
return Array.from(entries, ([k, v]) => `${k} : ${v}\n`).join("");
}
あとはnode index 2021-6-16のように日付を指定して実行するか、単にnode indexと実行すれば今日のデータを照会できます。

この例はあくまで出発点にすぎません。アナリティクスのニーズに応じて、チャートやテーブルを備えたリッチなWebアプリケーションへ発展させることができます。また、他のRedisデータ構造を活用すれば、さらに強力な分析も実現可能です。
Upstashのエッジロードマップ
REST APIは私たちのエッジ戦略における第一歩です。今後は以下の2つの重要な開発を予定しています。
- エッジキャッシュ:(アップデート: リリース済み)現在、すべてのRESTリクエストはデータベースのリージョンへ送信されます。エッジキャッシュをサポートすれば、RESTリクエストが世界中のすべてのエッジロケーションでキャッシュされるようになります。これにより、Cloudflare KVのように世界中で低レイテンシを提供できるようになります。
- グローバルレプリケーテッドデータベース:(アップデート: リリース済み)グローバル(マルチリージョン)レプリケーションにより、データが複数のリージョンへ複製されます。すべてのリクエスト(Redis APIとREST APIの両方)は最寄りのロケーションへ送られるため、整合性への影響を最小限に抑えつつ、グローバルに低レイテンシを実現します。
エッジロードマップについては、専用のブログ記事を執筆する予定です。ぜひTwitterでフォローして、最新情報をお見逃しなく。
-
Cloudflare WorkersとUpstash Redisで作る、Webサイト独自の待合室(Waiting Room)
この記事では、Cloudflare WorkersとUpstash Redisを組み合わせて、Webサイト向けの待合室(Waiting Room)ページを実装する方法を解説します。 なぜ待合室が必要なのか? Webサイトへの訪問者数が増えるのは本来喜ばしいことですが、必ずしもそうとは限りません。突発的な大量トラフィックはアプリケーションを容易に圧倒し、最悪の場合、サービス全体が停止してしまうこともあります。待合室は、トラフィック急増時にアクセスを制御し、リソースを保護するための有効なソリューションです。 Cloudflareには公式の「Waiting Room」機能がありますが、残念ながらBu
-
エッジキャッシングで実現する、世界どこでも5ミリ秒のRedisレイテンシ
Redisでは、データベースとクライアントが同じリージョン内にあれば、1ミリ秒のレイテンシは容易に実現できます。しかし、クライアントが世界中に分散している場合、レイテンシは100ミリ秒を超えてしまいます。この課題を解決するために開発されたのが「Edge Caching(エッジキャッシング)」です。 エッジキャッシングとは エッジキャッシングでは、CDNと同じようにREST APIのレスポンスが世界各地のエッジロケーションにキャッシュされます。エッジキャッシングを有効にすると、平均5ミリ秒というグローバルレイテンシを実現できます。実際に、10の異なるリージョンに配置したクライアントからレイテンシ