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

feat: d1 adapter for the tag cache #320

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

Conversation

james-elicx
Copy link
Collaborator

@james-elicx james-elicx commented Feb 2, 2025

This PR is stacked on top of the changes in #319.

  • Enable tag/path revalidation and next/after e2es for app directory example.
  • Enable passing a function for the tag cache.
  • D1 adapter for the tag cache, based on the AWS DynamoDB adapter.
  • Extracts a manifest of build-time tags/paths for use as a fallback instead of pre-populating D1 as IMO there is not a simple way for us to do that.
  • Adds the env.d.ts file to the tsconfig so that TypeScript recognises env vars defined in there.

Docs PR - opennextjs/docs#66

Copy link

changeset-bot bot commented Feb 2, 2025

🦋 Changeset detected

Latest commit: 54a89cb

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

This PR includes changesets to release 1 package
Name Type
@opennextjs/cloudflare Minor

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

Copy link

pkg-pr-new bot commented Feb 2, 2025

Open in Stackblitz

pnpm add https://pkg.pr.new/@opennextjs/cloudflare@320

commit: 54a89cb

@james-elicx james-elicx linked an issue Feb 2, 2025 that may be closed by this pull request
@james-elicx james-elicx marked this pull request as ready for review February 2, 2025 22:44
@james-elicx james-elicx mentioned this pull request Feb 2, 2025
*
* The mapping creates an index of each tags pointing to its paths, and each path pointing to its tags.
*/
export function extractCacheAssetsManifest(buildOpts: BuildOptions) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We already compute all the tags and cache in the aws adapter https://github.com/opennextjs/opennextjs-aws/blob/14b81827f9078b98e32115fb5cfe706d03d64537/packages/open-next/src/build/createAssets.ts#L159-L239
BTW you are missing the fetch cache here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, I didn't realise the manifest was already generated. I'll change it to use that

Copy link
Contributor

Choose a reason for hiding this comment

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

This is resolved, right?

// inlined during build
const manifest = process.env.__OPENNEXT_CACHE_TAGS_MANIFEST;

class D1TagCache implements TagCache {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure that D1 is the right fit for this. The tag cache has been designed around DynamoDB and its strength/limitation.
I'm introducing a new mode for the tag cache that would probably better fit an SQL db opennextjs/opennextjs-aws#717

I think that what would make the most sense would be either using D1 with the new mode (and it avoids having to prepopulate anything) or using this mode with KV (But this would probably mean duplicating some data in KV)

Another thing is that if i'm not mistaken D1 is regional, but the tag cache will be reached from all over the world (And the fetch cache could be used in SSR request as well )

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure KV would really work that well due to its eventually consistent nature - you get up to 60 seconds on entries continuing to be polled, and if you use a <5 min revalidation time, it essentially becomes pointless trying to use a cache IMO.

D1 is regional yeah - means a little more latency for some regions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can rework this PR to be based on the new mode if that makes most sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

For the new mode there is benefits and drawbacks, one option could be to implement both mode so that each user could use the one that is better suited for them.
@vicb @dario-piotrowicz What do you think about that ? For reference here is the issue explaining the 2 mode and their benefits/drawback opennextjs/opennextjs-aws#706

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to have the 2 modes if it's not too much dev & maintenance vs the benefits.

We can also merge a single mode when it's ready and implement the other mode later.

@james-elicx james-elicx force-pushed the james/tag-cache branch 3 times, most recently from 401843a to 69f2d20 Compare February 5, 2025 22:11
@james-elicx james-elicx force-pushed the james/isr branch 2 times, most recently from 229b2e2 to fa3b420 Compare February 6, 2025 22:08

try {
const { success, results } = await db
.prepare(`SELECT tag FROM ${table} WHERE path = ?`)
Copy link
Contributor

Choose a reason for hiding this comment

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

can't we use bind for the table name? (SQL injection)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unfortunately not - it gives a syntax error. The table is driven by an env var, so i wouldnt think sql injection will be a problem

Copy link
Contributor

Choose a reason for hiding this comment

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

It should not... but maybe we can hardcode tags here anyway.

Copy link
Collaborator Author

@james-elicx james-elicx Feb 7, 2025

Choose a reason for hiding this comment

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

I did this with the idea that people might want to use a different table name (multiple projects using same db), so it was using the env var and defaulting to tags if not present. I can change to always hardcode to tags if you want, but I feel like it's a useful bit of configurability to have?

@@ -112,6 +113,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
"process.env.NEXT_RUNTIME": '"nodejs"',
"process.env.NODE_ENV": '"production"',
"process.env.NEXT_MINIMAL": "true",
"process.env.__OPENNEXT_CACHE_TAGS_MANIFEST": `${JSON.stringify(extractCacheAssetsManifest(buildOpts))}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

@conico974 do you think this could be added to the config adapter?

Copy link
Contributor

Choose a reason for hiding this comment

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

You mean the open-next.config.ts ? Not sure how it would work since it is bundled before next build

One thing about inlining the manifest here is that this could become pretty big, especially for website with a lot of page.
For example the __NT_/layout tag is on every page.
Inlining this will likely become an issue at scale

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking about adapter/config but the size issue would be the same.

So we should preload the DB?

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to generate a SQL file instead and run wrangler execute as part of the deployment ? This seems like a better idea
Or maybe using the rest API with the initialization function from OpenNext

Base automatically changed from james/isr to main February 7, 2025 11:30
packages/cloudflare/src/api/d1-tag-cache.ts Outdated Show resolved Hide resolved
* The mapping creates an index of each tags pointing to its paths, and each path pointing to its tags.
*/
export function extractCacheAssetsManifest(options: BuildOptions): CacheAssetsManifest {
// TODO: Expose the function for getting this data as an adapter-agnostic utility in AWS.
Copy link
Contributor

Choose a reason for hiding this comment

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

That's kind of the point of the initializationFunction https://opennext.js.org/aws/inner_workings/components/initializer
With a custom wrapper and a custom tag cache it could be turned into a function.
Not entirely sure it's worth it though

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.

Cache support
3 participants