{"record":{"id":"24ed7165b257be24","repo":"redis/redis-rb","slug":"watch-cannot-be-used-in-redis-distributed-because","errorCode":null,"errorMessage":"WATCH cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic.","messagePattern":"WATCH cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic\\.","errorType":"exception","errorClass":"Redis::Distributed::CannotDistribute","httpStatus":null,"severity":"error","filePath":"lib/redis/distributed.rb","lineNumber":36,"sourceCode":"    attr_reader :ring\n\n    def initialize(node_configs, options = {})\n      @tag = options[:tag] || /^\\{(.+?)\\}/\n      @ring = options[:ring] || HashRing.new\n      @node_configs = node_configs.map(&:dup)\n      @default_options = options.dup\n      # Schemas registered via himport_prepare, replayed to nodes that join the ring later (see\n      # #add_node) so ring membership changes don't leave nodes without the fieldsets that\n      # key-routed himport_set calls expect. Initialized before the add_node loop below.\n      @himport_fieldsets = {}\n      node_configs.each { |node_config| add_node(node_config) }\n      @subscribed_node = nil\n      @watch_key = nil\n    end\n\n    def node_for(key)\n      key = key_tag(key.to_s) || key.to_s\n      raise CannotDistribute, :watch if @watch_key && @watch_key != key\n\n      @ring.get_node(key)\n    end\n\n    def nodes\n      @ring.nodes\n    end\n\n    def add_node(options)\n      options = { url: options } if options.is_a?(String)\n      options = @default_options.merge(options)\n      options.delete(:tag)\n      options.delete(:ring)\n      node = Redis.new(options)\n      # Replay registered fieldsets before the node can receive key-routed himport_set calls:\n      # a node joining after a himport_prepare fan-out has neither the server-side fieldset nor\n      # a populated registry to self-recover from \"no such fieldset\".\n      @himport_fieldsets.each { |name, fields| node.himport_prepare(name, fields) }","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/redis/redis-rb/blob/2ba9010b91dab9e0fde1fbae3a9aae003f8bc307/lib/redis/distributed.rb#L18-L54","documentation":"Error \"WATCH cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic.\" thrown in redis/redis-rb.","triggerScenarios":"Thrown at lib/redis/distributed.rb:36 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Do not use watch/multi optimistic locking on Redis::Distributed; run the WATCH-based transaction against a single Redis instance that holds all involved keys.","Use Redis::Cluster or a standalone Redis connection if you need WATCH semantics."],"exampleFix":null,"handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"2ba9010b91dab9e0fde1fbae3a9aae003f8bc307","analyzedAt":"2026-08-23T03:54:57.017Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}