diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index cb01d62e00..25792b0e78 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -57,6 +57,29 @@ REDIS = None # Configure CORS for Socket.IO SOCKETIO_CORS_ORIGINS = '*' if CORS_ALLOW_ORIGIN == ['*'] else CORS_ALLOW_ORIGIN +class LocalFilteredRedisManager(socketio.AsyncRedisManager): + """AsyncRedisManager that drops pub/sub emits with no local recipients. + + Every instance subscribed to the shared Socket.IO channel receives every + emit published by the whole fleet. Upstream ``_handle_emit`` re-encodes + the full packet before discovering the target room has no participants on + this instance, so each instance burns CPU serializing payloads addressed + to sessions it does not host — a cost that grows with instance count. + Bail out before that work when the room is empty here. Only string + rooms take the fast path; broadcasts (``room=None``) and any other + room shape (e.g. lists, which upstream indexes before validating) + always pass through unchanged. + """ + + async def _handle_emit(self, message): + room = message.get('room') + if isinstance(room, str): + namespace = message.get('namespace') or '/' + if next(self.get_participants(namespace, room), None) is None: + return + await super()._handle_emit(message) + + if WEBSOCKET_MANAGER == 'redis': sentinel_hosts = WEBSOCKET_SENTINEL_HOSTS or '' ws_redis_url = ( @@ -64,7 +87,7 @@ if WEBSOCKET_MANAGER == 'redis': if sentinel_hosts else WEBSOCKET_REDIS_URL ) - redis_manager = socketio.AsyncRedisManager(ws_redis_url, redis_options=WEBSOCKET_REDIS_OPTIONS) + redis_manager = LocalFilteredRedisManager(ws_redis_url, redis_options=WEBSOCKET_REDIS_OPTIONS) sio = socketio.AsyncServer( cors_allowed_origins=SOCKETIO_CORS_ORIGINS, async_mode='asgi', diff --git a/backend/requirements-min.txt b/backend/requirements-min.txt index d5646443e6..7ccc3f7261 100644 --- a/backend/requirements-min.txt +++ b/backend/requirements-min.txt @@ -33,6 +33,7 @@ alembic==1.18.4 pycrdt==0.13.1 redis +hiredis APScheduler==3.11.2 RestrictedPython==8.2 diff --git a/backend/requirements.txt b/backend/requirements.txt index e33bf131ea..4a3169762f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -30,7 +30,8 @@ psycopg[binary]==3.3.4 alembic==1.18.4 pycrdt==0.13.1 -redis==8.0.0 +redis==8.0.1 +hiredis==3.4.0 APScheduler==3.11.2 RestrictedPython==8.2 diff --git a/pyproject.toml b/pyproject.toml index 74659791df..1944904e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,8 @@ dependencies = [ "alembic==1.18.4", "pycrdt==0.13.1", - "redis==8.0.0", + "redis==8.0.1", + "hiredis==3.4.0", # "valkey-glide-sync==2.3.1", # optional: install manually if VECTOR_DB=valkey "pytz==2026.2",