epicweb-dev/cachified

[RFC - New Adapter] Create new Cloudflare KV cache adapter

Closed this issue ยท 6 comments

Proposal

cachified already has a solid foundation as a generic key-value cache utility. The existing adapters will handle most scenarios where a distributed store is needed (with Redis). However for those building tools in the Cloudflare Ecosystem, Cloudflare KV is a fantastic distributed KV store, one with global low latency reads and very reasonable cost.

The goal here is to export a new adapter called cloudflareKvCacheAdapter which would be exported from the cachified package, allowing users to use Cloudflare KV as a datastore.

Usage Example

The API exposed by the KV adapter should closely mirror the existing setup for the existing adapters, e.g the redis cache.
Here is how I envision the KV cache would be setup in a sample worker script

// This is a sample Cloudflare worker script

import { cachified, Cache, cloudflareKvCacheAdapter } from 'cachified';

export interface Env {
  KV: KVNamespace;
  CACHIFIED_KV_CACHE: Cache;
}

export async function getUserById(userId: number, env: Env): Promise<Record<string, unknown>> {
  return cachified({
    key: `user-${userId}`,
    cache: env.CACHIFIED_KV_CACHE,
    async getFreshValue() {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
      return response.json();
    },
    ttl: 300_000,
  });
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // It is a common pattern to pass around the env object to most functions when writing workers code
    // So it's convenient to inject the cache adapter into the env object
    env.CACHIFIED_KV_CACHE = cloudflareKvCacheAdapter({
      kv: env.KV,
      ctx: ctx,
      keyPrefix: 'mycache', // optional
    });
    const userId = Math.floor(Math.random() * 10) + 1;
    const user = await getUserById(userId, env);
    return new Response(`User data is ${JSON.stringify(user)}`);
  },
};

Making a PR

I wanted to ask the maintainers / contributors to this project, would you be willing to accept a PR to create the adapter described above in this project.
Happy to connect / have further discussions on anything that is of concern.

Really looking forward to hearing more. Genuinely love the package you've made, and I'm keen to make it accessible with CF KV.

Awesome! Thanks for the idea and for pro-actively opening the PR!

๐ŸŽ‰ This issue has been resolved in version 5.0.0 ๐ŸŽ‰

The release is available on:

Your semantic-release bot ๐Ÿ“ฆ๐Ÿš€

Hi @Xiphe and @AdiRishi -- I'm just trying out cachified with your KV store adapter running on CF Workers and I could be missing something, but I can't see how refreshing stale values in the background or migrating values would work without cachfieid accepting waitUntil? ๐Ÿค”

@richardscarrott good question. The updates happen "in the background" due to the setTimeout wrapped code. This means the JS engine can choose to execute the code whenever the current thread yields control back (probably the next time you call an async function).

This is absolutely not as good as wrapping the call in ctx.waitUntil, since that provides a better way to do background non critical work in Cloudflare workers.

I didn't add waitUntil support since it messes with the reporters and some of the control flow that Cachified has. But it doesn't "break" and absolutely would work, with some odd behaviour from Cachified itself.
I'm happy to add experimental support into the library, or if you're interested in making a PR you could do so too ๐Ÿ˜€

Hope that explanation makes sense. Feel free to ask more, or even open an issue on KV adapter repo if you want to discuss other issues/concerns.

@AdiRishi cache writes would actually have a high chance of failing in CF Workers as it currently stands because the process is likely to be shutdown before the cache has been revalidated / migrated.

The fix really needs to be part of cachified itself, because it performs more than just cache reads / writes in the background (e.g. waits for staleRefreshTimeout).

I've sent a PR to cachified -- shouldn't need any changes to the adapter ๐Ÿคž

It makes sense that this change needs to be in cachified itself. And yeah, I didn't think of the fact that the process could shut down before the promises have finished resolving, great catch!
The PR looks good :)