I Tried Connecting Directly to Amazon Aurora DSQL Using Cloudflare Workers' TCP Sockets
This page has been translated by machine translation. View original
In the previous article, I connected to Aurora DSQL from AWS Lambda and tested write and read performance.
This time, I will check whether it is possible to connect to Aurora DSQL from Cloudflare Workers' TCP sockets.
Why connect to DSQL from Workers
I am considering turning some of the blog feature's APIs into microservices using Cloudflare Workers + D1. D1 is SQLite-based and excels at read performance at the edge, but it is less suited to advanced aggregation processing such as complex JOINs and window functions.
So I am thinking of a setup that uses Aurora DSQL, which supports Postgres-compatible SQL, alongside D1 as a "master data and aggregation DB," and periodically syncs the results to D1 with a Cron Trigger. To realize this setup, it is a fundamental prerequisite that Workers can directly query DSQL.
Cloudflare Workers has supported TCP sockets (the connect() API) since May 2023, so direct connections to PostgreSQL are already possible. However, it was unknown whether DSQL's unique IAM token authentication would work properly in the Workers runtime environment, so I decided to start by verifying connectivity.
Test environment
| Item | Value |
|---|---|
| DSQL region | us-west-2 |
| Docker image | node:22-slim + wrangler 4.71.0 |
| Bundle size | 147.79 KiB / gzip: 37.37 KiB |
| Worker Startup Time | 14–16 ms |
I put wrangler, the development CLI for Cloudflare Workers, in a Docker container so that I could test without polluting my local environment.
FROM node:22-slim
RUN npm install -g wrangler
WORKDIR /app
EXPOSE 8787
# compose.yaml
services:
worker:
build: .
volumes:
- ./app:/app
ports:
- "8788:8787"
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
working_dir: /app
command: sleep infinity
Here is wrangler.toml. The nodejs_compat flag is required; without it, TCP sockets cannot be used.
name = "dsql-test"
main = "src/index.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
npm install postgres @aws-sdk/dsql-signer @aws/aurora-dsql-postgresjs-connector
Connecting with the official connector
I connect using the official connector @aws/aurora-dsql-postgresjs-connector. The connector transparently handles token generation and connection parameter optimization, so the amount of code is small.
import { auroraDSQLPostgres } from "@aws/aurora-dsql-postgresjs-connector";
interface Env {
AWS_ACCESS_KEY_ID: string;
AWS_SECRET_ACCESS_KEY: string;
AWS_SESSION_TOKEN: string;
DSQL_ENDPOINT: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sql = auroraDSQLPostgres({
host: env.DSQL_ENDPOINT,
username: "admin",
customCredentialsProvider: async () => ({
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
sessionToken: env.AWS_SESSION_TOKEN,
}),
});
try {
const [row] = await sql`SELECT current_timestamp as ts`;
await sql.end();
return Response.json({ ok: true, timestamp: row.ts });
} catch (e: any) {
try { await sql.end(); } catch {}
return Response.json({ ok: false, error: e.message }, { status: 500 });
}
},
};
I ran wrangler deploy and sent a request.
{ "ok": true, "timestamp": "2026-03-08T08:49:29.290Z" }
The connection worked without any problems.
Querying real data
Next, let's try fetching data from actual tables.
const posts = await sql`SELECT id, title FROM blogpost_meta LIMIT 3`;
const tags = await sql`SELECT * FROM tag_master LIMIT 3`;
{
"posts": [
{ "id": "00oAKWv1w3mtdzVecJwdU", "title": "[2025年7月21日から]BigQueryの..." }
],
"tags": [
{ "tag_name": "1on1", "usage_count": 14, "tag_category": "Methodology" }
]
}
The data was retrieved without any problems. Japanese text also came back without garbled characters.
Latency measurement
I added measurement with Date.now() to the previous code and measured the processing time inside the Worker (connection establishment + query execution). These are the results for two clusters: us-west-2, which is in the same region as the Worker edge (PDX), and ap-northeast-1, which is across the Pacific Ocean.
| Measurement | us-west-2 (near the edge) | ap-northeast-1 (across the Pacific) |
|---|---|---|
| SELECT 1 row | median 100–170ms | median 1,400–1,800ms |
| INSERT 100 rows (serial) | ~16ms per row | ~147ms per row |
In us-west-2, close to the Worker edge (PDX), SELECT stays at around 100–170ms, but in ap-northeast-1 across the Pacific, latency was about 10 times higher. These numbers include everything: the TCP/TLS handshake + IAM token generation + query execution.
Note that this time I called the Worker from an EC2 instance in Oregon, so the Worker was launched at the Oregon edge (PDX = the IATA code for Portland International Airport). I will cover this point in more detail in the next section.
Caveats of Workers × DSQL
Managing AWS credentials
With Lambda, IAM authentication is handled transparently through the execution role, but Workers runs outside AWS, so you need to pass access keys explicitly. For this test, for convenience, I used temporary credentials (access key + session token) issued with STS assume-role. However, STS temporary tokens expire after at most 12 hours, so they are not suited to being registered statically in Cloudflare Secrets. In production, I recommend managing the access key of an IAM user with least-privilege permissions for DSQL connections as a secret, stored securely.
You cannot control the edge location
Workers launches at the edge closest to the origin of the request. The application cannot control which edge it runs on. In this test I called it from an EC2 instance in Oregon, so the Worker also launched at the Oregon edge (PDX) and could connect from a position close to the DSQL in the same region.
However, if a user in Japan accesses it, for example, it will launch at the Tokyo edge, and the connection to DSQL in us-west-2 will incur an RTT across the Pacific. With Lambda, you can pin the region by "deploying to the Tokyo region and connecting to DSQL in Tokyo," but you cannot do that with Workers. You need a design that assumes increased latency caused by the distance to DSQL.
Connections cannot be reused
It is natural to think, "Can't we just keep the connection in a global variable and reuse it?", but this does not work in Workers.
Cannot perform I/O on behalf of a different request.
I/O objects (such as streams, request/response bodies, and others)
created in the context of one request handler cannot be accessed
from a different request's handler.
In Workers, the I/O context is isolated per request, so a TCP socket created in one request cannot be reused from another request. Every request ends up making a fresh connection (TCP/TLS handshake + IAM token generation).
In the previous Lambda article, connection reuse was the biggest point. Thanks to reuse, INSERT improved to under 10ms and high throughput was maintained, but Workers cannot benefit from this.
What also needs attention is the interaction with DSQL's connection rate limit (100 connections per second, a hard limit that cannot be raised). With Workers, the number of requests and the number of new DB connections are 1:1, so if traffic exceeds 100 rps, connections will be rejected on the DSQL side. If high-frequency access is expected, you should consider an architecture that puts a cache or asynchronous processing in between, rather than connecting directly from Workers to DSQL.
Reference: Implementation without the connector
There is also a way to skip the official connector, generate the token directly with @aws-sdk/dsql-signer, and pass it to postgres.js.
import { DsqlSigner } from "@aws-sdk/dsql-signer";
import postgres from "postgres";
const signer = new DsqlSigner({
hostname: env.DSQL_ENDPOINT,
region: "us-west-2",
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
sessionToken: env.AWS_SESSION_TOKEN,
},
});
const token = await signer.getDbConnectAdminAuthToken();
const sql = postgres({
host: env.DSQL_ENDPOINT,
port: 5432,
database: "postgres",
username: "admin",
password: token,
ssl: "require",
});
This also worked without problems in the Workers runtime. SigV4 is an HTTP-based signing process, so there are no compatibility issues with Workers. However, using the official connector removes the need to optimize token generation and idleTimeoutMillis / maxLifetimeSeconds yourself, so I recommend using the connector unless you have a specific reason not to.
Summary
I confirmed that you can connect to Aurora DSQL without problems by combining Workers' TCP sockets with the official connector.
On the other hand, Workers has constraints that differ from Lambda, such as being unable to control the edge location and being unable to reuse connections, which makes it easy to hit DSQL's connection limit (100 connections/second). Given these characteristics, direct connections from Workers to DSQL are suited to use cases without strict latency requirements, such as periodic synchronization via Cron Trigger and background processing.
When low latency or high throughput is required on the user request path, a realistic approach seems to be syncing DSQL data to D1 with a Cron Trigger to take advantage of edge reads, or making use of Workers' page cache.
