ioredis

repository·main·Indexed 12 days ago

https://github.com/redis/ioredis

A robust, performance-focused, and full-featured Redis client for Node.js written in TypeScript. Version 6.0.0 supports advanced Redis features including Cluster, Sentinel, Streams, and Lua scripting. It provides built-in support for pipelining, transactions via multi(), RESP3 protocol, and binary data handling with Buffers.

Tokens
20.7K
Snippets
58
Records
85
Agent score
94%

What's inside ioredis

  1. Use Transactions and Pipelines in Cluster Mode

    main

    Most features like multi and pipeline work in Cluster mode, but with specific constraints:

    1. Pipelines: All keys in a single pipeline must belong to the same slot (and thus the same node), as ioredis sends the entire pipeline to one node.
    2. Transactions (multi): You cannot use multi without a pipeline. Use cluster.multi({ pipeline: false }) if needed, but note that ioredis won't know which node to send the multi command to unless it's part of a pipeline context.
    3. Automatic Resending: If a pipeline receives a MOVED or ASK error, ioredis will automatically resend the entire pipeline to the new node only if:
      • All errors in the pipeline are identical (e.g., all point to the same new node).
      • All successfully executed commands in that pipeline were read-only.

    If these conditions aren't met, the pipeline will fail to prevent side effects.

  2. Implement Read-Write Splitting in Redis Cluster

    main

    You can scale your cluster by directing read queries to slave nodes using the scaleReads option. By default, scaleReads is set to "master" (only masters are queried).

    Available options for scaleReads:

    1. "all": Write queries go to masters; read queries go to masters or slaves randomly.
    2. "slave": Write queries go to masters; read queries go to slaves.
    3. function(nodes, command): A custom selection function. The first node in nodes is always the master for the relevant slots. If the function returns an array, a random node from that array is selected.

    Warning: When using "slave", results might be stale due to replication lag between master and slave.

    const cluster = new Redis.Cluster(
      [
        /* nodes */
      ],
      {
        scaleReads: "slave",
      }
    );
    cluster.set("foo", "bar"); // Sent to a master
    cluster.get("foo", (err, res) => {
      // Sent to a slave
    });
  3. Use Transparent Key Prefixing

    main

    You can automatically prepend a string to all keys in a command by setting the keyPrefix option during Redis instance construction. This is useful for managing namespaces.

    Warning: This does not apply to commands that take patterns instead of actual keys (like KEYS or SCAN), nor does it apply to the replies of commands even if they are key names.

    const fooRedis = new Redis({ keyPrefix: "foo:" });
    fooRedis.set("bar", "baz"); // Actually sends SET foo:bar baz
    
    // Works with custom commands and pipelining
    fooRedis
      .pipeline()
      .sort("list", "BY", "weight_*->fieldname")
      .exec();
  4. Implement Pub/Sub with ioredis

    main

    ioredis supports the Publish–subscribe pattern using Node.js events.

    Important: A single Redis instance cannot act as both a publisher and a subscriber simultaneously. When subscribe() or psubscribe() is called, the connection enters 'subscriber mode', where only subscription-related commands (like ping, quit, unsubscribe) are valid. To publish messages while also listening for them in the same process, you must create two separate Redis instances.

    const Redis = require("ioredis");
    
    // To use both roles, create two instances
    const sub = new Redis();
    const pub = new Redis();
    
    // Subscriber setup
    sub.subscribe("my-channel", (err, count) => {
      console.log(`Subscribed to ${count} channels`);
    });
    
    sub.on("message", (channel, message) => {
      console.log(`Received ${message} from ${channel}`);
    });
    
    // Pattern subscription
    sub.psubscribe("pat*ern", (err, count) => {});
    sub.on("pmessage", (pattern, channel, message) => {});
    
    // Publisher usage
    pub.publish("my-channel", "Hello World");
  5. Observe telemetry using Diagnostics Channels

    main

    ioredis publishes telemetry through Node.js diagnostics_channel, allowing APM tools to observe commands, connections, and batch operations without modifying application code. This requires Node.js >= 18.19.0.

    Sub-events include start, end, asyncStart, asyncEnd, and error. You subscribe using the pattern tracing:ioredis:<type>:<event>.

    Available channels:

    • ioredis:command: Individual commands (standalone, pipeline, or MULTI).
    • ioredis:batch: Entire MULTI transactions.
    • ioredis:connect: Socket connection attempts.

    Note on Security: Command arguments are sanitized. Sensitive values in commands like SET or AUTH are replaced with ?. Read-only commands like GET and DEL retain all arguments.

    import dc from "node:diagnostics_channel";
    
    dc.subscribe("tracing:ioredis:command:start", ({ command, args }) => {
      console.log(`> ${command}`, args);
    });
    
    dc.subscribe("tracing:ioredis:command:asyncEnd", ({ command }) => {
      console.log(`${command} settled`);
    });
    
    dc.subscribe("tracing:ioredis:command:error", ({ command, error }) => {
      console.error(`${command} failed:`, error);
    });
  6. Use Managed HIMPORT Fieldsets (experimental)

    main

    The HIMPORT command (requires Redis 8.10+) provides fast ingestion for hashes sharing the same field names. ioredis supports managed fieldsets to automate preparation and recovery.

    Warning: This feature is experimental. Always await a SET command before issuing dependent commands.

    For Cluster clients, himportFieldsets must be passed at the top level of the Cluster options, not under redisOptions.

    // Standalone configuration
    const redis = new Redis({
      himportFieldsets: [
        {
          name: "user-profile",
          fields: ["name", "email", "age"],
        },
      ],
    });
    
    // Cluster configuration
    const cluster = new Redis.Cluster(nodes, {
      himportFieldsets: [
        {
          name: "user-profile",
          fields: ["name", "email", "age"],
        },
      ],
    });
    
    // Usage
    await redis.himport(
      "SET",
      "user:42",
      "user-profile",
      "Ada",
      "ada@example.com",
      "37"
    );
  7. Handle binary data with Buffers

    main

    ioredis supports binary data out of the box. To send binary data, pass a Buffer to standard commands like set.

    To retrieve binary data, use the command variant with the Buffer suffix (e.g., getBuffer). This returns a Buffer instead of a UTF-8 string. Note that you do not need the Buffer suffix to send binary data, but you might need it if you use the GET parameter to return the old value during a set operation.

    // Sending binary data
    redis.set("foo", Buffer.from([0x62, 0x75, 0x66]));
    
    // Retrieving binary data
    const result = await redis.getBuffer("foo");
    // result is <Buffer 62 75 66>
    
    // Using GET to return old value as Buffer
    const result = await redis.setBuffer("foo", "new value", "GET");
  8. Connect to Redis using Sentinel

    main

    ioredis supports Redis Sentinel for high availability. When connecting via Sentinel, ioredis guarantees that the node you connect to is always a master (or a slave if role: 'slave' is specified), even during failovers. During a failover, commands are queued and executed once the new master is established.

    Key Sentinel Options:

    • sentinels: An array of { host, port } objects for Sentinel instances.
    • name: The name of the Sentinel group (e.g., mymaster).
    • sentinelPassword: (Optional) Password for Sentinel instances.
    • role: Set to slave to connect to a random slave instead of the master.
    • preferredSlaves: (Optional) A function or array to prioritize specific slaves.
    • enableTLSForSentinelMode: (Optional) Set to true if connecting to encrypted Sentinel instances.
    • sentinelRetryStrategy: (Optional) A function invoked when all Sentinel nodes are unreachable.
    const redis = new Redis({
      sentinels: [
        { host: "localhost", port: 26379 },
        { host: "localhost", port: 26380 },
      ],
      name: "mymaster",
    });
    
    redis.set("foo", "bar");
  9. Consume Redis Streams

    main

    Redis Streams can be used as communication channels or log-like data structures. To consume messages, use the xread command with the BLOCK option. This allows you to wait for new data to arrive. When consuming, you typically pass the ID of the last processed message to the next xread call to continue from where you left off.

    const Redis = require("ioredis");
    const redis = new Redis();
    
    const processMessage = (message) => {
      console.log("Id: %s. Data: %O", message[0], message[1]);
    };
    
    async function listenForMessage(lastId = "$") {
      // results is an array of [key, messages]
      const results = await redis.xread("BLOCK", 0, "STREAMS", "mystream", lastId);
      const [key, messages] = results[0];
    
      messages.forEach(processMessage);
    
      // Pass the last id of the results to the next round.
      await listenForMessage(messages[messages.length - 1][0]);
    }
    
    listenForMessage();
  10. Use Sharded Pub/Sub in Redis Cluster

    main

    Standard Pub/Sub in a cluster broadcasts messages across all nodes, which has scalability limits. For better scalability, use Sharded Pub/Sub with the spublish and ssubscribe commands.

    Requirements:

    1. You must enable shardedSubscribers: true in the Redis.Cluster options.
    2. All channel names in a single ssubscribe call must map to the same hash slot.
    3. You can call ssubscribe multiple times to subscribe to different slots.

    Listen for messages using the smessage event.

    const cluster: Cluster = new Cluster([{ host: host, port: port }], {
      shardedSubscribers: true,
    });
    
    // Register the callback
    cluster.on("smessage", (channel, message) => {
      console.log(message);
    });
    
    // Subscribe to the channels on the same slot
    cluster
      .ssubscribe("channel{my}:1", "channel{my}:2")
      .then((count: number) => {
        console.log(count);
      })
      .catch((err: any) => {
        console.log(err);
      });
    
    // Publish a message
    cluster
      .spublish("channel{my}:1", "This is a test message to my first channel.")
      .then((value: number) => {
        console.log("Published a message to channel{my}:1");
      });
  11. Connect to a Redis Cluster

    main

    To connect to a Redis Cluster, use the Redis.Cluster constructor. The first argument is an array of node objects (at least one is required to discover the rest of the cluster). The second argument is an optional configuration object.

    Note that you do not need to list every node in the cluster; ioredis will automatically discover other nodes once it connects to at least one.

    const Redis = require("ioredis");
    
    const cluster = new Redis.Cluster([
      {
        port: 6380,
        host: "127.0.0.1",
      },
      {
        port: 6381,
        host: "127.0.0.1",
      },
    ]);
    
    cluster.set("foo", "bar");
    cluster.get("foo", (err, res) => {
      // res === 'bar'
    });