Skip to content

Conversation

@r1tsuu
Copy link
Member

@r1tsuu r1tsuu commented Aug 16, 2024

Description

Adds an implementation of caching to Payload.

Usage

Simply providing cache: true into args will cache the current operation:

payload.find({ cache: true, collection: 'posts' })
payload.findByID({ id: 1, cache: true, collection: 'posts' })
payload.count({ cache: true, collection: 'posts' })
payload.findGlobal({ cache: true, slug: 'header' })

With the REST API - query parameter ?cache=true.

Configuration / cache adapters

By default, if cache is not provided - inMemoryDatabaseCache will be used.
Any implemented database cache adapter accepts logging and ttl options.
Example of logging: true - useful for debugging
image
image

ttl - Time To Live for the cached values, by default it's 1 hour. Providing false will disable TTL.

import { inMemoryDatabaseCache } from 'payload'
import { nextDatabaseCache } from '@payloadcms/next/cache'

export default buildConfig({
  // ...config here
  cache: inMemoryDatabaseCache({ logging: true, ttl: 100000  }),
})

// 
export default buildConfig({
  // ...config here
  cache: nextDatabaseCache({ logging: true, ttl: 100000  }),
})

inMemoryDatabaseCache probably won't work well on a server-less envs like Vercel!

Fields Cache Indexes

By default, only id field is used for revalidation of the findOne calls, you can provide cacheIndex: true to a needed field (for example a common one - slug).

 {
          name: "slug",
          type: "text",
          cacheIndex:true
}

The implementation as well handles the cache invalidation for us, no need to write revalidation hooks more!
That means if you're using nextDatabaseCache - ISR should happen automatically as well. This also includes depth parameter handling.

Underlying API calls payload.db directly and stores its result into the implemented storage.
You can use it like this:

// important to pass 'req' with 'payload' inside
payload.cache.count({collection: "posts",req, where:{}})

ttl can as well be resolved dynamically via TTLResolveFunction.

export type TTLResolveFunction = (
  args: { draft?: boolean } & (
    | {
        operation: 'count'
        operationArgs: CountArgs
      }
    | {
        operation: 'find'
        operationArgs: FindArgs
      }
    | {
        operation: 'findGlobal'
        operationArgs: FindGlobalArgs
      }
    | {
        operation: 'findOne'
        operationArgs: FindOneArgs
      }
  ),
) => false | number

No tests / docs and probably some things don't work normally yet

  • I have read and understand the CONTRIBUTING.md document in this repository.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Checklist:

  • I have added tests that prove my fix is effective or that my feature works
  • Existing test suite passes locally with my changes
  • I have made corresponding changes to the documentation
@r1tsuu r1tsuu requested review from denolfe and jmikrut as code owners August 16, 2024 01:28
@DanRibbens
Copy link
Contributor

I like the ideas here. I have some concerns with the implementation as I see it described, though I haven't reviewed the code yet.

I feel like the API for this is not at all complete and should be designed better.
A) It should be usable through http cache control headers
B) The programmatic API is completely missing, how can I set/clear the cache?

@r1tsuu
Copy link
Member Author

r1tsuu commented Aug 16, 2024

I like the ideas here. I have some concerns with the implementation as I see it described, though I haven't reviewed the code yet.

I feel like the API for this is not at all complete and should be designed better. A) It should be usable through http cache control headers B) The programmatic API is completely missing, how can I set/clear the cache?

A) Agree
B) Next.js doesn't provide full functionallity here. Only "full cache clearing" is doable via tags, they don't provide us abillity to set a cache value by key or get it without calling a wrapped with unstable_cache function https://nextjs.org/docs/app/api-reference/functions/unstable_cache. That's why my interface for storage contains only 2 methods:
cacheFn
revalidateTags
Which you can access as well via payload.cache, I didn't mention this. So yeah, you can with any cache adapter wrap your own function with it.

With InMemory it should already work - you can access its Map storage for cache, but it's private for now. Anyway, because of the difference between Next and InMemory storages - Typescript will give an error even if it wouldn't be private.

private cache = new Map<string, { expiresAt: null | number; tags: string[]; value: unknown }>()

So that's the main block here. But people definetely want to use Next.js caching in a first place for frontend revalidation. In fact, that's my main motivation for the PR, because I see a lot of people are struggling with caching / invalidation.
I doubt that we can access the Next.js underlying cache API, as it also seems like behaves differently depending on how we host it - Vercel / self / etc.

Overall, do you think it's right from me to cache payload.db calls instead of the local api calls (payload.find etc)?
I feel like payload.db calls are more "pure" in terms of dependencies like hooks, auth and so on, that's why it's much more easier to cache them / invalidate

@r1tsuu r1tsuu marked this pull request as draft August 16, 2024 16:42
@github-actions
Copy link
Contributor

This PR is stale due to lack of activity.

To keep the PR open, please indicate that it is still relevant in a comment below.

@github-actions github-actions bot added the stale label Dec 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants