refactor: improve premium squads feature performance, add benchmarks

This commit is contained in:
3252a8
2026-05-20 22:31:37 +03:00
parent 5a0e0033ec
commit 8192eaf55b
10 changed files with 618 additions and 77 deletions
+29 -1
View File
@@ -1,9 +1,13 @@
import hashlib
import logging
from typing import Optional, Tuple
from bot.services.panel_api_service import PanelApiService
from bot.utils.ttl_cache import AsyncTTLCache
from config.settings import Settings
_CRYPT4_LINK_CACHES: dict[tuple[int, int], AsyncTTLCache] = {}
async def _encrypt_raw_link(settings: Settings, raw_link: str) -> Optional[str]:
"""Encrypt the raw subscription URL using the panel's happ crypt4 API."""
@@ -14,6 +18,30 @@ async def _encrypt_raw_link(settings: Settings, raw_link: str) -> Optional[str]:
return None
def _crypt4_link_cache(settings: Settings) -> Optional[AsyncTTLCache]:
ttl_seconds = int(getattr(settings, "CRYPT4_LINK_CACHE_TTL_SECONDS", 3600) or 0)
if ttl_seconds <= 0:
return None
cache_key = (id(settings), ttl_seconds)
cache = _CRYPT4_LINK_CACHES.get(cache_key)
if cache is None:
cache = AsyncTTLCache(
ttl_seconds=ttl_seconds,
settings=settings,
namespace="crypt4:links",
)
_CRYPT4_LINK_CACHES[cache_key] = cache
return cache
async def _encrypt_raw_link_cached(settings: Settings, raw_link: str) -> Optional[str]:
cache = _crypt4_link_cache(settings)
if cache is None:
return await _encrypt_raw_link(settings, raw_link)
key = hashlib.sha256(raw_link.encode("utf-8")).hexdigest()
return await cache.get_or_load(key, lambda: _encrypt_raw_link(settings, raw_link))
async def prepare_config_links(
settings: Settings, raw_link: Optional[str]
) -> Tuple[Optional[str], Optional[str]]:
@@ -35,7 +63,7 @@ async def prepare_config_links(
button_link = cleaned
if settings.CRYPT4_ENABLED:
encrypted_payload = await _encrypt_raw_link(settings, cleaned)
encrypted_payload = await _encrypt_raw_link_cached(settings, cleaned)
if encrypted_payload:
display_link = encrypted_payload
button_link = display_link
+28 -20
View File
@@ -37,37 +37,45 @@ class AsyncTTLCache:
return True
async def get_or_load(self, key: str, loader: Callable[[], Awaitable[Any]]) -> Any:
if self.settings is not None and self.namespace:
try:
from bot.infra.redis import cache_get_json, cache_set_json, redis_key
cache_key = redis_key(self.settings, "cache", self.namespace, key)
cached = await cache_get_json(self.settings, cache_key)
if cached is not None:
return cached
value = await loader()
if self._is_cacheable(value):
await cache_set_json(
self.settings,
cache_key,
value,
max(1, int(self.ttl_seconds)),
)
return value
except Exception:
pass
cached = self.get_fresh(key)
if cached is not None:
return cached
lock = self._locks.setdefault(key, asyncio.Lock())
async with lock:
cached = self.get_fresh(key)
if cached is not None:
return cached
cache_key = None
if self.settings is not None and self.namespace:
try:
from bot.infra.redis import cache_get_json, redis_key
cache_key = redis_key(self.settings, "cache", self.namespace, key)
cached = await cache_get_json(self.settings, cache_key)
if cached is not None:
if self._is_cacheable(cached):
self._data[key] = (time.monotonic() + self.ttl_seconds, cached)
return cached
except Exception:
cache_key = None
value = await loader()
if self._is_cacheable(value):
self._data[key] = (time.monotonic() + self.ttl_seconds, value)
if cache_key is not None:
try:
from bot.infra.redis import cache_set_json
await cache_set_json(
self.settings,
cache_key,
value,
max(1, int(self.ttl_seconds)),
)
except Exception:
pass
return value
def invalidate(self, key: Optional[str] = None) -> None: