### 🔧 Redis Connection Pool Fix - **🐛 Fixed "max number of clients reached" error**: Исправлена критическая ошибка превышения лимита соединений Redis - Добавлен `aioredis.ConnectionPool` с ограничением `max_connections=20` для 5 микросервисов - Реализовано переиспользование соединений вместо создания новых для каждого запроса - Добавлено правильное закрытие connection pool при shutdown приложения - Улучшена обработка ошибок соединения с автоматическим переподключением - **📊 Health Monitoring**: Добавлен `/health` endpoint для мониторинга состояния Redis - Отображает количество активных соединений, использование памяти, версию Redis - Помогает диагностировать проблемы с соединениями в production - **🔄 Connection Management**: Оптимизировано управление соединениями - Один connection pool для всех операций Redis - Автоматическое переподключение при потере соединения - Корректное закрытие всех соединений при остановке приложения ### 🧪 TypeScript Warnings Fix - **🏷️ Type Annotations**: Добавлены явные типы для устранения implicit `any` ошибок - Исправлены типы в `RolesModal.tsx` для параметров `roleName` и `r` - Устранены все TypeScript warnings в admin panel ### 🚀 CI/CD Improvements - **⚡ Mypy Optimization**: Исправлена проблема OOM (exit status 137) в CI - Оптимизирован `mypy.ini` с исключением тяжелых зависимостей - Добавлен `dmypy` с fallback на обычный `mypy` - Ограничена область проверки типов только критичными модулями - Добавлена проверка доступной памяти перед запуском mypy - **🐳 Docker Build**: Исправлены проблемы с PyTorch зависимостями - Увеличен `UV_HTTP_TIMEOUT=300` для загрузки больших пакетов - Установлен `TORCH_CUDA_AVAILABLE=0` для предотвращения CUDA зависимостей - Упрощены зависимости PyTorch в `pyproject.toml` для совместимости с Python 3.13
This commit is contained in:
@@ -23,30 +23,28 @@ class RedisService:
|
||||
self._client: aioredis.Redis | None = None
|
||||
self._redis_url = redis_url # Исправлено на _redis_url
|
||||
self._is_available = aioredis is not None
|
||||
self._connection_pool: aioredis.ConnectionPool | None = None
|
||||
|
||||
if not self._is_available:
|
||||
logger.warning("Redis is not available - aioredis not installed")
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close Redis connection"""
|
||||
"""Close Redis connection and connection pool"""
|
||||
if self._client:
|
||||
# Закрываем существующее соединение если есть
|
||||
try:
|
||||
await self._client.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing Redis connection: {e}")
|
||||
# Для теста disconnect_exception_handling
|
||||
if str(e) == "Disconnect error":
|
||||
# Сохраняем клиент для теста
|
||||
self._last_close_error = e
|
||||
raise
|
||||
# Для других исключений просто логируем
|
||||
logger.error(f"Error closing Redis client: {e}")
|
||||
finally:
|
||||
# Сохраняем клиент для теста disconnect_exception_handling
|
||||
if hasattr(self, "_last_close_error") and str(self._last_close_error) == "Disconnect error":
|
||||
pass
|
||||
else:
|
||||
self._client = None
|
||||
self._client = None
|
||||
|
||||
if self._connection_pool:
|
||||
try:
|
||||
await self._connection_pool.disconnect()
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing Redis connection pool: {e}")
|
||||
finally:
|
||||
self._connection_pool = None
|
||||
|
||||
# Добавляем метод disconnect как алиас для close
|
||||
async def disconnect(self) -> None:
|
||||
@@ -54,16 +52,13 @@ class RedisService:
|
||||
await self.close()
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""Connect to Redis"""
|
||||
"""Connect to Redis with connection pooling"""
|
||||
try:
|
||||
if self._client:
|
||||
# Закрываем существующее соединение
|
||||
try:
|
||||
await self._client.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing Redis connection: {e}")
|
||||
# Закрываем существующие соединения
|
||||
await self.close()
|
||||
|
||||
self._client = aioredis.from_url(
|
||||
# Создаем connection pool
|
||||
self._connection_pool = aioredis.ConnectionPool.from_url(
|
||||
self._redis_url,
|
||||
encoding="utf-8",
|
||||
decode_responses=True,
|
||||
@@ -71,16 +66,20 @@ class RedisService:
|
||||
socket_timeout=5,
|
||||
retry_on_timeout=True,
|
||||
health_check_interval=30,
|
||||
max_connections=20, # 20 соединений
|
||||
retry_on_error=[ConnectionError, TimeoutError],
|
||||
)
|
||||
|
||||
# Создаем клиент с connection pool
|
||||
self._client = aioredis.Redis(connection_pool=self._connection_pool)
|
||||
|
||||
# Test connection
|
||||
await self._client.ping()
|
||||
logger.info("Successfully connected to Redis")
|
||||
logger.info("Successfully connected to Redis with connection pooling")
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Failed to connect to Redis")
|
||||
if self._client:
|
||||
await self._client.close()
|
||||
self._client = None
|
||||
await self.close()
|
||||
return False
|
||||
|
||||
@property
|
||||
@@ -95,9 +94,12 @@ class RedisService:
|
||||
return None
|
||||
|
||||
async def execute(self, command: str, *args: Any) -> Any:
|
||||
"""Execute Redis command with reconnection logic"""
|
||||
"""Execute Redis command with connection pooling"""
|
||||
if not self.is_connected:
|
||||
await self.connect()
|
||||
logger.warning("Redis not connected, attempting to connect...")
|
||||
if not await self.connect():
|
||||
logger.error("Failed to connect to Redis")
|
||||
return None
|
||||
|
||||
try:
|
||||
cmd_method = getattr(self._client, command.lower(), None)
|
||||
@@ -122,8 +124,8 @@ class RedisService:
|
||||
except Exception:
|
||||
logger.exception("Redis retry failed")
|
||||
return None
|
||||
except Exception:
|
||||
logger.exception("Redis command failed")
|
||||
except Exception as e:
|
||||
logger.error(f"Redis command {command} failed: {e}")
|
||||
return None
|
||||
|
||||
async def get(self, key: str) -> str | bytes | None:
|
||||
@@ -251,6 +253,22 @@ class RedisService:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def get_info(self) -> dict[str, Any]:
|
||||
"""Get Redis server info"""
|
||||
if not self.is_connected or self._client is None:
|
||||
return {}
|
||||
try:
|
||||
info = await self._client.info()
|
||||
return {
|
||||
"connected_clients": info.get("connected_clients", 0),
|
||||
"used_memory": info.get("used_memory_human", "0B"),
|
||||
"redis_version": info.get("redis_version", "unknown"),
|
||||
"uptime_in_seconds": info.get("uptime_in_seconds", 0),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get Redis info: {e}")
|
||||
return {}
|
||||
|
||||
async def execute_pipeline(self, commands: list[tuple[str, tuple[Any, ...]]]) -> list[Any]:
|
||||
"""
|
||||
Выполняет список команд через pipeline для лучшей производительности.
|
||||
|
||||
Reference in New Issue
Block a user