mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 01:25:16 +00:00
Improve purging of cache and return keys (#111)
* Use input url instead of request url * Improve cache purging * Lint * Fix TS * Add method to delete from cloudflare * Clear on all backends * Fix * Add global cloudflare tag
This commit is contained in:
@@ -25,9 +25,10 @@ export async function POST(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
await revalidateTags(json.tags, !!json.purge);
|
||||
const result = await revalidateTags(json.tags, !!json.purge);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
keys: result.keys,
|
||||
});
|
||||
}
|
||||
|
||||
Vendored
+16
-1
@@ -33,8 +33,22 @@ export const cloudflareCache: CacheBackend = {
|
||||
await cache.put(cacheKey, serializeEntry(entry));
|
||||
}
|
||||
},
|
||||
async del(keys) {
|
||||
const cache = getCache();
|
||||
if (cache) {
|
||||
await Promise.all(
|
||||
keys.map(async (key) => {
|
||||
const cacheKey = await serializeKey(key);
|
||||
await cache.delete(cacheKey);
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
async revalidateTags(tags) {
|
||||
return [];
|
||||
return {
|
||||
keys: [],
|
||||
metas: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,6 +78,7 @@ function serializeEntry(entry: CacheEntry): WorkerResponse {
|
||||
const headers = new Headers();
|
||||
headers.set('Content-Type', 'application/json');
|
||||
headers.set('Cache-Control', `public, max-age=${(entry.meta.expiresAt - Date.now()) / 1000}`);
|
||||
headers.set('Cache-Tag', ['gitbook-open', ...entry.meta.tags].join(','));
|
||||
|
||||
// @ts-ignore
|
||||
return new Response(JSON.stringify(entry), {
|
||||
|
||||
Vendored
+8
-1
@@ -23,6 +23,10 @@ export const memoryCache: CacheBackend = {
|
||||
const memoryCache = getMemoryCache();
|
||||
memoryCache.set(key, entry);
|
||||
},
|
||||
async del(keys) {
|
||||
const memoryCache = getMemoryCache();
|
||||
keys.forEach((key) => memoryCache.delete(key));
|
||||
},
|
||||
async revalidateTags(tags) {
|
||||
const memoryCache = getMemoryCache();
|
||||
|
||||
@@ -32,7 +36,10 @@ export const memoryCache: CacheBackend = {
|
||||
}
|
||||
});
|
||||
|
||||
return [];
|
||||
return {
|
||||
keys: [],
|
||||
metas: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Vendored
+26
-10
@@ -63,10 +63,24 @@ export const redisCache: CacheBackend = {
|
||||
await multi.exec();
|
||||
},
|
||||
|
||||
async revalidateTags(tags) {
|
||||
async del(keys) {
|
||||
const redis = getRedis();
|
||||
if (!redis) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
|
||||
const multi = redis.multi();
|
||||
keys.forEach((key) => {
|
||||
multi.del(getRedisKey(key));
|
||||
});
|
||||
|
||||
await multi.exec();
|
||||
},
|
||||
|
||||
async revalidateTags(tags, purge) {
|
||||
const redis = getRedis();
|
||||
if (!redis) {
|
||||
return { keys: [], metas: [] };
|
||||
}
|
||||
|
||||
const keys = new Set(
|
||||
@@ -77,13 +91,15 @@ export const redisCache: CacheBackend = {
|
||||
let metas: Array<CacheEntryMeta | null> = [];
|
||||
|
||||
if (keys.size > 0) {
|
||||
metas = (
|
||||
await redis.json.mget(
|
||||
// Hard limit to avoid fetching a massive list of data
|
||||
Array.from(keys).slice(0, 1000),
|
||||
'$.meta',
|
||||
)
|
||||
).flat() as Array<CacheEntryMeta | null>;
|
||||
if (!purge) {
|
||||
metas = (
|
||||
await redis.json.mget(
|
||||
// Hard limit to avoid fetching a massive list of data
|
||||
Array.from(keys).slice(0, 1000),
|
||||
'$.meta',
|
||||
)
|
||||
).flat() as Array<CacheEntryMeta | null>;
|
||||
}
|
||||
|
||||
// Finally, delete all keys
|
||||
keys.forEach((key) => {
|
||||
@@ -96,7 +112,7 @@ export const redisCache: CacheBackend = {
|
||||
});
|
||||
|
||||
await pipeline.exec();
|
||||
return metas.filter(filterOutNullable);
|
||||
return { keys: Array.from(keys), metas: metas.filter(filterOutNullable) };
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Vendored
+36
-4
@@ -8,17 +8,32 @@ import { CacheEntryMeta } from './types';
|
||||
* Revalidate all values associated with tags.
|
||||
* It clears the values from the caches, but also start a background task to revalidate them.
|
||||
*/
|
||||
export async function revalidateTags(tags: string[], purge: boolean): Promise<void> {
|
||||
export async function revalidateTags(
|
||||
tags: string[],
|
||||
purge: boolean,
|
||||
): Promise<{
|
||||
keys: string[];
|
||||
}> {
|
||||
if (tags.length === 0) {
|
||||
return;
|
||||
return { keys: [] };
|
||||
}
|
||||
|
||||
const processed = new Set<string>();
|
||||
|
||||
const keysByBackend = new Map<number, string[]>();
|
||||
const keys = new Set<string>();
|
||||
const metas: CacheEntryMeta[] = [];
|
||||
|
||||
await Promise.all(
|
||||
cacheBackends.map(async (backend) => {
|
||||
const addedMetas = await backend.revalidateTags(tags);
|
||||
cacheBackends.map(async (backend, backendIndex) => {
|
||||
const { keys: addedKeys, metas: addedMetas } = await backend.revalidateTags(
|
||||
tags,
|
||||
purge,
|
||||
);
|
||||
addedKeys.forEach((key) => {
|
||||
keys.add(key);
|
||||
keysByBackend.set(backendIndex, [...(keysByBackend.get(backendIndex) ?? []), key]);
|
||||
});
|
||||
addedMetas.forEach((meta) => {
|
||||
const key = getCacheKey(meta.cache, meta.args);
|
||||
if (!processed.has(key)) {
|
||||
@@ -29,6 +44,19 @@ export async function revalidateTags(tags: string[], purge: boolean): Promise<vo
|
||||
}),
|
||||
);
|
||||
|
||||
// Clear the keys on the backends that didn't return them
|
||||
await Promise.all(
|
||||
cacheBackends.map(async (backend, backendIndex) => {
|
||||
const unclearedKeys = Array.from(keys).filter(
|
||||
(key) => !keysByBackend.get(backendIndex)?.includes(key),
|
||||
);
|
||||
|
||||
if (unclearedKeys.length > 0) {
|
||||
await backend.del(unclearedKeys);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Refresh the values in the cache
|
||||
if (metas && !purge) {
|
||||
await pMap(
|
||||
@@ -46,4 +74,8 @@ export async function revalidateTags(tags: string[], purge: boolean): Promise<vo
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
keys: Array.from(keys.keys()),
|
||||
};
|
||||
}
|
||||
|
||||
Vendored
+9
-1
@@ -48,9 +48,17 @@ export interface CacheBackend {
|
||||
*/
|
||||
set(key: string, entry: CacheEntry): Promise<void>;
|
||||
|
||||
/**
|
||||
* Delete a value from the cache.
|
||||
*/
|
||||
del(keys: string[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Revalidate all keys associated with tags.
|
||||
* It should return the meta of all entries that were revalidated.
|
||||
*/
|
||||
revalidateTags(tags: string[]): Promise<CacheEntryMeta[]>;
|
||||
revalidateTags(
|
||||
tags: string[],
|
||||
purge: boolean,
|
||||
): Promise<{ keys: string[]; metas: CacheEntryMeta[] }>;
|
||||
}
|
||||
|
||||
+3
-2
@@ -65,10 +65,11 @@ export type LookupResult = PublishedContentWithCache & {
|
||||
export async function middleware(request: NextRequest) {
|
||||
const { url, mode } = getInputURL(request);
|
||||
|
||||
Sentry.setTag('url', request.url);
|
||||
Sentry.setTag('url', url.toString());
|
||||
Sentry.setContext('request', {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
url: url.toString(),
|
||||
rawRequestURL: request.url,
|
||||
userAgent: userAgent(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user