コンテンツにスキップ

サーバーレスでグローバルに分散された時系列APIをTimescaleで作成する

Last reviewed: 11 months ago

このチュートリアルでは、Timescale(彼らはPostgreSQLをクラウドで高速化します)に保存された時系列データを取り込み、クエリするAPIをWorkers上に構築する方法を学びます。

データを取り込むためのAPIルートを公開するWorker関数を作成してデプロイし、Hyperdriveを使用してエッジからデータベース接続をプロキシし、新しいデータベース接続をリクエストごとに作成しないように接続プールを維持します。

以下のことを学びます:

  • Cloudflare Workerを構築してデプロイする。
  • Wrangler CLIを使用してWorkerシークレットを管理する。
  • Timescaleデータベースサービスをデプロイする。
  • Hyperdriveを使用してWorkerをTimescaleデータベースサービスに接続する。
  • 新しいAPIをクエリする。

Timescaleについての詳細は、彼らのドキュメントを読むことで学べます。


1. Workerプロジェクトを作成する

以下のコマンドを実行して、コマンドラインからWorkerプロジェクトを作成します:

Terminal window
npm create cloudflare@latest -- timescale-api

For setup, select the following options:

  • For What would you like to start with?, choose Hello Worldの例.
  • For Which template would you like to use?, choose Hello World Worker.
  • For Which language do you want to use?, choose TypeScript.
  • For Do you want to use git for version control?, choose Yes.
  • For Do you want to deploy your application?, choose No (we will be making some changes before deploying).

アプリケーションがデプロイされたURLをメモしておいてください。これはGitHubウェブフックを設定する際に使用します。

作成したWorkerプロジェクトのディレクトリに移動します:

Terminal window
cd timescale-api

2. Timescaleサービスを準備する

新しいサービスを作成する場合は、Timescale Consoleにアクセスし、以下の手順に従ってください:

  1. 右上の黒いプラスを選択してサービスを作成を選択します。
  2. サービスの種類として時系列を選択します。
  3. 希望するリージョンとインスタンスサイズを選択します。このチュートリアルには1 CPUで十分です。
  4. ランダムに生成されたサービス名を置き換えるためのサービス名を設定します。
  5. サービスを作成を選択します。
  6. 右側の接続情報ダイアログを展開し、サービスURLをコピーします。
  7. 表示されたパスワードをコピーします。これは再取得できません。
  8. パスワードを保存しました、サービスの概要に移動を選択します。

以前に作成したサービスを使用している場合は、Timescale Consoleでサービス接続情報を取得できます:

  1. Hyperdriveが接続するサービス(データベース)を選択します。
  2. 接続情報を展開します。
  3. サービスURLをコピーします。サービスURLはHyperdriveが接続するために使用する接続文字列です。この文字列にはデータベースのホスト名、ポート番号、データベース名が含まれています。

以下のようにサービスURLにパスワードを挿入します(@の後の部分はそのままにします):

postgres://tsdbadmin:YOURPASSWORD@...

これは以下のセクションでSERVICEURLと呼ばれます。

3. ハイパーテーブルを作成する

Timescaleでは、通常のPostgreSQLテーブルをハイパーテーブルに変換できます。ハイパーテーブルは、時系列、イベント、または分析データを扱うために使用されます。この変更を行うと、Timescaleはハイパーテーブルのパーティショニングをシームレスに管理し、圧縮や継続的集計などの他の機能を適用できるようにします。

前のステップでコピーしたサービスURLを使用してTimescaleデータベースに接続します(パスワードが埋め込まれています)。

デフォルトのPostgreSQL CLIツールpsqlを使用して接続する場合、以下のようにpsqlを実行します(前のステップからのサービスURLを置き換えます)。PgAdminのようなグラフィカルツールを使用して接続することもできます。

Terminal window
psql <SERVICEURL>

接続したら、以下のSQLを貼り付けてテーブルを作成します:

CREATE TABLE readings(
ts timestamptz DEFAULT now() NOT NULL,
sensor UUID NOT NULL,
metadata jsonb,
value numeric NOT NULL
);
SELECT create_hypertable('readings', 'ts');

Timescaleは、データを取り込み、クエリする際に残りの管理を行います。

4. データベース構成を作成する

新しいHyperdriveインスタンスを作成するには、以下が必要です:

  • ステップ2からのSERVICEURL
  • Hyperdriveサービスの名前。今回のチュートリアルではhyperdriveを使用します。

Hyperdriveは、createコマンドと--connection-string引数を使用してこの情報を渡します。以下のように実行します:

Terminal window
npx wrangler hyperdrive create hyperdrive --connection-string="SERVICEURL"

このコマンドは、Hyperdrive IDを出力します。次に、以下の内容でwrangler.toml構成にHyperdrive構成をバインドできます:

name = "timescale-api"
main = "src/index.ts"
compatibility_date = "2024-08-21"
compatibility_flags = [ "nodejs_compat_v2"]
[[hyperdrive]]
binding = "HYPERDRIVE"
id = "your-id-here"

WorkerプロジェクトにPostgresドライバーをインストールします:

Terminal window
npm install pg

次に、以下のWorkerコードをコピーし、./src/index.tsの現在のコードを置き換えます。以下のコードは:

  1. Hyperdriveを使用して、env.HYPERDRIVE.connectionStringから直接ドライバーに接続します。
  2. Timescaleに一度のトランザクションで挿入するためのJSON読み取りの配列を受け入れるPOSTルートを作成します。
  3. limitパラメータを受け取り、最新の読み取りを返すGETルートを作成します。これはIDやタイムスタンプでフィルタリングするように適応できます。
import { Client } from "pg";
export interface Env {
HYPERDRIVE: Hyperdrive;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString,
});
await client.connect();
const url = new URL(request.url);
// JSONを読み取りとして挿入するためのルートを作成
if (request.method === "POST" && url.pathname === "/readings") {
// リクエストのJSONペイロードを解析
const productData = await request.json();
// 生のクエリを書く。jsonb_to_recordsetを使用してJSONをPG INSERT形式に展開し、
// tsフィールドが存在しない場合は現在のタイムスタンプで挿入するためにcoalesceを使用
const insertQuery = `
INSERT INTO readings (ts, sensor, metadata, value)
SELECT coalesce(ts, now()), sensor, metadata, value FROM jsonb_to_recordset($1::jsonb)
AS t(ts timestamptz, sensor UUID, metadata jsonb, value numeric)
`;
const insertResult = await client.query(insertQuery, [
JSON.stringify(productData),
]);
// 挿入された生の行数を収集して返す
const resp = new Response(JSON.stringify(insertResult.rowCount), {
headers: { "Content-Type": "application/json" },
});
ctx.waitUntil(client.end());
return resp;
// 時間枠内でクエリするためのルートを作成
} else if (request.method === "GET" && url.pathname === "/readings") {
const limit = url.searchParams.get("limit");
// 渡されたlimitパラメータを使用して読み取りテーブルをクエリ
const result = await client.query(
"SELECT * FROM readings ORDER BY ts DESC LIMIT $1",
[limit],
);
// 結果をJSONとして返す
const resp = new Response(JSON.stringify(result.rows), {
headers: { "Content-Type": "application/json" },
});
ctx.waitUntil(client.end());
return resp;
}
},
} satisfies ExportedHandler<Env>;

5. Workerをデプロイする

以下のコマンドを実行してWorkerを再デプロイします:

Terminal window
npx wrangler deploy

アプリケーションは現在ライブで、timescale-api.<YOUR_SUBDOMAIN>.workers.devでアクセス可能です。正確なURIは、実行したwranglerコマンドの出力に表示されます。

デプロイ後、Cloudflare Workerを使用してTimescale IoT読み取りデータベースと対話できます。エッジからの接続は、Cloudflare Hyperdriveを使用して接続するため、より高速になります。

これで、Cloudflare Workerを使用してreadingsテーブルに新しい行を挿入できます。この機能をテストするには、WorkerのURLに/readingsパスを付けて、JSONペイロードに新しい製品データを含むPOSTリクエストを送信します:

[
{ "sensor": "6f3e43a4-d1c1-4cb6-b928-0ac0efaf84a5", "value": 0.3 },
{ "sensor": "d538f9fa-f6de-46e5-9fa2-d7ee9a0f0a68", "value": 10.8 },
{ "sensor": "5cb674a0-460d-4c80-8113-28927f658f5f", "value": 18.8 },
{ "sensor": "03307bae-d5b8-42ad-8f17-1c810e0fbe63", "value": 20.0 },
{ "sensor": "64494acc-4aa5-413c-bd09-2e5b3ece8ad7", "value": 13.1 },
{ "sensor": "0a361f03-d7ec-4e61-822f-2857b52b74b3", "value": 1.1 },
{ "sensor": "50f91cdc-fd19-40d2-b2b0-c90db3394981", "value": 10.3 }
]

このチュートリアルでは、ts(タイムスタンプ)とmetadata(JSONブロブ)は省略されるため、それぞれnow()NULLに設定されます。

POSTリクエストを送信した後、WorkerのURLに/readingsパスを付けてGETリクエストを発行することもできます。limitパラメータを設定して、返されるレコードの量を制御します。

curlがインストールされている場合は、以下のコマンドでテストできます(<YOUR_SUBDOMAIN>を上記のデプロイコマンドからのサブドメインに置き換えます):

データを取り込む
curl --request POST --data @- 'https://timescale-api.<YOUR_SUBDOMAIN>.workers.dev/readings' <<EOF
[
{ "sensor": "6f3e43a4-d1c1-4cb6-b928-0ac0efaf84a5", "value":0.3},
{ "sensor": "d538f9fa-f6de-46e5-9fa2-d7ee9a0f0a68", "value":10.8},
{ "sensor": "5cb674a0-460d-4c80-8113-28927f658f5f", "value":18.8},
{ "sensor": "03307bae-d5b8-42ad-8f17-1c810e0fbe63", "value":20.0},
{ "sensor": "64494acc-4aa5-413c-bd09-2e5b3ece8ad7", "value":13.1},
{ "sensor": "0a361f03-d7ec-4e61-822f-2857b52b74b3", "value":1.1},
{ "sensor": "50f91cdc-fd19-40d2-b2b0-c90db3394981", "metadata": {"color": "blue" }, "value":10.3}
]
EOF
データをクエリする
curl "https://timescale-api.<YOUR_SUBDOMAIN>.workers.dev/readings?limit=10"

このチュートリアルでは、Timescale、Workers、Hyperdrive、TypeScriptを使用してエッジから読み取りを取り込み、クエリする作業例を作成する方法を学びました。

次のステップ