JusDB
Free DB Audit
NoSQL Databases

Running valkey-search on a Valkey Cluster: The Coordinator, Index Consistency and Partial Results

Run valkey-search 1.2.1 on Valkey Cluster: enable the gRPC coordinator, open port +20294, handle eventually consistent indexes, and catch silent partial results.

JusDB Team
Published October 31, 2026
Updated November 10, 2026
18 min read

You load valkey-search on a Valkey Cluster, create an index, and FT.SEARCH returns fewer documents than you stored. There is no error. Or FT.INFO idx PRIMARY fails with ERR PRIMARY option is not valid in this configuration. Both come from the same place: the search coordinator, a gRPC service inside the module that fans each query out to every shard and merges the answers. It needs its own port, a module argument that the valkey-bundle image does not pass, and timeout settings whose defaults the valkey.io pages disagree about. This guide covers valkey-search 1.2.1, the module in valkey-bundle 9.1.4 on Valkey 9.1.3: how the coordinator works, what it needs from the network, what happens on a timeout, and how to enable, monitor, and upgrade it.

TL;DR
  • On a cluster, load the module with --use-coordinator. Without it, each node indexes and searches its own keys only, and valkey-bundle loads modules with no arguments.
  • The coordinator listens on the Valkey port plus 20294 (26673 for port 6379). In the current source it has no TLS and no authentication, so allow it only between cluster nodes.
  • search.enable-partial-results defaults to yes in the source. A shard that fails is dropped from the reply with no error. Two valkey.io pages say the opposite.
  • Index definitions converge eventually. FT.CREATE can return an error and still leave the index in place.
  • Alert on search_coordinator_client_search_index_partition_failure_count, the main signal that a default-mode reply was incomplete.

Scope and review date: November 2026. This post covers valkey-search 1.2.1, the newest GA release, on self-managed Valkey Cluster 9.0 and 9.1, with notes on 1.3.0-rc2. Defaults, error strings, and port logic were checked on 10 November 2026 against the valkey.io documentation, the GitHub release notes, and the module source at the 1.2.1 and 1.3.0-rc2 tags. The commands were not run in a lab, so rehearse them on a staging cluster first.


Which valkey-search version does valkey-bundle ship?

As of November 2026, the newest GA release is valkey-search 1.2.1, published 7 July 2026. Like 1.2.0, it requires Valkey 9.0.1 or later. 1.3.0-rc2 followed on 26 October 2026 and requires Valkey 9.1.0.

In the bundle's versions.json, the 9.1.4 tag, pushed to Docker Hub with 9.1 and latest on 27 October 2026, contains Valkey 9.1.3 and search 1.2.1. The 9.0.7 and 8.1.11 images still carry search 1.0.3. Only the 9.2.0-rc4 image carries search 1.3.0-rc2, on Valkey 9.2.0-rc2.

bash
# On every node: core version and loaded modules
valkey-cli -h 10.0.1.11 INFO server | grep valkey_version
valkey-cli -h 10.0.1.11 MODULE LIST

Interpretation: MODULE LIST names the module search, with ver packed as major × 65536 + minor × 256 + patch, so 1.2.1 reports 66049. Every node should match before you create indexes.

Why does FT.SEARCH return only one node's keys?

Because the coordinator is off. In the 1.2.1 source, use-coordinator defaults to false and is a hidden setting. Hidden settings are not registered with Valkey's CONFIG system, so the only way to turn it on is the module argument --use-coordinator, given with no value, at startup. Neither CONFIG SET nor a search.use-coordinator line in valkey.conf can set it.

Without the coordinator, a cluster node behaves like a standalone server. FT.CREATE reaches that node and its replicas, no further, and FT.SEARCH searches only that node's slots. The valkey-bundle entrypoint appends --loadmodule for every module with no arguments, and the valkey-bundle page even shows search.use-coordinator no under the comment "Enable cluster mode".

ini
# valkey.conf on every cluster node (valkey-bundle path shown)
cluster-enabled yes
loadmodule /usr/lib/valkey/libsearch.so --use-coordinator

Interpretation: when you pass the config file as the container argument, the bundle entrypoint skips its own --loadmodule for any module whose loadmodule line it finds there, so this line wins. Do not write --use-coordinator yes. The parser treats the flag as taking no value, so it reads yes as the next argument and rejects it because it does not start with --.

After a restart, the module logs a use_coordinator: line, and the search_coordinator_* fields appear in INFO SEARCH only when the coordinator runs. If they are missing, that node searches locally only.

valkey-search behaviour by deployment mode

Switch modes to see what changes once the coordinator is on (valkey-search 1.2.1)

Standalone

BehaviourStandalone
Where FT.CREATE appliesThis node and its replicas
What FT.SEARCH searchesEvery indexed key on the node
FT.INFO PRIMARY or CLUSTERError: not valid in this configuration
Extra network portNone
A shard fails or is slowNot applicable
FT.SEARCH inside MULTI or LuaAllowed

Cluster, no coordinator

BehaviourCluster without --use-coordinator
Where FT.CREATE appliesThis node and its replicas only
What FT.SEARCH searchesKeys in this node's slots only
FT.INFO PRIMARY or CLUSTERError: not valid in this configuration
Extra network portNone
A shard fails or is slowNot applicable
FT.SEARCH inside MULTI or LuaAllowed

Cluster with coordinator

BehaviourCluster with --use-coordinator
Where FT.CREATE appliesEvery node, through the metadata protocol
What FT.SEARCH searchesOne node per shard, results merged
FT.INFO PRIMARY or CLUSTERAggregated across primaries or all nodes
Extra network portValkey port + 20294 (gRPC)
A shard fails or is slowDropped from the reply by default (SOMESHARDS)
FT.SEARCH inside MULTI or LuaError in 1.2.1; single-slot local index allowed in 1.3.0 RC

The silent case: a cluster without the coordinator returns valid-looking results for one shard. Check for search_coordinator_server_listening_port on every node before trusting result counts.

How does a query fan out across shards?

Per the search overview, index definitions are copied to every node, but each node indexes only the keys in its own slots. Any node that receives FT.SEARCH becomes the coordinator for that query. In the 1.2.1 source, it picks one node per shard at random, primary or replica, and sends each a gRPC request that carries a fingerprint of its index definition. It then merges the replies.

So replicas serve queries, and replication lag shows up in search results. Every query also costs one RPC per shard. For a non-vector query, each shard returns up to offset plus limit results, so LIMIT 1000 10 asks every shard for 1,010 keys. For a KNN query, each shard returns up to K. The overview warns that queries "may scale sub-linearly with increasing shard count".

FT.SEARCH fan-out through the coordinator

Follow one query from the client to every shard and back

FT.SEARCH fan-out on a Valkey Cluster Step 1: the client sends FT.SEARCH to any node, which becomes the coordinator. Step 2: it sends the query over gRPC on the Valkey port plus 20294 to one random node per shard, which checks the index fingerprint and searches locally. Step 3: the coordinator merges the results and replies; by default a failed shard is left out without an error. Client FT.SEARCH products Coordinator any node you hit Shard 2 node primary or replica Shard 3 node gRPC port + 20294 1 2 3 merge, then reply

Cost grows with shards: every cluster-wide query is one RPC per shard plus a merge. A hash-tagged single-slot index skips step 2 and goes to one node.

The way around fan-out is a single-slot index: its name contains a hash tag, and every prefix must carry the same tag. Since 1.2.0, queries on it go to one node. A cluster-wide index has the opposite rule: no hash tag in its prefixes.

bash
# Single-slot index: one tenant, one slot, one node per query
valkey-cli -c -h 10.0.1.11 FT.CREATE "idx:{tenant42}" ON HASH \
  PREFIX 1 "doc:{tenant42}:" SCHEMA price NUMERIC category TAG

# Cluster-wide index: every shard holds part of the data
valkey-cli -c -h 10.0.1.11 FT.CREATE products ON HASH PREFIX 1 product: \
  SCHEMA category TAG embedding VECTOR HNSW 6 \
  TYPE FLOAT32 DIM 768 DISTANCE_METRIC COSINE

Interpretation: breaking either rule fails with All PREFIX arguments must contain the same hash tag as the index or PREFIX argument(s) must not contain a hash tag. A single-slot index keeps all its data in one shard, so it suits per-tenant data, not one large catalogue.

Which ports and TLS settings does the coordinator need?

A cluster node already uses the client port and the cluster bus, which is the client port plus 10000 unless cluster-port is set (cluster tutorial). valkey-search adds a third: the Valkey port plus 20294, per the overview and GetCoordinatorPort() in src/coordinator/util.h. Port 6378 is the exception and uses an offset of 20295, so both 6378 and 6379 map to 26673. No config overrides the offset, and the module refuses to start the coordinator if the Valkey port is above 45241.

Two TLS details matter. The listener takes its base from tls-port when that is non-zero, and from port otherwise. And the gRPC channel itself is not encrypted. In both the 1.2.1 and 1.3.0-rc2 source, the server and client are built with gRPC's insecure credentials, and the server binds to [::], all addresses. We found no authentication on the service. Queries and result documents cross it in plaintext, even when clients use TLS.

bash
# Allow gRPC only from the cluster subnet (example with ufw)
sudo ufw allow from 10.0.1.0/24 to any port 26673 proto tcp
sudo ufw deny 26673/tcp

# From each node, check every peer's coordinator port
for n in 10.0.1.11 10.0.1.12 10.0.1.13; do nc -vz -w 2 "$n" 26673; done

Interpretation: every node must reach every other node on this port, replicas included, because replicas serve queries. A failed nc means queries through that node drop a shard. On untrusted networks, encrypt below the application, for example with a VPN between hosts.

Running both port and tls-port

The listener uses tls-port, but peers dial the port CLUSTER SLOTS reports, which Valkey picks by the asking connection's type. On nodes with both listeners, test fan-out from TLS and plaintext clients. The source has a TODO about TLS handling here.

For TLS and ACLs on the client and bus ports, see hardening Valkey with ACLs, TLS and LDAP.

How consistent are index definitions across the cluster?

Eventually consistent, by design. Each primary periodically broadcasts a Merkle-tree checksum of its index definitions over the cluster bus. Nodes that see a mismatch negotiate each index with version numbers and last-writer-wins timestamps. The node that loses deletes its copy and recreates it from the winning definition, which restarts that node's backfill. Since 1.2.0, replicas no longer take part and take their metadata from their primary.

FT.CREATE and FT.DROPINDEX change the local copy first and then wait for the cluster to converge. In 1.2.1, the create check waits up to search.ft-info-timeout-ms (5000 ms by default in the source). If convergence fails, the command returns an error. The overview is explicit that "No attempt is made to undo any failed metadata mutation". The usual cause is a shard down or a network partition.

bash
valkey-cli -c -h 10.0.1.11 FT.CREATE orders ON HASH PREFIX 1 order: \
  SCHEMA status TAG total NUMERIC
# (error) Unable to contact all cluster members

# Do not retry blindly: see whether the index exists anyway
valkey-cli -c -h 10.0.1.11 FT._LIST
valkey-cli -c -h 10.0.1.12 FT._LIST

Interpretation: this is the fan-out timeout reply in the 1.2.1 source; other failures read Inconsistent index state error found. or Communication error between nodes found. If FT._LIST shows the index, it exists and will spread once the shard is back, and a second FT.CREATE fails because the name is taken.

A metadata revision bug in 1.2.x

1.3.0-rc2 fixes #1447: nodes rejected index metadata once its revision, which grows with every create and drop, exceeded the packed module version (66049 for 1.2.1). It affects clusters that create and drop indexes very often.

What happens when a shard times out or fails?

This is where the documentation contradicts itself. Four pages and the source give different defaults:

SourceDefault it states
Search configurables and search monitoringenable-partial-results yes, enable-consistent-results no
FT.SEARCH referenceSOMESHARDS unless enable-partial-results is no; INCONSISTENT unless enable-consistent-results is yes
Search overview"If a timeout occurs then by default an error is returned"
FT.INFO referenceALLSHARDS and CONSISTENT are the defaults
Module source, 1.2.1 and 1.3.0-rc2Partial results true, consistent results false, for FT.SEARCH and FT.INFO alike

The source wins. Out of the box, FT.SEARCH, FT.AGGREGATE, and FT.INFO run as SOMESHARDS and INCONSISTENT. In the 1.2.1 fan-out code, a shard whose RPC returns an error is left out, and the reply carries no marker. The command fails only if every shard fails.

bash
# Completeness required: fail instead of dropping a shard
valkey-cli -c -h 10.0.1.11 FT.SEARCH products "@category:{shoes}" \
  ALLSHARDS CONSISTENT TIMEOUT 2000 LIMIT 0 20

# Or change the default for every query on a node
valkey-cli -h 10.0.1.11 CONFIG SET search.enable-partial-results no

Interpretation: with ALLSHARDS, the first failed shard fails the query. The index fingerprint check always runs on each shard, and CONSISTENT adds a slot-ownership check. With CONSISTENT, a mismatch returns Index or slot consistency check failed; without it, the mismatched shard is treated like any failed shard. CONFIG SET is per node, so apply it everywhere and in your config files.

Three timeouts apply (defaults from the 1.2.1 source):

  • search.default-timeout-ms: 50000, used when a query gives no TIMEOUT. TIMEOUT is capped at 60000; 1.3 adds search.max-timeout-ms for the cap.
  • search.coordinator-query-timeout-secs: the gRPC deadline per shard RPC. 25 in the source; the monitoring page says 120.
  • search.ft-info-timeout-ms: 5000 (2500 per RPC) for FT.INFO fan-out and the FT.CREATE check.

Reading the 1.2.1 source, partial results cover shards that return an error inside the query window. If the coordinator's own query timeout fires first, the client gets Search operation cancelled due to timeout whatever the setting. Test both paths on staging.

How do you size and monitor search on a cluster?

Size per node, not per cluster. Each primary indexes its slots, and each replica builds and holds its own copy of the index from the replication stream. Backfill runs on every node independently. Reader and writer threads default to the physical core count. There is no vendor formula for index memory, so load a representative sample and measure. search_used_memory_bytes in INFO SEARCH gives the module total per node. FT.INFO gives user_indexed_memory per attribute, summed across primaries with PRIMARY and across all nodes with CLUSTER.

The search observables page drops the search_ prefix that INFO prints; the monitoring page keeps it. Alert on the prefixed names.

bash
valkey-cli -h 10.0.1.11 INFO SEARCH | grep -E \
  'coordinator_client_search_index_partition_failure_count|get_global_metadata_failure_count|background_indexing_status|failure_requests_count'

Interpretation: a rise in the partition failure count means this node, as coordinator, missed a shard's answer; under SOMESHARDS that query returned incomplete results. A rising metadata failure count points to metadata sync trouble. Alert on the rate of these counters; Valkey monitoring with Prometheus covers the exporter side.

Coordinator health and cluster-wide backfill

Illustrative output: field names and reply shape follow the 1.2.1 source; the numbers are invented

ops@bastion:~
$ valkey-cli -h 10.0.1.11 INFO SEARCH | grep coordinator_search_coordinator_server_listening_port:26673search_coordinator_client_search_index_partition_success_count:48210search_coordinator_client_search_index_partition_failure_count:37$ valkey-cli -c -h 10.0.1.11 FT.INFO products CLUSTER 1) mode 2) cluster 3) index_name 4) products 5) backfill_in_progress 6) "1" 7) backfill_complete_percent_max 8) "1.000000" 9) backfill_complete_percent_min10) "0.412000"11) state12) backfill_in_progress

Read the minimum: backfill_complete_percent_min is the slowest node. Until it reaches 1, queries routed to that node miss pre-existing keys. The state backfill_paused_by_oom means a node hit maxmemory. Field list: FT.INFO reference.

How do you enable search on an existing cluster safely?

The coordinator setting is read only at startup, so enabling it is a rolling restart: replicas first, then fail over each shard and restart the old primary.

Enabling valkey-search on a running Valkey Cluster

Seven steps; with a replica in every shard, no step needs a write outage

  1. Check core and module versions

    search 1.2.x needs Valkey 9.0.1 or later on every node; 1.3 needs 9.1.0.

    valkey-cli -h 10.0.1.11 INFO server \ | grep valkey_version
  2. Open the gRPC port between nodes

    Valkey port plus 20294 on every node, from cluster nodes only, replicas included.

    sudo ufw allow from 10.0.1.0/24 \ to any port 26673 proto tcp
  3. Add the module line with --use-coordinator

    Put it in every node's config file. The flag takes no value.

    loadmodule /usr/lib/valkey/libsearch.so \ --use-coordinator
  4. Restart replicas, then fail over each shard

    Restart one replica at a time, promote it, then restart the old primary.

    valkey-cli -h 10.0.1.21 CLUSTER FAILOVER
  5. Confirm the coordinator on every node

    The listening port must appear, and peers must reach it.

    valkey-cli -h 10.0.1.11 INFO SEARCH \ | grep listening_port nc -vz -w 2 10.0.1.12 26673
  6. Create the index once and watch backfill

    Run FT.CREATE on one node, and wait for backfill on every node before sending traffic.

    valkey-cli -c -h 10.0.1.11 \ FT.INFO products CLUSTER
  7. Alert on shard failures

    Page on any rise in the partition failure counter.

    valkey-cli -h 10.0.1.11 INFO SEARCH \ | grep partition_failure_count

Rollback point: until step 6, removing --use-coordinator and restarting returns each node to local-only search. After indexes exist cluster-wide, plan a drop and recreate instead.

Coming from Redis Stack? See moving off Redis Stack to valkey-bundle for which indexes to rebuild.

What breaks during upgrades, node failures, and slot migrations?

Module upgrades

Upgrade the core first: each search release refuses to load on a core older than its minimum. The bundle's 9.0 to 9.1 move takes search from 1.0.3 to 1.2.1. Mixed versions are allowed during a roll, but per src/version.h, a node older than a metadata message's minimum module version drops the whole message. Do not use features newer than the oldest node's release, such as 1.3 FLOAT16 vectors, until every node is upgraded.

bash
# Downgrade check: an older module refuses newer index RDB data
grep "ValkeySearch RDB contents require minimum version" /var/log/valkey/valkey.log

Interpretation: this error, from the 1.2.1 loader, means the RDB holds index data that needs a newer module. Keep an RDB written before each module step, because an older module can refuse one written after it. On 1.3, leave search.emulate-release at its default until every node is upgraded, because its fixes change query behaviour.

A node goes down

Until the cluster marks the node failed (cluster-node-timeout defaults to 15000 ms per valkey.conf), random target selection can still pick it. The 1.2.1 gRPC client sets waitForReady, so a call to an unreachable peer waits until its 25-second deadline, which is shorter than the 50-second query timeout. Our reading of the source: such a query stalls for about 25 seconds, then returns without that shard. Set an explicit TIMEOUT on latency-sensitive queries. After failover, the coordinator refreshes its topology every 250 ms (search.cluster-map-expiration-ms).

A slot is migrating

