Last active
May 31, 2025 10:47
-
-
Save farnoy/32dc892ec7218d55beb1c2037db9ca78 to your computer and use it in GitHub Desktop.
Cloudflare durable object storage latency repro: test by visiting `/whatever?transact=select-1&n=1`, or `/whatever?transact=no-op&n=1`, and `/whatever?transact=local-no-op`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { DurableObject } from "cloudflare:workers"; | |
import { z } from "zod/v4"; | |
const transactMethod = z | |
.enum(["transaction", "sync-sql", "blockConcurrencyWhile", "racy", "no-op", "select-1", "local-no-op"]) | |
.default("sync-sql"); | |
export class MyDurableObject extends DurableObject<Env> { | |
constructor(ctx: DurableObjectState, env: Env) { | |
super(ctx, env); | |
this.ctx.blockConcurrencyWhile(async () => { | |
// this.ctx.storage.sql.exec("DROP TABLE IF EXISTS vars"); | |
this.ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS vars(key integer primary key, value integer)"); | |
}); | |
} | |
async perform(id: number, method: z.infer<typeof transactMethod>) { | |
let counter: number; | |
if (method === "transaction") | |
await this.ctx.storage.transaction(async () => { | |
this.ctx.storage.sql.exec("INSERT INTO vars (key, value) VALUES (?, 0) ON CONFLICT DO NOTHING", id); | |
counter = this.ctx.storage.sql.exec("SELECT * FROM vars WHERE key = ?", id).one().value as number; | |
await new Promise((resolve) => setTimeout(resolve, 100)); | |
this.ctx.storage.sql.exec("UPDATE vars SET value = ? WHERE key = ?", counter + 1, id); | |
}); | |
else if (method === "sync-sql") { | |
this.ctx.storage.sql.exec("INSERT INTO vars (key, value) VALUES (?, 0) ON CONFLICT DO NOTHING", id); | |
counter = this.ctx.storage.sql.exec("SELECT * FROM vars WHERE key = ?", id).one().value as number; | |
this.ctx.storage.sql.exec("UPDATE vars SET value = ? WHERE key = ?", counter + 1, id); | |
} else if (method === "blockConcurrencyWhile") | |
await this.ctx.blockConcurrencyWhile(async () => { | |
this.ctx.storage.sql.exec("INSERT INTO vars (key, value) VALUES (?, 0) ON CONFLICT DO NOTHING", id); | |
counter = this.ctx.storage.sql.exec("SELECT * FROM vars WHERE key = ?", id).one().value as number; | |
await new Promise((resolve) => setTimeout(resolve, 100)); | |
this.ctx.storage.sql.exec("UPDATE vars SET value = ? WHERE key = ?", counter + 1, id); | |
}); | |
else if (method === "racy") { | |
this.ctx.storage.sql.exec("INSERT INTO vars (key, value) VALUES (?, 0) ON CONFLICT DO NOTHING", id); | |
counter = this.ctx.storage.sql.exec("SELECT * FROM vars WHERE key = ?", id).one().value as number; | |
await new Promise((resolve) => setTimeout(resolve, 100)); | |
this.ctx.storage.sql.exec("UPDATE vars SET value = ? WHERE key = ?", counter + 1, id); | |
} else if (method === "select-1") { | |
this.ctx.storage.sql.exec("INSERT INTO vars (key, value) VALUES (?, 0)", Math.round(Math.random() * 10000)); | |
counter = this.ctx.storage.sql.exec("SELECT * FROM vars LIMIT 1").one().value as number; | |
} else { | |
counter = 0; | |
} | |
return counter! + 1; | |
} | |
} | |
export default { | |
async fetch(request: Request, env: Env, ctx: ExecutionContext) { | |
const url = new URL(request.url); | |
if (url.pathname === "/favicon.ico") return new Response(null, { status: 404 }); | |
const id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName(new URL(request.url).pathname); | |
const stub = env.MY_DURABLE_OBJECT.get(id); | |
const transact = transactMethod.safeParse(url.searchParams.get("transact") ?? undefined); | |
const requests = z.coerce | |
.number() | |
.pipe(z.int()) | |
.default(10) | |
.safeParse(url.searchParams.get("n") ?? undefined); | |
if (transact.error || requests.error) return new Response(null, { status: 400 }); | |
if (transact.data === "local-no-op") return new Response("OK"); | |
const promises = await Promise.all(Array.from({ length: requests.data }, () => stub.perform(2, transact.data))); | |
return new Response(`parallel results: ${promises}`); | |
}, | |
} satisfies ExportedHandler<Env>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "workers-play", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"private": true, | |
"keywords": [], | |
"author": "", | |
"license": "ISC", | |
"packageManager": "[email protected]", | |
"devDependencies": { | |
"@cloudflare/vitest-pool-workers": "^0.8.34", | |
"typescript": "^5.8.3", | |
"vitest": "^3.1.4", | |
"wrangler": "^4.18.0" | |
}, | |
"dependencies": { | |
"zod": "^3.25.42" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
/* Visit https://aka.ms/tsconfig.json to read more about this file */ | |
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ | |
"target": "es2021", | |
/* Specify a set of bundled library declaration files that describe the target runtime environment. */ | |
"lib": ["es2021"], | |
/* Specify what module code is generated. */ | |
"module": "es2022", | |
/* Specify how TypeScript looks up a file from a given module specifier. */ | |
"moduleResolution": "Bundler", | |
/* Enable importing .json files */ | |
"resolveJsonModule": true, | |
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ | |
"allowJs": true, | |
/* Enable error reporting in type-checked JavaScript files. */ | |
"checkJs": false, | |
/* Disable emitting files from a compilation. */ | |
"noEmit": true, | |
/* Ensure that each file can be safely transpiled without relying on other imports. */ | |
"isolatedModules": true, | |
/* Allow 'import x from y' when a module doesn't have a default export. */ | |
"allowSyntheticDefaultImports": true, | |
/* Ensure that casing is correct in imports. */ | |
"forceConsistentCasingInFileNames": true, | |
/* Enable all strict type-checking options. */ | |
"strict": true, | |
/* Skip type checking all .d.ts files. */ | |
"skipLibCheck": true | |
}, | |
"exclude": ["test"], | |
"include": ["worker-configuration.d.ts", "src/**/*.ts"] | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = "main" | |
compatibility_date = "2025-05-30" | |
main = "index.ts" | |
[durable_objects] | |
bindings = [ | |
{ name = "MY_DURABLE_OBJECT", class_name = "MyDurableObject" }, | |
] | |
[[migrations]] | |
tag = "v1" | |
new_sqlite_classes = ["MyDurableObject"] | |
[observability] | |
enabled = true | |
head_sampling_rate = 1 | |
[placement] | |
mode = "smart" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment