Skip to content

Commit b0f1a19

Browse files
committed
use two tables for the tag cache
1 parent 1feaaad commit b0f1a19

File tree

5 files changed

+48
-19
lines changed

5 files changed

+48
-19
lines changed

examples/e2e/app-router/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"start": "next start --port 3001",
1010
"lint": "next lint",
1111
"clean": "rm -rf .turbo node_modules .next .open-next",
12-
"d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS tags\"",
12+
"d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS revalidations\"",
1313
"d1:setup": "wrangler d1 execute NEXT_CACHE_D1 --file .open-next/cloudflare/cache-assets-manifest.sql",
1414
"build:worker": "pnpm opennextjs-cloudflare",
1515
"preview": "pnpm build:worker && pnpm d1:clean && pnpm d1:setup && pnpm wrangler dev",

packages/cloudflare/env.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ declare global {
77
NEXT_PRIVATE_DEBUG_CACHE?: string;
88
OPEN_NEXT_ORIGIN: string;
99
NODE_ENV?: string;
10+
NEXT_CACHE_D1_TAGS_TABLE?: string;
11+
NEXT_CACHE_D1_REVALIDATIONS_TABLE?: string;
1012
}
1113
}
1214
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ declare global {
44
interface CloudflareEnv {
55
NEXT_CACHE_WORKERS_KV?: KVNamespace;
66
NEXT_CACHE_D1?: D1Database;
7-
NEXT_CACHE_D1_TABLE?: string;
7+
NEXT_CACHE_D1_TAGS_TABLE?: string;
8+
NEXT_CACHE_D1_REVALIDATIONS_TABLE?: string;
89
ASSETS?: Fetcher;
910
}
1011
}

packages/cloudflare/src/api/d1-tag-cache.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ import { getCloudflareContext } from "./cloudflare-context.js";
77
/**
88
* An instance of the Tag Cache that uses a D1 binding (`NEXT_CACHE_D1`) as it's underlying data store.
99
*
10-
* The table used defaults to `tags`, but can be configured with the `NEXT_CACHE_D1_TABLE`
10+
* **Tag/path mappings**
11+
*
12+
* Information about the relation between tags and paths is stored in a `tags` table that contains
13+
* two columns; `tag`, and `path`. The table name can be configured with `NEXT_CACHE_D1_TAGS_TABLE`
1114
* environment variable.
1215
*
13-
* There should be three columns created in the table; `tag`, `path`, and `revalidatedAt`.
16+
* **Tag revalidations**
17+
*
18+
* Revalidation times for tags are stored in a `revalidations` table that contains two columns; `tags`,
19+
* and `revalidatedAt`. The table name can be configured with `NEXT_CACHE_D1_REVALIDATIONS_TABLE`
20+
* environment variable.
1421
*/
1522
class D1TagCache implements TagCache {
1623
public readonly name = "d1-tag-cache";
@@ -23,7 +30,7 @@ class D1TagCache implements TagCache {
2330

2431
try {
2532
const { success, results } = await db
26-
.prepare(`SELECT tag FROM ${table} WHERE path = ?`)
33+
.prepare(`SELECT tag FROM ${table.tags} WHERE path = ?`)
2734
.bind(path)
2835
.all<{ tag: string }>();
2936

@@ -47,7 +54,7 @@ class D1TagCache implements TagCache {
4754

4855
try {
4956
const { success, results } = await db
50-
.prepare(`SELECT path FROM ${table} WHERE tag = ?`)
57+
.prepare(`SELECT path FROM ${table.tags} WHERE tag = ?`)
5158
.bind(tag)
5259
.all<{ path: string }>();
5360

@@ -69,7 +76,11 @@ class D1TagCache implements TagCache {
6976

7077
try {
7178
const { success, results } = await db
72-
.prepare(`SELECT tag FROM ${table} WHERE path = ? AND revalidatedAt > ?`)
79+
.prepare(
80+
`SELECT ${table.revalidations}.tag FROM ${table.revalidations}
81+
INNER JOIN ${table.tags} ON ${table.revalidations}.tag = ${table.tags}.tag
82+
WHERE ${table.tags}.path = ? AND ${table.revalidations}.revalidatedAt > ?;`
83+
)
7384
.bind(this.getCacheKey(path), lastModified ?? 0)
7485
.all<{ tag: string }>();
7586

@@ -89,11 +100,19 @@ class D1TagCache implements TagCache {
89100

90101
try {
91102
const results = await db.batch(
92-
tags.map(({ tag, path, revalidatedAt }) =>
93-
db
94-
.prepare(`INSERT INTO ${table} (tag, path, revalidatedAt) VALUES(?, ?, ?)`)
95-
.bind(this.getCacheKey(tag), this.getCacheKey(path), revalidatedAt ?? Date.now())
96-
)
103+
tags.map(({ tag, path, revalidatedAt }) => {
104+
if (revalidatedAt === 1) {
105+
// new tag/path mapping from set
106+
return db
107+
.prepare(`INSERT INTO ${table.tags} (tag, path) VALUES (?, ?)`)
108+
.bind(this.getCacheKey(tag), this.getCacheKey(path));
109+
}
110+
111+
// tag was revalidated
112+
return db
113+
.prepare(`INSERT INTO ${table.revalidations} (tag, revalidatedAt) VALUES (?, ?)`)
114+
.bind(this.getCacheKey(tag), revalidatedAt ?? Date.now());
115+
})
97116
);
98117

99118
const failedResults = results.filter((res) => !res.success);
@@ -109,7 +128,6 @@ class D1TagCache implements TagCache {
109128
private getConfig() {
110129
const cfEnv = getCloudflareContext().env;
111130
const db = cfEnv.NEXT_CACHE_D1;
112-
const table = cfEnv.NEXT_CACHE_D1_TABLE ?? "tags";
113131

114132
if (!db) debug("No D1 database found");
115133

@@ -120,7 +138,14 @@ class D1TagCache implements TagCache {
120138
return { isDisabled: true as const };
121139
}
122140

123-
return { isDisabled: false as const, db, table };
141+
return {
142+
isDisabled: false as const,
143+
db,
144+
table: {
145+
tags: cfEnv.NEXT_CACHE_D1_TAGS_TABLE ?? "tags",
146+
revalidations: cfEnv.NEXT_CACHE_D1_REVALIDATIONS_TABLE ?? "revalidations",
147+
},
148+
};
124149
}
125150

126151
protected removeBuildId(key: string) {

packages/cloudflare/src/cli/build/open-next/compile-cache-assets-manifest.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,25 @@ export function compileCacheAssetsManifestSqlFile(options: BuildOptions) {
1717
const rawManifestPath = path.join(options.outputDir, "dynamodb-provider/dynamodb-cache.json");
1818
const outputPath = path.join(options.outputDir, "cloudflare/cache-assets-manifest.sql");
1919

20-
const table = process.env.NEXT_CACHE_D1 || "tags";
20+
const tagsTable = process.env.NEXT_CACHE_D1_TAGS_TABLE || "tags";
21+
const revalidationsTable = process.env.NEXT_CACHE_D1_REVALIDATIONS_TABLE || "revalidations";
2122

2223
mkdirSync(path.dirname(outputPath), { recursive: true });
2324
writeFileSync(
2425
outputPath,
25-
`CREATE TABLE IF NOT EXISTS ${table} (tag TEXT NOT NULL, path TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag, path) ON CONFLICT REPLACE);\n`
26+
`CREATE TABLE IF NOT EXISTS ${tagsTable} (tag TEXT NOT NULL, path TEXT NOT NULL, UNIQUE(tag, path) ON CONFLICT REPLACE);
27+
CREATE TABLE IF NOT EXISTS ${revalidationsTable} (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);\n`
2628
);
2729

2830
if (existsSync(rawManifestPath)) {
2931
const rawManifest: RawManifest = JSON.parse(readFileSync(rawManifestPath, "utf-8"));
3032

3133
const values = rawManifest.map(
32-
({ tag, path, revalidatedAt }) =>
33-
`(${JSON.stringify(tag.S)}, ${JSON.stringify(path.S)}, ${revalidatedAt.N})`
34+
({ tag, path }) => `(${JSON.stringify(tag.S)}, ${JSON.stringify(path.S)})`
3435
);
3536

3637
if (values.length) {
37-
appendFileSync(outputPath, `INSERT INTO tags (tag, path, revalidatedAt) VALUES ${values.join(", ")};`);
38+
appendFileSync(outputPath, `INSERT INTO ${tagsTable} (tag, path) VALUES ${values.join(", ")};`);
3839
}
3940
}
4041
}

0 commit comments

Comments
 (0)