Skip to content

Redis: Socket already opened #237

Closed
Closed

Description

@AlexisWalravens

Brief Description of the Bug
Hi, we use NextJS deployed on Docker and with Kubernetes and we use this package with the redis-string example as it is recommended in the NextJS docs to use another cache configuration when using kubernetes.

When a second kubernetes pod is spawned the memory of the first pod is copied, and as such the redis client connection too.
And as soon as the second pod starts to talk to redis I get a Socket already opened and the pod crashes because it tried to use the same connection socket as the first.

I don't really know if it's the responsibility of this package to solve this but maybe you can help me.

Basically we would need a way to create a new connection if a new pod spawn, because the connection — if I understand correctly — is only initiated once when the NextJS process starts.

Thanks.

Severity
Major

Frequency of Occurrence
Always (when a second pod spawn)

Steps to Reproduce
Complicated...

Expected vs. Actual Behavior
A way to create another connection when a pod starts

Environment:

  • OS: Linux
  • Node.js version: 18.9.0
  • @neshca/cache-handler version: 0.6.1
  • next version: 14.0.4

Dependencies and Versions
redis: 4.6.12

Activity

  1. ezeparziale commented on Dec 22, 2023

    @ezeparziale

    Check your Redis config if you have any limit on connections.

    I have Kubernetes set up with two pods running Next.js and one pod running Redis, and I don't encounter any problems with connections.

  2. better-salmon commented on Dec 22, 2023

    @better-salmon
    Contributor

    Hey, @AlexisWalravens! I can’t but agree that the steps to reproduce is complicated. But maybe this dead simple solution might help.

    Before all, update the @neshca/cache-handler to the latest version, 0.6.2, at the time of writing.

    Then, add NEXT_PRIVATE_DEBUG_CACHE=1 to your .env file during the debugging.

    After these steps, add the following check to your onCreation hook:

    // Check if the socket is already opened
    if (!client.isOpen) {
        await client.connect();
    }

    Write back if it helps!

  3. better-salmon commented on Dec 22, 2023

    @better-salmon
    Contributor

    I have found that if your Redis server is unreachable during the building process, Redis will throw an Socket already opened error. Please also check if this is the root of the issue.

  4. AlexisWalravens commented on Dec 24, 2023

    @AlexisWalravens
    Author

    Hey !

    @ezeparziale I don't think it's the connection limit since it's already used by another service I just added NextJS to it. But I'll make sure thanks!

    @better-salmon I will also try this solution, but I will not able to test it until early January as it a work project and I'm on holiday until then.

    Will let you know!

  5. rutwik-fale-amla commented on Dec 26, 2023

    @rutwik-fale-amla

    Hey, @AlexisWalravens! I can’t but agree that the steps to reproduce is complicated. But maybe this dead simple solution might help.

    Before all, update the @neshca/cache-handler to the latest version, 0.6.2, at the time of writing.

    Then, add NEXT_PRIVATE_DEBUG_CACHE=1 to your .env file during the debugging.

    After these steps, add the following check to your onCreation hook:

    // Check if the socket is already opened
    if (!client.isOpen) {
        await client.connect();
    }

    Write back if it helps!

    I was also encountering the same issue. By using @better-salmon workaround it get resolved.
    Thank you!

  6. better-salmon commented on Dec 26, 2023

    @better-salmon
    Contributor

    @rutwik-fale-amla, it is possible that your app is falling back to the local-lru Handler. If this is the case, let's continue communicating.

    Please check if your app works with the redis-stack Handler by adding NEXT_PRIVATE_DEBUG_CACHE=1 to your .env file. You should see the following log in your stdout: get from "redis-stack" ....

  7. rutwik-fale-amla commented on Dec 26, 2023

    @rutwik-fale-amla

    @better-salmon, I have checked by adding NEXT_PRIVATE_DEBUG_CACHE=1 in the .env file and getting the following log.

    image

    "cache-handler-redis" is the name of the cache handler file.

  8. rutwik-fale-amla commented on Dec 26, 2023

    @rutwik-fale-amla

    @better-salmon

    This is my cache handler

    /* eslint-disable no-console */
    /* eslint-disable @typescript-eslint/no-var-requires */
    const { IncrementalCache } = require("@neshca/cache-handler");
    const createLruCache = require("@neshca/cache-handler/local-lru").default;
    const { reviveFromBase64Representation, replaceJsonWithBase64 } = require("@neshca/json-replacer-reviver");
    const { createClient } = require("redis");
    
    const REVALIDATED_TAGS_KEY = "sharedRevalidatedTags";
    
    const client = createClient({
      url: "redis://localhost:6379",
    });
    
    client.on("error", (error) => {
      console.error("Redis error:", error);
    });
    
    IncrementalCache.onCreation(async () => {
      // read more about TTL limitations https://caching-tools.github.io/next-shared-cache/configuration/ttl
      const useTtl = false;
    
      if (!client.isOpen) {
        await client.connect();
      }
    
      const localCache = createLruCache({
        useTtl,
      });
    
      function assertClientIsReady() {
        if (!client.isReady) {
          throw new Error("Redis client is not ready");
        }
      }
      const redisCache = {
        name: "cache-handler-redis",
        async get(key) {
          assertClientIsReady();
    
          const result = await client.get(key);
    
          if (!result) {
            return null;
          }
    
          return JSON.parse(result, reviveFromBase64Representation);
        },
        async set(key, value, ttl) {
          assertClientIsReady();
    
          await client.set(key, JSON.stringify(value, replaceJsonWithBase64), useTtl && typeof ttl === "number" ? { EX: ttl } : undefined);
        },
        async getRevalidatedTags() {
          assertClientIsReady();
    
          const sharedRevalidatedTags = await client.hGetAll(REVALIDATED_TAGS_KEY);
    
          const entries = Object.entries(sharedRevalidatedTags);
    
          const revalidatedTags = entries.reduce((acc, [tag, revalidatedAt]) => {
            acc[tag] = Number(revalidatedAt);
            return acc;
          }, {});
    
          return revalidatedTags;
        },
        async revalidateTag(tag, revalidatedAt) {
          assertClientIsReady();
    
          await client.hSet(REVALIDATED_TAGS_KEY, {
            [tag]: revalidatedAt,
          });
        },
      };
    
      return {
        cache: [redisCache, localCache],
        useFileSystem: !useTtl,
      };
    });
    
    module.exports = IncrementalCache;
  9. locked and limited conversation to collaborators on Dec 26, 2023
  10. converted this issue into a discussion #240 on Dec 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions