### 🔧 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:
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.21] - 2025-09-21
|
||||
|
||||
### 🔧 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
|
||||
|
||||
## [0.9.20] - 2025-09-10
|
||||
|
||||
### 🐛 Authors Endpoint Critical Fix
|
||||
|
||||
14
main.py
14
main.py
@@ -143,6 +143,18 @@ async def spa_handler(request: Request) -> Response:
|
||||
return JSONResponse({"error": "Admin panel not built"}, status_code=404)
|
||||
|
||||
|
||||
async def health_handler(request: Request) -> Response:
|
||||
"""Health check endpoint with Redis monitoring"""
|
||||
try:
|
||||
redis_info = await redis.get_info()
|
||||
return JSONResponse(
|
||||
{"status": "healthy", "redis": {"connected": redis.is_connected, "ping": await redis.ping(), **redis_info}}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Health check failed: {e}")
|
||||
return JSONResponse({"status": "unhealthy", "error": str(e)}, status_code=500)
|
||||
|
||||
|
||||
async def shutdown() -> None:
|
||||
"""Остановка сервера и освобождение ресурсов"""
|
||||
logger.info("Остановка сервера")
|
||||
@@ -293,6 +305,8 @@ app = Starlette(
|
||||
# OAuth маршруты
|
||||
Route("/oauth/{provider}", oauth_login, methods=["GET"]),
|
||||
Route("/oauth/{provider}/callback", oauth_callback, methods=["GET"]),
|
||||
# Health check endpoint
|
||||
Route("/health", health_handler, methods=["GET"]),
|
||||
# Статические файлы (CSS, JS, изображения)
|
||||
Mount("/assets", app=StaticFiles(directory=str(DIST_DIR / "assets"))),
|
||||
# Корневой маршрут для админ-панели
|
||||
|
||||
@@ -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