Skip to content

Commit ced7d46

Browse files
authored
Add an option for disabling sqlite on the durable object queue (#485)
* Add an option for disabling sqlite on the durable object queue * changeset
1 parent 0c93e8b commit ced7d46

File tree

4 files changed

+64
-19
lines changed

4 files changed

+64
-19
lines changed

.changeset/silver-baboons-begin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
add an option for disabling sqlite on the durable object queue

packages/cloudflare/src/api/cloudflare-context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ declare global {
3737
REVALIDATION_RETRY_INTERVAL_MS?: string;
3838
// The maximum number of attempts that can be made to revalidate a path
3939
MAX_REVALIDATION_ATTEMPTS?: string;
40+
// Disable SQLite for the durable object queue handler
41+
// This can be safely used if you don't use an eventually consistent incremental cache (i.e. R2 without the regional cache for example)
42+
REVALIDATION_DO_DISABLE_SQLITE?: string;
4043
}
4144
}
4245

packages/cloudflare/src/api/durable-objects/queue.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ const createDurableObjectQueue = ({
1515
fetchDuration,
1616
statusCode,
1717
headers,
18+
disableSQLite,
1819
}: {
1920
fetchDuration: number;
2021
statusCode?: number;
2122
headers?: Headers;
23+
disableSQLite?: boolean;
2224
}) => {
2325
const mockState = {
2426
waitUntil: vi.fn(),
@@ -52,6 +54,7 @@ const createDurableObjectQueue = ({
5254
),
5355
connect: vi.fn(),
5456
},
57+
REVALIDATION_DO_DISABLE_SQLITE: disableSQLite ? "true" : undefined,
5558
});
5659
};
5760

@@ -323,4 +326,29 @@ describe("DurableObjectQueue", () => {
323326
expect(queue.service.fetch).toHaveBeenCalledTimes(2);
324327
});
325328
});
329+
330+
describe("disableSQLite", () => {
331+
it("should not initialize the sqlite storage", async () => {
332+
const queue = createDurableObjectQueue({ fetchDuration: 10, disableSQLite: true });
333+
expect(queue.sql.exec).not.toHaveBeenCalled();
334+
});
335+
336+
it("should not write to the sqlite storage on failed state", async () => {
337+
const queue = createDurableObjectQueue({ fetchDuration: 10, disableSQLite: true });
338+
await queue.addToFailedState(createMessage("id"));
339+
expect(queue.sql.exec).not.toHaveBeenCalled();
340+
});
341+
342+
it("should not read from the sqlite storage on checkSyncTable", async () => {
343+
const queue = createDurableObjectQueue({ fetchDuration: 10, disableSQLite: true });
344+
queue.checkSyncTable(createMessage("id"));
345+
expect(queue.sql.exec).not.toHaveBeenCalled();
346+
});
347+
348+
it("should not write to sql on successful revalidation", async () => {
349+
const queue = createDurableObjectQueue({ fetchDuration: 10, disableSQLite: true });
350+
await queue.revalidate(createMessage("id"));
351+
expect(queue.sql.exec).not.toHaveBeenCalled();
352+
});
353+
});
326354
});

packages/cloudflare/src/api/durable-objects/queue.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
3636
readonly revalidationTimeout: number;
3737
readonly revalidationRetryInterval: number;
3838
readonly maxRevalidationAttempts: number;
39+
readonly disableSQLite: boolean;
3940

4041
constructor(ctx: DurableObjectState, env: CloudflareEnv) {
4142
super(ctx, env);
@@ -44,12 +45,6 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
4445
if (!this.service) throw new IgnorableError("No service binding for cache revalidation worker");
4546
this.sql = ctx.storage.sql;
4647

47-
// We restore the state
48-
ctx.blockConcurrencyWhile(async () => {
49-
debug(`Restoring the state of the durable object`);
50-
await this.initState();
51-
});
52-
5348
this.maxRevalidations = env.MAX_REVALIDATION_BY_DURABLE_OBJECT
5449
? parseInt(env.MAX_REVALIDATION_BY_DURABLE_OBJECT)
5550
: DEFAULT_MAX_REVALIDATION_BY_DURABLE_OBJECT;
@@ -66,6 +61,14 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
6661
? parseInt(env.MAX_REVALIDATION_ATTEMPTS)
6762
: DEFAULT_MAX_REVALIDATION_ATTEMPTS;
6863

64+
this.disableSQLite = env.REVALIDATION_DO_DISABLE_SQLITE === "true";
65+
66+
// We restore the state
67+
ctx.blockConcurrencyWhile(async () => {
68+
debug(`Restoring the state of the durable object`);
69+
await this.initState();
70+
});
71+
6972
debug(`Durable object initialized`);
7073
}
7174

@@ -103,7 +106,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
103106
this.ctx.waitUntil(revalidationPromise);
104107
}
105108

106-
private async executeRevalidation(msg: QueueMessage) {
109+
async executeRevalidation(msg: QueueMessage) {
107110
try {
108111
debug(`Revalidating ${msg.MessageBody.host}${msg.MessageBody.url}`);
109112
const {
@@ -151,12 +154,14 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
151154
// Everything went well, we can update the sync table
152155
// We use unixepoch here,it also works with Date.now()/1000, but not with Date.now() alone.
153156
// TODO: This needs to be investigated
154-
this.sql.exec(
155-
"INSERT OR REPLACE INTO sync (id, lastSuccess, buildId) VALUES (?, unixepoch(), ?)",
156-
// We cannot use the deduplication id because it's not unique per route - every time a route is revalidated, the deduplication id is different.
157-
`${host}${url}`,
158-
process.env.__NEXT_BUILD_ID
159-
);
157+
if (!this.disableSQLite) {
158+
this.sql.exec(
159+
"INSERT OR REPLACE INTO sync (id, lastSuccess, buildId) VALUES (?, unixepoch(), ?)",
160+
// We cannot use the deduplication id because it's not unique per route - every time a route is revalidated, the deduplication id is different.
161+
`${host}${url}`,
162+
process.env.__NEXT_BUILD_ID
163+
);
164+
}
160165
// If everything went well, we can remove the route from the failed state
161166
this.routeInFailedState.delete(msg.MessageDeduplicationId);
162167
} catch (e) {
@@ -217,12 +222,14 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
217222
};
218223
}
219224
this.routeInFailedState.set(msg.MessageDeduplicationId, updatedFailedState);
220-
this.sql.exec(
221-
"INSERT OR REPLACE INTO failed_state (id, data, buildId) VALUES (?, ?, ?)",
222-
msg.MessageDeduplicationId,
223-
JSON.stringify(updatedFailedState),
224-
process.env.__NEXT_BUILD_ID
225-
);
225+
if (!this.disableSQLite) {
226+
this.sql.exec(
227+
"INSERT OR REPLACE INTO failed_state (id, data, buildId) VALUES (?, ?, ?)",
228+
msg.MessageDeduplicationId,
229+
JSON.stringify(updatedFailedState),
230+
process.env.__NEXT_BUILD_ID
231+
);
232+
}
226233
// We probably want to do something if routeInFailedState is becoming too big, at least log it
227234
await this.addAlarm();
228235
}
@@ -246,6 +253,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
246253
// We don't restore the ongoing revalidations because we cannot know in which state they are
247254
// We only restore the failed state and the alarm
248255
async initState() {
256+
if (this.disableSQLite) return;
249257
// We store the failed state as a blob, we don't want to do anything with it anyway besides restoring
250258
this.sql.exec("CREATE TABLE IF NOT EXISTS failed_state (id TEXT PRIMARY KEY, data TEXT, buildId TEXT)");
251259

@@ -272,6 +280,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
272280
*/
273281
checkSyncTable(msg: QueueMessage) {
274282
try {
283+
if (this.disableSQLite) return false;
275284
const numNewer = this.sql
276285
.exec<{
277286
numNewer: number;

0 commit comments

Comments
 (0)