Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Timescale でサーバーレスかつグローバル分散の時系列 API を作成する

最終更新 Markdown で表示Agent セットアップ

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

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

次の内容を学びます。

  • Cloudflare Worker の構築とデプロイ
  • Wrangler CLI での Worker シークレットの利用
  • Timescale データベースサービスのデプロイ
  • Hyperdrive で Worker を Timescale データベースサービスへ接続する
  • 新しい API の照会

Timescale の詳細は、Timescale の ドキュメント を参照してください。


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

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

npm create cloudflare@latest -- timescale-api

セットアップでは、次のオプションを選びます。

  • What would you like to start with? では、Hello World example を選びます。
  • Which template would you like to use? では、Worker only を選びます。
  • Which language do you want to use? では、TypeScript を選びます。
  • Do you want to use git for version control? では、Yes を選びます。
  • Do you want to deploy your application? では、No を選びます(デプロイ前にいくつか変更します)。

アプリケーションがデプロイされた URL を控えておきます。GitHub webhook を設定するときに使います。

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

cd timescale-api

2. Timescale Service を準備する

新しいサービスを作成する場合は、Timescale Console を開き、次の手順を行います。

  1. 右上の黒いプラスを選び、Create Service を選択します。
  2. サービスの種類として Time Series を選びます。
  3. 希望のリージョンとインスタンスサイズを選びます。このチュートリアルでは 1 CPU で十分です。
  4. ランダム生成された名前を、任意のサービス名に置き換えます。
  5. Create Service を選択します。
  6. 右側で Connection Info ダイアログを展開し、Service URL をコピーします。
  7. 表示されたパスワードをコピーします。あとから再表示できません。
  8. I stored my password, go to service overview を選択します。

以前作成したサービスを使う場合は、Timescale Console で接続情報を確認できます。

  1. Hyperdrive を接続するサービス(データベース)を選びます。
  2. Connection info を展開します。
  3. Service URL をコピーします。Service URL は Hyperdrive が接続に使う接続文字列です。この文字列には、データベースのホスト名、ポート番号、データベース名が含まれます。

Service URL にパスワードを次のように挿入します(@ 以降はそのままにします)。

postgres://tsdbadmin:YOURPASSWORD@...

以降のセクションでは、これを SERVICEURL と呼びます。

3. Hypertable を作成する

Timescale では、通常の PostgreSQL テーブルを hypertables(時系列、イベント、分析データを扱うテーブル)へ変換できます。この変更後、Timescale が hypertable のパーティショニングを透過的に管理し、圧縮や継続的な集計などの機能も適用できます。

前の手順でコピーした Service URL(パスワードを含みます)で、Timescale データベースへ接続します。

デフォルトの PostgreSQL CLI ツール psql で接続する場合は、次のように実行します(前の手順の Service URL に置き換えます)。PgAdmin などのグラフィカルツールでも接続できます。

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 インスタンスを作成するには、次が必要です。

  • 手順 2SERVICEURL
  • Hyperdrive サービスの名前。このチュートリアルでは hyperdrive を使います。

Hyperdrive は create コマンドと --connection-string 引数でこの情報を渡します。次のように実行します。

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

このコマンドは Hyperdrive ID を出力します。Wrangler 設定の内容を次に置き換え、Hyperdrive 構成を Worker にバインドします。

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "timescale-api",
	"main": "src/index.ts",
	// Set this to today's date
	"compatibility_date": "2026-09-20",
	"compatibility_flags": [
		"nodejs_compat"
	],
	"hyperdrive": [
		{
			"binding": "HYPERDRIVE",
			"id": "your-id-here"
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "timescale-api"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
compatibility_flags = [ "nodejs_compat" ]

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "your-id-here"

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

npm i pg

次の Worker コードをコピーし、./src/index.ts の現在のコードを置き換えます。このコードは次を行います。

  1. env.HYPERDRIVE.connectionString から生成した接続文字列をドライバーへ直接渡し、Hyperdrive 経由で Timescale へ接続します。
  2. JSON の readings 配列を受け取り、1 つのトランザクションで Timescale へ挿入する POST ルートを作成します。
  3. limit パラメーターを受け取り、最新の readings を返す GET ルートを作成します。ID やタイムスタンプで絞り込むように拡張できます。
import { Client } from "pg";

export interface Env {
	HYPERDRIVE: Hyperdrive;
}

export default {
	async fetch(request, env, ctx): Promise<Response> {
		// Create a new client on each request. Hyperdrive maintains the underlying
		// database connection pool, so creating a new client is fast.
		const client = new Client({
			connectionString: env.HYPERDRIVE.connectionString,
		});
		await client.connect();

		const url = new URL(request.url);
		// Create a route for inserting JSON as readings
		if (request.method === "POST" && url.pathname === "/readings") {
			// Parse the request's JSON payload
			const productData = await request.json();

			// Write the raw query. You are using jsonb_to_recordset to expand the JSON
			// to PG INSERT format to insert all items at once, and using coalesce to
			// insert with the current timestamp if no ts field exists
			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),
			]);

			// Collect the raw row count inserted to return
			const resp = new Response(JSON.stringify(insertResult.rowCount), {
				headers: { "Content-Type": "application/json" },
			});

			return resp;

			// Create a route for querying within a time-frame
		} else if (request.method === "GET" && url.pathname === "/readings") {
			const limit = url.searchParams.get("limit");

			// Query the readings table using the limit param passed
			const result = await client.query(
				"SELECT * FROM readings ORDER BY ts DESC LIMIT $1",
				[limit],
			);

			// Return the result as JSON
			const resp = new Response(JSON.stringify(result.rows), {
				headers: { "Content-Type": "application/json" },
			});

			return resp;
		}
	},
} satisfies ExportedHandler<Env>;

5. Worker をデプロイする

次のコマンドを実行し、Worker を再デプロイします。

npx wrangler deploy

アプリケーションは timescale-api.<YOUR_SUBDOMAIN>.workers.dev で公開されます。正確な URI は、今実行した wrangler コマンドの出力に表示されます。

デプロイ後は、Cloudflare Worker から Timescale の IoT readings データベースを操作できます。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> を上のデプロイコマンドで表示されたサブドメインに置き換えます)。

Ingest some databash
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
Query some datash
curl "https://timescale-api.<YOUR_SUBDOMAIN>.workers.dev/readings?limit=10"

このチュートリアルでは、Timescale、Workers、Hyperdrive、TypeScript を使い、エッジから readings を取り込み、照会する動作例を作成しました。

次のステップ

役に立ちましたか?