{"record":{"id":"11406471f1c58954","repo":"redis/node-redis","slug":"no-replicas-available-for-read","errorCode":null,"errorMessage":"no replicas available for read","messagePattern":"no replicas available for read","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/sentinel/index.ts","lineNumber":1122,"sourceCode":"  }\n\n  #handlePubSubControlChannel(channel: Buffer, _message: Buffer) {\n    this.#trace(\"pubsub control channel message on \" + channel);\n    this.#resetInBackground();\n  }\n\n  // if clientInfo is defined, it corresponds to a master client in the #masterClients array, otherwise loop around replicaClients\n  #getClient(clientInfo?: ClientInfo): RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping> {\n    if (clientInfo !== undefined) {\n      return this.#masterClients[clientInfo.id];\n    }\n\n    if (this.#replicaClientsIdx >= this.#replicaClients.length) {\n      this.#replicaClientsIdx = 0;\n    }\n\n    if (this.#replicaClients.length == 0) {\n      throw new Error(\"no replicas available for read\");\n    }\n\n    return this.#replicaClients[this.#replicaClientsIdx++];\n  }\n\n  async #reset() {\n    /* closing / don't reset */\n    if (this.#isReady == false || this.#destroy == true) {\n      return;\n    }\n\n    // already in #connect()\n    if (this.#connectPromise !== undefined) {\n      this.#anotherReset = true;\n      return await this.#connectPromise;\n    }\n\n    try {","sourceCodeStart":1104,"sourceCodeEnd":1140,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/sentinel/index.ts#L1104-L1140","documentation":"`#getClient` routes reads marked for replicas through `#replicaClients`; with `replicaPoolSize` defaulting to 0 and no replicas ever pooled, that array is empty and the round-robin helper throws instead of silently falling back. It is a hard failure by design so callers know read-splitting was never actually configured.","triggerScenarios":"Issuing a read routed to replicas (via the replica client path) when `replicaPoolSize` is 0 or when discovery connected zero replicas. The replica index helper runs before any command is sent.","commonSituations":"Leaving `replicaPoolSize` at its default of 0 while expecting reads to spread to replicas; topology where replicas exist but none were enumerated at connect time; replicas temporarily all down.","solutions":["Set `replicaPoolSize` to a positive number so the pool is populated.","Ensure the deployment actually has replicas and Sentinel reports them.","Route the read at the master when no replicas are required."],"exampleFix":"// before\nnew RedisSentinel({ name: 'mymaster', rootNodes }); // replicaPoolSize defaults to 0\n// after\nnew RedisSentinel({ name: 'mymaster', rootNodes, replicaPoolSize: 2 });","handlingStrategy":"validation","validationCode":"function assertReplicasConfigured(opts: { replicaPoolSize?: number }) {\n  if ((opts.replicaPoolSize ?? 0) <= 0) {\n    throw new Error('Reads routed to replicas require replicaPoolSize > 0');\n  }\n}\nassertReplicasConfigured(opts);","typeGuard":"function hasReplicaPool(opts: { replicaPoolSize?: number }): boolean {\n  return (opts.replicaPoolSize ?? 0) > 0;\n}","tryCatchPattern":"try {\n  return await sentinel.getReplicaClient();\n} catch (e) {\n  if (String(e).includes('no replicas available for read')) {\n    return await sentinel.getMasterClient(); // explicit fallback\n  }\n  throw e;\n}","preventionTips":["Set replicaPoolSize explicitly when you intend to read from replicas.","Confirm Sentinel reports replicas before enabling read-splitting."],"tags":["sentinel","replicas","read-routing","configuration"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}