1.3.0-rc1 fixes results that included keys in slots the node does not own, which double counted during slot migration (#921). On 1.2.1, expect duplicate hits or inflated totals during a reshard. Only CONSISTENT checks slot ownership and rejects such queries. Inside MULTI/EXEC or Lua, 1.2.1 rejects FT.SEARCH in cluster mode with MULTI/EXEC or Lua script are not supported in CME mode. 1.3.0-rc1 allows it for a single-slot index that the local node owns (#866).

Frequently asked questions

What port does valkey-search use in cluster mode?

The coordinator listens for gRPC on the Valkey port plus 20294, which is 26673 for the default port 6379. When tls-port is set, the module uses it as the base. The offset cannot be configured, so every cluster node must allow that port from every other node, replicas included.

Does valkey-search return partial results by default?

Yes. In the 1.2.1 and 1.3.0-rc2 source, search.enable-partial-results defaults to true, so FT.SEARCH runs as SOMESHARDS. A shard that returns an error is left out of the reply with no warning. The overview and FT.INFO pages say an error is the default; the source and configuration pages do not. Use ALLSHARDS where results must be complete.

Do I need to run FT.CREATE on every Valkey Cluster node?

No, if every node loads the module with --use-coordinator. Then FT.CREATE on one node spreads the definition through an eventually consistent metadata protocol. Without the coordinator, the index exists only on the node where you ran it and that node's replicas, and FT.SEARCH searches only that node's slots.

Is valkey-search coordinator traffic encrypted with TLS?

Not in the current source. In valkey-search 1.2.1 and 1.3.0-rc2, the coordinator builds its gRPC server and clients with insecure credentials and binds to all addresses, even when Valkey clients use TLS. Restrict the port to cluster nodes, and encrypt node-to-node traffic at the network layer on untrusted networks.

Which valkey-search version is in valkey-bundle latest?

As of November 2026, valkey/valkey-bundle:latest and 9.1.4, pushed on 27 October 2026, contain Valkey 9.1.3 and valkey-search 1.2.1. The 9.0.7 and 8.1.11 images carry valkey-search 1.0.3. Only the 9.2.0-rc4 image carries the 1.3.0-rc2 release candidate.

Key takeaways

  • Load valkey-search with --use-coordinator on every cluster node, and confirm that search_coordinator_server_listening_port appears in INFO SEARCH.
  • Allow the Valkey port plus 20294 only between cluster nodes. The gRPC channel has no TLS and no authentication in the current source.
  • Partial results are the default. Use ALLSHARDS, or set search.enable-partial-results no on every node, where an incomplete answer is worse than an error.
  • Treat an FT.CREATE error as "maybe created" and check FT._LIST before retrying.
  • Upgrade the core before the module, keep new index features off until every node is upgraded, and avoid resharding under heavy search load on 1.2.1.

Run valkey-search on Valkey Cluster with JusDB

JusDB's Valkey Cluster team can review how valkey-search is loaded and networked on your cluster, check your timeout and partial-result settings against what your application expects, and plan a rolling enablement or module upgrade with a rollback point.

Bring your Valkey and valkey-bundle versions, your module load lines, your index definitions, and the queries that matter most. Your team approves every production change.

Share this article

Database engineering notes

Articles like this one, in your inbox. No spam, unsubscribe anytime.

JusDB Team

Official JusDB content team

Keep reading

Moving a Multi-Database Valkey Instance into a 9.x Cluster

Move a Valkey instance that uses SELECT 1 to 15 into a 9.x cluster: keep or remap databases, copy each one, verify counts per database, and keep a way back.

Redis18 minNov 10, 2026
Read

Valkey Keyspace Notifications in Production: Expired Events, hexpired and Cluster Mode

Run Valkey keyspace notifications in production: why expired and hexpired events lag the TTL, how to subscribe on every cluster node, and when a stream is safer.

Redis20 minNov 10, 2026
Read

Valkey GLIDE in Production: AZ-Affinity Reads and Migrating from Legacy Clients

Configure Valkey GLIDE AZ-affinity reads on servers and clients, learn what GLIDE 2.5.3 does not support, and migrate from redis-py, Jedis, ioredis, or go-redis one service at a time.

Redis20 minNov 10, 2026
Read