Skip to content

Commit c799375

Browse files
conico974Nicolas Dorseuil
andauthored
Fix regional caching of tag cache (#3504)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
1 parent d024658 commit c799375

File tree

7 files changed

+123
-36
lines changed

7 files changed

+123
-36
lines changed

bun.lock

Lines changed: 63 additions & 9 deletions
Large diffs are not rendered by default.

packages/gitbook/openNext/customWorkers/default.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/ini
22

33
import { DurableObject } from 'cloudflare:workers';
44

5+
//Only needed to run locally, in prod we'll use the one from do.js
6+
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
7+
58
// Only needed to run locally, in prod we'll use the one from do.js
69
export class R2WriteBuffer extends DurableObject {
710
writePromise;

packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@
3636
{
3737
"name": "WRITE_BUFFER",
3838
"class_name": "R2WriteBuffer"
39+
},
40+
{
41+
"name": "NEXT_TAG_CACHE_DO_SHARDED",
42+
"class_name": "DOShardedTagCache"
3943
}
4044
]
4145
},
4246
"migrations": [
4347
{
4448
"tag": "v1",
45-
"new_sqlite_classes": ["R2WriteBuffer"]
49+
"new_sqlite_classes": ["R2WriteBuffer", "DOShardedTagCache"]
4650
}
4751
]
4852
},

packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"vars": {
2323
"STAGE": "dev",
2424
"NEXT_PRIVATE_DEBUG_CACHE": "true",
25+
// When deployed locally, we don't have access to the tag cache here,
26+
// we should just bypass the cache to go to the server directly
27+
"SHOULD_BYPASS_CACHE": "true",
2528
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
2629
},
2730
"r2_buckets": [

packages/gitbook/openNext/incrementalCache.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ class GitbookIncrementalCache implements IncrementalCache {
3636
const r2 = getCloudflareContext().env[BINDING_NAME];
3737
const localCache = await this.getCacheInstance();
3838
if (!r2) throw new Error('No R2 bucket');
39+
if (process.env.SHOULD_BYPASS_CACHE === 'true') {
40+
// We are in a local middleware environment, we should bypass the cache
41+
// and go directly to the server.
42+
return null;
43+
}
3944
try {
4045
// Check local cache first if available
4146
const localCacheEntry = await localCache.match(this.getCacheUrlKey(cacheKey));
@@ -134,19 +139,27 @@ class GitbookIncrementalCache implements IncrementalCache {
134139
}
135140

136141
async writeToR2(key: string, value: string): Promise<void> {
137-
const env = getCloudflareContext().env as {
138-
WRITE_BUFFER: DurableObjectNamespace<
139-
Rpc.DurableObjectBranded & {
140-
write: (key: string, value: string) => Promise<void>;
141-
}
142-
>;
143-
};
144-
const id = env.WRITE_BUFFER.idFromName(key);
145-
146-
// A stub is a client used to invoke methods on the Durable Object
147-
const stub = env.WRITE_BUFFER.get(id);
148-
149-
await stub.write(key, value);
142+
try {
143+
const env = getCloudflareContext().env as {
144+
WRITE_BUFFER: DurableObjectNamespace<
145+
Rpc.DurableObjectBranded & {
146+
write: (key: string, value: string) => Promise<void>;
147+
}
148+
>;
149+
};
150+
const id = env.WRITE_BUFFER.idFromName(key);
151+
152+
// A stub is a client used to invoke methods on the Durable Object
153+
const stub = env.WRITE_BUFFER.get(id);
154+
155+
await stub.write(key, value);
156+
} catch {
157+
// We fallback to writing directly to R2
158+
// it can fail locally because the limit is 1Mb per args
159+
// It is 32Mb in production, so we should be fine
160+
const r2 = getCloudflareContext().env[BINDING_NAME];
161+
r2?.put(key, value);
162+
}
150163
}
151164

152165
async getCacheInstance(): Promise<Cache> {
Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { getLogger } from '@/lib/logger';
1+
import { createLogger, getLogger } from '@/lib/logger';
22
import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js';
33
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
44
import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter';
55

66
const originalTagCache = doShardedTagCache({
77
baseShardSize: 12,
88
regionalCache: true,
9-
// We can probably increase this value even further
10-
regionalCacheTtlSec: 60,
9+
regionalCacheTtlSec: 60 * 5 /* 5 minutes */,
10+
// Because we invalidate the Cache API on update, we can safely set this to true
11+
regionalCacheDangerouslyPersistMissingTags: true,
1112
shardReplication: {
1213
numberOfSoftReplicas: 2,
1314
numberOfHardReplicas: 1,
@@ -20,6 +21,7 @@ const originalTagCache = doShardedTagCache({
2021
export default {
2122
name: 'GitbookTagCache',
2223
mode: 'nextMode',
24+
// We don't really use this one, as of now it is only used for soft tags
2325
getLastRevalidated: async (tags: string[]) => {
2426
const tagsToCheck = tags.filter(softTagFilter);
2527
if (tagsToCheck.length === 0) {
@@ -29,12 +31,20 @@ export default {
2931
return await originalTagCache.getLastRevalidated(tagsToCheck);
3032
},
3133
hasBeenRevalidated: async (tags: string[], lastModified?: number) => {
32-
const tagsToCheck = tags.filter(softTagFilter);
33-
if (tagsToCheck.length === 0) {
34-
return false; // If no tags to check, return false
35-
}
34+
try {
35+
const tagsToCheck = tags.filter(softTagFilter);
36+
if (tagsToCheck.length === 0) {
37+
return false; // If no tags to check, return false
38+
}
3639

37-
return await originalTagCache.hasBeenRevalidated(tagsToCheck, lastModified);
40+
return await originalTagCache.hasBeenRevalidated(tagsToCheck, lastModified);
41+
} catch (e) {
42+
createLogger('gitbookTagCache', {}).error(
43+
`hasBeenRevalidated - Error checking tags ${tags.join(', ')}`,
44+
e
45+
);
46+
return false; // In case of error, return false
47+
}
3848
},
3949
writeTags: async (tags: string[]) => {
4050
const tagsToWrite = tags.filter(softTagFilter);
@@ -43,7 +53,7 @@ export default {
4353
logger.warn('writeTags - No valid tags to write');
4454
return; // If no tags to write, exit early
4555
}
46-
// Write only the filtered tags
56+
4757
await originalTagCache.writeTags(tagsToWrite);
4858
},
4959
} satisfies NextModeTagCache;

packages/gitbook/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@gitbook/react-contentkit": "workspace:*",
1414
"@gitbook/react-math": "workspace:*",
1515
"@gitbook/react-openapi": "workspace:*",
16-
"@opennextjs/cloudflare": "^1.4.0",
16+
"@opennextjs/cloudflare": "^1.6.2",
1717
"@radix-ui/react-checkbox": "^1.0.4",
1818
"@radix-ui/react-dropdown-menu": "^2.1.12",
1919
"@radix-ui/react-navigation-menu": "^1.2.3",
@@ -22,6 +22,7 @@
2222
"@sindresorhus/fnv1a": "^3.1.0",
2323
"@tailwindcss/container-queries": "^0.1.1",
2424
"@tailwindcss/typography": "^0.5.16",
25+
"@tusbar/cache-control": "^1.0.2",
2526
"ai": "^4.2.2",
2627
"assert-never": "^1.2.1",
2728
"bun-types": "^1.1.20",
@@ -49,7 +50,7 @@
4950
"object-identity": "^0.1.2",
5051
"openapi-types": "^12.1.3",
5152
"p-map": "^7.0.3",
52-
"@tusbar/cache-control": "^1.0.2",
53+
"quick-lru": "^7.0.1",
5354
"react-hotkeys-hook": "^4.4.1",
5455
"rehype-sanitize": "^6.0.0",
5556
"rehype-stringify": "^10.0.1",
@@ -67,8 +68,7 @@
6768
"url-join": "^5.0.0",
6869
"usehooks-ts": "^3.1.0",
6970
"warn-once": "^0.1.1",
70-
"zustand": "^5.0.3",
71-
"quick-lru": "^7.0.1"
71+
"zustand": "^5.0.3"
7272
},
7373
"devDependencies": {
7474
"@argos-ci/playwright": "^5.0.5",

0 commit comments

Comments
 (0)