diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index 8ffdeeadb3..bf6f1deb33 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -121,6 +121,7 @@ if WEBSOCKET_MANAGER == 'redis': redis_url=WEBSOCKET_REDIS_URL, redis_sentinels=ws_sentinels, redis_cluster=WEBSOCKET_REDIS_CLUSTER, + cache_set_signature=True, ) SESSION_POOL = RedisDict( diff --git a/backend/open_webui/socket/utils.py b/backend/open_webui/socket/utils.py index 00f8424aae..09fa47de15 100644 --- a/backend/open_webui/socket/utils.py +++ b/backend/open_webui/socket/utils.py @@ -61,12 +61,16 @@ class RedisLock: class RedisDict: - def __init__(self, name, redis_url, redis_sentinels=[], redis_cluster=False): + def __init__( + self, + name, + redis_url, + redis_sentinels=[], + redis_cluster=False, + cache_set_signature=False, + ): self.name = name - # Per-process cache of the last payload fingerprint written by set(). - # Used to skip redundant HSET round-trips when the model list hasn't - # changed — the dominant Redis write source on busy multi-pod setups. - self._last_signature: str | None = None + self._signature_name = f'{name}:signature' if cache_set_signature else None self.redis = get_redis_connection( redis_url, redis_sentinels, @@ -77,6 +81,8 @@ class RedisDict: def __setitem__(self, key, value): serialized_value = JSONCodec.dumps(value) self.redis.hset(self.name, key, serialized_value) + if self._signature_name: + self.redis.delete(self._signature_name) def __getitem__(self, key): value = self.redis.hget(self.name, key) @@ -88,6 +94,8 @@ class RedisDict: result = self.redis.hdel(self.name, key) if result == 0: raise KeyError(key) + if self._signature_name: + self.redis.delete(self._signature_name) def __contains__(self, key): return self.redis.hexists(self.name, key) @@ -106,8 +114,7 @@ class RedisDict: def set(self, mapping: dict): if not mapping: - self.redis.delete(self.name) - self._last_signature = None + self.clear() return # Serialize values once — reused for both the fingerprint and the write. @@ -120,11 +127,7 @@ class RedisDict: digest.update(b'\0') signature = digest.hexdigest() - # Skip the write when the prepared mapping is identical to the last one - # this process wrote. The check is per-instance (not distributed), but - # still eliminates the majority of redundant writes because each pod - # typically produces the same model list on consecutive refreshes. - if signature == self._last_signature: + if self._signature_name and self.redis.get(self._signature_name) == signature: return # Fetch existing keys before writing so we know which ones to remove. @@ -140,7 +143,8 @@ class RedisDict: if keys_to_remove: self.redis.hdel(self.name, *keys_to_remove) - self._last_signature = signature + if self._signature_name: + self.redis.set(self._signature_name, signature) def get(self, key, default=None): try: @@ -149,8 +153,11 @@ class RedisDict: return default def clear(self): - self.redis.delete(self.name) - self._last_signature = None + if self._signature_name: + self.redis.delete(self.name) + self.redis.delete(self._signature_name) + else: + self.redis.delete(self.name) def update(self, other=None, **kwargs): if other is not None: