{"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/bb5beb56578573910e2ee8f39681edc214c41398/packages/client/lib/sentinel/index.ts#L1104-L1140","documentation":"Thrown by #getClient() on the replica read path when this.#replicaClients.length === 0 (sentinel/index.ts:1121). It fires for read commands routed to replicas (no clientInfo) after the replica client array was found empty — meaning replicaPoolSize was configured > 0 (useReplicas true) but no replica connection is currently established, e.g. all replicas down, none enumerated by sentinel, or a reconfigure in progress.","triggerScenarios":"Issuing a read-only command against a Sentinel client configured with replicaPoolSize > 0 when no replica client exists at that instant — typically right after a failover, during the initial connect window before replicas connect, or when the sentinel reports zero replicas.","commonSituations":"Spin-up race where reads fire before observe/transform wires up replicas; a deployment with no replicas but replicaPoolSize set; replicas all marked sdown so none got connected.","solutions":["If you do not actually need replica reads, set replicaPoolSize to 0 (default) so reads route to the master.","Ensure at least one healthy replica exists and is reachable from the client before issuing reads.","Retry the read after a short backoff to ride out a reconfigure/failover window.","Verify sentinelReplicas(name) returns nodes and that nodeClientOptions let the client dial them (host/port mapping, TLS, nodeAddressMap)."],"exampleFix":"// before\nconst opts = { name, sentinelRootNodes, replicaPoolSize: 2 };\n// reads fail immediately if replicas lag behind connect\n// after — fall back to master when no replicas are needed\nconst opts = { name, sentinelRootNodes, replicaPoolSize: 0 };","handlingStrategy":"retry","validationCode":"// Before issuing a replica read, confirm the sentinel client actually has replicas.\n// (useReplicas is true when replicaPoolSize > 0; if 0, reads go to master and this error cannot fire.)\nfunction wantsReplicaReads(opts) {\n  return (opts.replicaPoolSize ?? 0) > 0;\n}\n// If unsure replicas are up, prefer replicaPoolSize: 0 for correctness over read scaling.","typeGuard":null,"tryCatchPattern":"async function readWithReplicaFallback(sentinel, fn) {\n  for (let attempt = 0; attempt < 3; attempt++) {\n    try {\n      return await sentinel.execute(fn); // replica read path\n    } catch (e) {\n      if (e instanceof Error && /no replicas available for read/.test(e.message)) {\n        await new Promise(r => setTimeout(r, 200 * (attempt + 1)));\n        continue;\n      }\n      throw e;\n    }\n  }\n  // last resort: route to master by using a master lease\n  return sentinel.execute(fn, await sentinel.getClientLease());\n}","preventionTips":["Set replicaPoolSize to 0 unless you have confirmed healthy replicas.","Handle this error with a short backoff retry to survive reconfigure windows.","Subscribe to the sentinel 'error'/'ready' events to detect when replicas come back."],"tags":["sentinel","replicas","availability","read-routing"],"analyzedSha":"bb5beb56578573910e2ee8f39681edc214c41398","analyzedAt":"2026-08-03T19:09:15.686Z","schemaVersion":2}