Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove now-unused asset preservation cache v1 from Pages #7544

Merged
merged 1 commit into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/green-socks-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/pages-shared": patch
---

chore: Remove now-unused asset preservation cache (v1)
84 changes: 0 additions & 84 deletions packages/pages-shared/__tests__/asset-server/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,90 +611,6 @@ describe("asset-server handler", () => {
}
`);
});

test("preservationCacheV1 (fallback)", async () => {
vi.setSystemTime(new Date("2024-05-09")); // 1 day before fallback is disabled

const deploymentId = "deployment-" + Math.random();
const metadata = createMetadataObject({ deploymentId }) as Metadata;
const { caches } = createCacheStorage();

const preservationCacheV1 = await caches.open("assetPreservationCache");

// Write a response to the V1 cache and make sure it persists
await preservationCacheV1.put(
"https://example.com/foo",
new Response("preserved in V1 cache!", {
headers: {
"Cache-Control": "public, max-age=300",
},
})
);

const preservationRes = await preservationCacheV1.match(
"https://example.com/foo"
);

if (!preservationRes) {
throw new Error(
"Did not match preservation cache on https://example.com/foo"
);
}

expect(await preservationRes.text()).toMatchInlineSnapshot(
`"preserved in V1 cache!"`
);

// Delete the asset from the manifest and ensure it's served from V1 preservation cache
const findAssetEntryForPath = async (_path: string) => {
return null;
};
const { response, spies } = await getTestResponse({
request: new Request("https://example.com/foo"),
metadata,
findAssetEntryForPath,
caches,
fetchAsset: () =>
Promise.resolve(Object.assign(new Response("hello world!"))),
});
expect(response.status).toBe(200);
expect(await response.text()).toMatchInlineSnapshot(
`"preserved in V1 cache!"`
);
expect(Object.fromEntries(response.headers)).toMatchInlineSnapshot(`
{
"access-control-allow-origin": "*",
"cache-control": "public, max-age=300",
"cf-cache-status": "HIT",
"content-type": "text/plain;charset=UTF-8",
"referrer-policy": "strict-origin-when-cross-origin",
"x-content-type-options": "nosniff",
}
`);
// No cache or early hints writes
expect(spies.waitUntil.length).toBe(0);

// Should disable fallback starting may 10th
vi.setSystemTime(new Date("2024-05-10"));
const { response: response2, spies: spies2 } = await getTestResponse({
request: new Request("https://example.com/foo"),
metadata,
findAssetEntryForPath,
caches,
fetchAsset: () =>
Promise.resolve(Object.assign(new Response("hello world!"))),
});
expect(response2.status).toBe(404);
expect(Object.fromEntries(response2.headers)).toMatchInlineSnapshot(`
{
"access-control-allow-origin": "*",
"cache-control": "no-store",
"referrer-policy": "strict-origin-when-cross-origin",
}
`);
// No cache or early hints writes
expect(spies2.waitUntil.length).toBe(0);
});
});

describe("isPreservationCacheResponseExpiring()", async () => {
Expand Down
38 changes: 10 additions & 28 deletions packages/pages-shared/asset-server/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ type BodyEncoding = "manual" | "automatic";
// Before serving a 404, we check the cache to see if we've served this asset recently
// and if so, serve it from the cache instead of responding with a 404.
// This gives a bit of a grace period between deployments for any clients browsing the old deployment.
export const ASSET_PRESERVATION_CACHE_V1 = "assetPreservationCache";
// V2 stores the content hash instead of the asset.
// TODO: Remove V1 once we've fully migrated to V2
export const ASSET_PRESERVATION_CACHE_V2 = "assetPreservationCacheV2";
// Only the content hash is actually stored in the body.
export const ASSET_PRESERVATION_CACHE = "assetPreservationCacheV2";
const CACHE_CONTROL_PRESERVATION = "public, s-maxage=604800"; // 1 week

/** The preservation cache should be periodically
Expand Down Expand Up @@ -576,14 +574,14 @@ export async function generateHandler<
waitUntil(
(async () => {
try {
const assetPreservationCacheV2 = await caches.open(
ASSET_PRESERVATION_CACHE_V2
const assetPreservationCache = await caches.open(
ASSET_PRESERVATION_CACHE
);

// Check if the asset has changed since last written to cache
// or if the cached entry is getting too old and should have
// it's expiration reset.
const match = await assetPreservationCacheV2.match(request);
const match = await assetPreservationCache.match(request);
if (
!match ||
assetKey !== (await match.text()) ||
Expand All @@ -599,7 +597,7 @@ export async function generateHandler<
);
preservedResponse.headers.set("x-robots-tag", "noindex");

await assetPreservationCacheV2.put(
await assetPreservationCache.put(
request.url,
preservedResponse
);
Expand Down Expand Up @@ -637,30 +635,13 @@ export async function generateHandler<
async function notFound(): Promise<Response> {
if (caches) {
try {
const assetPreservationCacheV2 = await caches.open(
ASSET_PRESERVATION_CACHE_V2
const assetPreservationCache = await caches.open(
ASSET_PRESERVATION_CACHE
);
let preservedResponse = await assetPreservationCacheV2.match(
const preservedResponse = await assetPreservationCache.match(
request.url
);

// Continue serving from V1 preservation cache for some time to
// prevent 404s during the migration to V2
const cutoffDate = new Date("2024-05-17");
if (!preservedResponse && Date.now() < cutoffDate.getTime()) {
const assetPreservationCacheV1 = await caches.open(
ASSET_PRESERVATION_CACHE_V1
);
preservedResponse = await assetPreservationCacheV1.match(request.url);
if (preservedResponse) {
// V1 cache contains full response bodies so we return it directly
if (setMetrics) {
setMetrics({ preservationCacheResult: "checked-hit" });
}
return preservedResponse;
}
}

// V2 cache only contains the asset key, rather than the asset body:
if (preservedResponse) {
if (setMetrics) {
Expand All @@ -674,6 +655,7 @@ export async function generateHandler<
}
if (assetKey) {
const asset = await fetchAsset(assetKey);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random new line?

if (asset) {
// We know the asset hasn't changed, so use the cached headers.
return new Response(asset.body, preservedResponse);
Expand Down
Loading