Skip to content

Implement persistent data cache option and update cache key logic #920

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

conico974
Copy link
Contributor

@conico974 conico974 commented Jul 2, 2025

Introduce a new option to maintain the data cache across deployments and adjust cache key generation to reflect this change. Fix issues related to tag caching and ensure proper handling of cache keys in various scenarios.

BREAKING CHANGE: Incremental cache keys are now an object of type CacheKey instead of a string. The new type includes properties like baseKey, buildId, and cacheType. Build_id is automatically provided according to the cache type and the dangerous.persistentDataCache option. Up to the Incremental Cache implementation to use it as they see fit.

Copy link

changeset-bot bot commented Jul 2, 2025

🦋 Changeset detected

Latest commit: 4710f0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@opennextjs/aws Minor
app-pages-router Patch
app-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vicb
Copy link
Contributor

vicb commented Jul 2, 2025

Is the fix related to the other changes? (Maybe clarify in the PR description)

There are conflicts.

I'll take a look later today

@conico974
Copy link
Contributor Author

My bad i rebased on the wrong branch... I'll fix it

@conico974 conico974 force-pushed the feat/persistent-data-cache branch from 74ae605 to a8b69ff Compare July 2, 2025 09:58
Copy link

pkg-pr-new bot commented Jul 2, 2025

Open in StackBlitz

pnpm add https://pkg.pr.new/@opennextjs/aws@920

commit: 4710f0a

Copy link
Contributor

@vicb vicb left a comment

Choose a reason for hiding this comment

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

Nice!

I added a few minor comments but changes look good.

Probably we should doc that overrides should not use the BUILD_ID to enable the feature.

I question:

For composable cache, we use

globalThis.tagCache.hasBeenRevalidated(
          result.value.tags,
          result.lastModified,

vs hasBeenRevalidated from utils - any particular reason. Would be nice to add comments.

try {
const key = createCacheKey(baseKey, true);
const cachedEntry = await globalThis.incrementalCache.get(key, "fetch");

if (cachedEntry?.value === undefined) return null;

const _tags = [...(tags ?? []), ...(softTags ?? [])];
const _lastModified = cachedEntry.lastModified ?? Date.now();
const _hasBeenRevalidated = await hasBeenRevalidated(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should add a comment explaining why the base key is used (inline + on the method)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Re-opening.
Could we expand on why this is not using the obj key?

@conico974
Copy link
Contributor Author

I'll update the PR with the changes later today

@vicb
Copy link
Contributor

vicb commented Jul 2, 2025

Thought: In the Couldflare adapter the cache key is prefix/build-id/hash(key)

  • build-id is used to partition the cache
  • we need to hash(...) to fit within the allowed length and chars

That will become harder to do with this PR.

There are multiple ways to solve this but while thinking about this I was wondering if the change could not be implemented by only updating the overrides? i.e. we could pass a key that is independent of the BUILD_ID and then up to the override to add the build Id or not.

@conico974
Copy link
Contributor Author

There are multiple ways to solve this but while thinking about this I was wondering if the change could not be implemented by only updating the overrides? i.e. we could pass a key that is independent of the BUILD_ID and then up to the override to add the build Id or not.

That's exactly what was done before (except for the composable cache).
I feel like this is something that we need to handle here, not the override itself. It feels weird to have to do the same thing on every single override.

One option would be to have a special type as a key like

interface CacheKey {
  baseKey: string,
  buildId?:string
}

You can then decide what you do with it.

@vicb
Copy link
Contributor

vicb commented Jul 2, 2025

One option would be to have a special type as a key like

+1

It could also help alleviate the confusion between key: string and baseKey: string

@conico974 conico974 requested a review from vicb July 4, 2025 10:13

Add an option to keep the data cache persistent between deployments.

BREAKING CHANGE: Incremental cache keys are now an object of type `CacheKey` instead of a string. The new type includes properties like `baseKey`, `buildId`, and `cacheType`. Build_id is automatically provided according to the cache type and the `dangerous.persistentDataCache` option. Up to the Incremental Cache implementation to use it as they see fit.
Copy link
Contributor

Choose a reason for hiding this comment

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

Explicitly say that the overrides should be updated?

(Adding this text to the PR description)

await globalThis.incrementalCache.set(
key,
{
await globalThis.incrementalCache.set(key, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Look like this is a formatting only diff?

* @param key The composable cache key
* @returns The composable cache key.
*/
function getComposableCacheKey(key: string): CacheKey<"composable"> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be merged into createCacheKey?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah probably

try {
const cacheKey = getComposableCacheKey(key);
Copy link
Contributor

@vicb vicb Jul 4, 2025

Choose a reason for hiding this comment

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

Would it make sense to move that l45 and use key on l42 and 43?

edit:
Oh actually I think that's what we want as they might differ.
Should we add a comment warning that key should not be used?

edit2:
But I guess that I have the same question as the tag cache: why wouldn't we use the build?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here it doesn't really matter, it's in memory anyway.
For the tag cache, technically we don't need the build_id as it is time based.
There is one case actually when it is useful to have the build_id, it's for more complex deployment (like blue/green).
I think what make sense is just to pass the CacheKey to the tag cache as well and let the implementation decides.
For the default one, I think we should just follow the persistentDataCache option
WDYT ?

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM


const getCacheKey = (key: string) => {
return path.join(basePath, `${key}.cache`);
};

const cache: IncrementalCache = {
name: "fs-dev",
get: async (key: string) => {
const fileData = await fs.readFile(getCacheKey(key), "utf-8");
get: async ({ baseKey }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
get: async ({ baseKey }) => {
get: async (cacheKey: CacheKey) => {
// This cache is always shared across build (the build id is not used)
const { baseKey } = cacheKey;

const { NEXT_BUILD_ID } = process.env;
return `__meta_${NEXT_BUILD_ID}_${key}`;
const buildDynamoKey = (key: CacheKey<CacheEntryType>) => {
return `__meta_${key.buildId ? `${key.buildId}_` : ""}${key.baseKey}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could this be simplified?
not the exact same output as the current code but it should not matter?

Suggested change
return `__meta_${key.buildId ? `${key.buildId}_` : ""}${key.baseKey}`;
return `__meta_${key.buildId ?? ""}_${key.baseKey}`;

cacheType: type,
buildId: undefined,
baseKey: key,
} as CacheKey<CacheType>;
Copy link
Contributor

Choose a reason for hiding this comment

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

is as CacheKey<CacheType> needed here?

Comment on lines +90 to +91
// We always prepend the build ID to the cache key for ISR/SSG cache entry
// For data cache, we only prepend the build ID if the persistentDataCache is not enabled
Copy link
Contributor

Choose a reason for hiding this comment

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

"prepend" do not sound right any more.

would be great to update the comments and move to their corresponding case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants