#How to Cache Redis GETs Within a Single Vercel Fluid Instance (Next.js 15 + Upstash)

1 messages · Page 1 of 1 (latest)

tough lotus
#

Hi! I’m developing a Next.js 15 app, using Upstash Redis for caching and deploying on Vercel with Fluid Compute.

My goal is to cache and dedublicate all Redis GET responses within a single Fluid lambda container, so that any serverless function running in that container can check a shared variable and avoid calling Redis again if the data is already fetched.

I want:

A global in-memory cache inside the Fluid container

Deduplication across concurrent functions in the same container

To only call redis.get() if we haven't already fetched the key

Deduplication and every possible optimization is important to me.
If you’ve done anything similar or have suggestions on best practices, I’d really appreciate the help!

potent cypressBOT
#

🔎 This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord

🕵️ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize

✅ You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)

tough lotus
#
import { type Redis } from "@upstash/redis";

const memoryDedupe = new Map<string, Promise<unknown>>();
const redisDedupe = new Map<string, Promise<unknown>>(); // Dedupe Redis GETs
const redisCache = new Map<string, unknown>(); // Request-scoped Redis cache

#
export function createCachedFetcher(redis: Redis) {
  return async function cachedFetch<T>(
    key: string,
    fetcher: () => Promise<T>,
    ttl = 900, // TTL for cached data in seconds
    lockTtl = 5 // TTL for Redis lock in seconds
  ): Promise<T> {
    if (redisCache.has(key)) {
      return redisCache.get(key) as T;
    }

    // Layer 2: Deduplicated Redis GET
    if (!redisDedupe.has(key)) {
      redisDedupe.set(key, redis.get<T>(key));
    }
   
    const cached = (await redisDedupe.get(key)) as T;

    if (cached) {
      redisCache.set(key, cached);
      return cached;
    }

    // Layer 3: In-flight request deduplication
    if (memoryDedupe.has(key)) {
      return memoryDedupe.get(key) as Promise<T>;
    }

    // Layer 4: Distributed lock with Redis
    const lockKey = `lock:${key}`;
    const hasLock = await redis.set(lockKey, "1", { nx: true, ex: lockTtl });

    if (hasLock === "OK") {
      const promise = fetcher()
        .then(async (data) => {
          await redis.set(key, data, { ex: ttl });
          redisCache.set(key, data); // Populate request cache
          return data;
        })
        .finally(() => {
          memoryDedupe.delete(key);
          redisDedupe.delete(key); // 🔥 clear it after it's used
          redis.del(lockKey);
        });

      memoryDedupe.set(key, promise);
      return promise;
    } else {
      await new Promise((resolve) => setTimeout(resolve, 500));

      // Final check after backoff
      const retry = await redis.get<T>(key);
      if (retry) {
        redisCache.set(key, retry);
        return retry;
      }

      const promise = fetcher() // Fallback safe fetch
        .then(async (data) => {
          await redis.set(key, data, { ex: ttl });
          return data;
        });

      memoryDedupe.set(key, promise);
      return promise;
    }
  };
}

sand slate
#

why don't you directly cache using nextjs?