performance: db/aiohttp connection pooling

This commit is contained in:
2025-12-18 07:51:47 -05:00
parent bc8b407a91
commit 10ccf8c8eb
7 changed files with 350 additions and 44 deletions

View File

@@ -24,9 +24,7 @@ import aiohttp
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi_throttle import RateLimiter
from fastapi.responses import JSONResponse
import redis
from lyric_search.sources import private
from auth.deps import get_current_user
from dotenv import load_dotenv
@@ -72,10 +70,10 @@ class Lighting:
self.util = util
self.constants = constants
# Redis for state persistence
self.redis_client = redis.Redis(
password=private.REDIS_PW, decode_responses=True
)
# Redis for state persistence - use shared sync client
import shared
self.redis_client = shared.get_redis_sync_client(decode_responses=True)
self.lighting_key = "lighting:state"
# Cync configuration from environment

View File

@@ -686,23 +686,23 @@ class Radio(FastAPI):
async def _send_lrc_to_client(self, websocket: WebSocket, station: str, track_data: dict):
"""Send cached LRC data to a specific client asynchronously. Only sends if LRC exists in cache."""
logging.info(f"[LRC Send] Checking cached LRC for station {station}")
logging.info(f"[LRC Send] Current track: {track_data.get('artist', 'Unknown')} - {track_data.get('song', 'Unknown')}")
logging.debug(f"[LRC Send] Checking cached LRC for station {station}")
logging.debug(f"[LRC Send] Current track: {track_data.get('artist', 'Unknown')} - {track_data.get('song', 'Unknown')}")
try:
# Only send if LRC is in cache
cached_lrc = self.lrc_cache.get(station)
logging.info(f"[LRC Send] Cache status for station {station}: {'Found' if cached_lrc else 'Not found'}")
logging.debug(f"[LRC Send] Cache status for station {station}: {'Found' if cached_lrc else 'Not found'}")
if cached_lrc:
logging.info("[LRC Send] Sending cached LRC to client")
logging.debug("[LRC Send] Sending cached LRC to client")
lrc_data: dict = {
"type": "lrc",
"data": cached_lrc,
"source": "Cache"
}
await websocket.send_text(json.dumps(lrc_data))
logging.info("[LRC Send] Successfully sent cached LRC to client")
logging.debug("[LRC Send] Successfully sent cached LRC to client")
else:
logging.info(f"[LRC Send] No cached LRC available for station {station}")
logging.debug(f"[LRC Send] No cached LRC available for station {station}")
except Exception as e:
logging.error(f"[LRC Send] Failed to send cached LRC to client: {e}")
logging.error(f"[LRC Send] Error details: {traceback.format_exc()}")
@@ -711,34 +711,34 @@ class Radio(FastAPI):
"""Send cached LRC data to a specific client asynchronously. Only sends if valid LRC exists in cache."""
try:
track_info = f"{track_data.get('artist', 'Unknown')} - {track_data.get('song', 'Unknown')}"
logging.info(f"[LRC Send {id(websocket)}] Starting LRC send for {track_info}")
logging.info(f"[LRC Send {id(websocket)}] Cache keys before lock: {list(self.lrc_cache.keys())}")
logging.debug(f"[LRC Send {id(websocket)}] Starting LRC send for {track_info}")
logging.debug(f"[LRC Send {id(websocket)}] Cache keys before lock: {list(self.lrc_cache.keys())}")
# Get cached LRC with lock to ensure consistency
async with self.lrc_cache_locks[station]:
logging.info(f"[LRC Send {id(websocket)}] Got cache lock")
logging.debug(f"[LRC Send {id(websocket)}] Got cache lock")
cached_lrc = self.lrc_cache.get(station)
logging.info(f"[LRC Send {id(websocket)}] Cache keys during lock: {list(self.lrc_cache.keys())}")
logging.info(f"[LRC Send {id(websocket)}] Cache entry length: {len(cached_lrc) if cached_lrc else 0}")
logging.debug(f"[LRC Send {id(websocket)}] Cache keys during lock: {list(self.lrc_cache.keys())}")
logging.debug(f"[LRC Send {id(websocket)}] Cache entry length: {len(cached_lrc) if cached_lrc else 0}")
# Only send if we have actual lyrics
if cached_lrc:
logging.info(f"[LRC Send {id(websocket)}] Preparing to send {len(cached_lrc)} bytes of LRC")
logging.debug(f"[LRC Send {id(websocket)}] Preparing to send {len(cached_lrc)} bytes of LRC")
lrc_data: dict = {
"type": "lrc",
"data": cached_lrc,
"source": "Cache"
}
await websocket.send_text(json.dumps(lrc_data))
logging.info(f"[LRC Send {id(websocket)}] Successfully sent LRC")
logging.debug(f"[LRC Send {id(websocket)}] Successfully sent LRC")
else:
logging.info(f"[LRC Send {id(websocket)}] No LRC in cache")
logging.debug(f"[LRC Send {id(websocket)}] No LRC in cache")
# If we have no cache entry, let's check if a fetch is needed
async with self.lrc_cache_locks[station]:
logging.info(f"[LRC Send {id(websocket)}] Checking if fetch needed")
logging.debug(f"[LRC Send {id(websocket)}] Checking if fetch needed")
# Only attempt fetch if we're the first to notice missing lyrics
if station not in self.lrc_cache:
logging.info(f"[LRC Send {id(websocket)}] Initiating LRC fetch")
logging.debug(f"[LRC Send {id(websocket)}] Initiating LRC fetch")
lrc, source = await self._fetch_and_cache_lrc(station, track_data)
if lrc:
self.lrc_cache[station] = lrc
@@ -748,7 +748,7 @@ class Radio(FastAPI):
"source": source
}
await websocket.send_text(json.dumps(lrc_data))
logging.info(f"[LRC Send {id(websocket)}] Sent newly fetched LRC")
logging.debug(f"[LRC Send {id(websocket)}] Sent newly fetched LRC")
except Exception as e:
logging.error(f"[LRC Send {id(websocket)}] Failed: {e}")
logging.error(f"[LRC Send {id(websocket)}] Error details: {traceback.format_exc()}")
@@ -761,25 +761,25 @@ class Radio(FastAPI):
duration: Optional[int] = track_data.get("duration")
if not (artist and title):
logging.info("[LRC] Missing artist or title, skipping fetch")
logging.debug("[LRC] Missing artist or title, skipping fetch")
return None, "None"
logging.info(f"[LRC] Starting fetch for {station}: {artist} - {title}")
logging.debug(f"[LRC] Starting fetch for {station}: {artist} - {title}")
# Try LRCLib first with timeout
try:
async with asyncio.timeout(10.0): # 10 second timeout
logging.info("[LRC] Trying LRCLib")
logging.debug("[LRC] Trying LRCLib")
lrclib_result = await self.lrclib.search(artist, title, plain=False, raw=True)
if lrclib_result and lrclib_result.lyrics and isinstance(lrclib_result.lyrics, str):
logging.info("[LRC] Found from LRCLib")
logging.debug("[LRC] Found from LRCLib")
return lrclib_result.lyrics, "LRCLib"
except asyncio.TimeoutError:
logging.warning("[LRC] LRCLib fetch timed out")
except Exception as e:
logging.error(f"[LRC] LRCLib fetch error: {e}")
logging.info("[LRC] LRCLib fetch completed without results")
logging.debug("[LRC] LRCLib fetch completed without results")
# Try SR as fallback with timeout
try:
@@ -788,14 +788,14 @@ class Radio(FastAPI):
artist, title, duration=duration
)
if lrc:
logging.info("[LRC] Found from SR")
logging.debug("[LRC] Found from SR")
return lrc, "SR"
except asyncio.TimeoutError:
logging.warning("[LRC] SR fetch timed out")
except Exception as e:
logging.error(f"[LRC] SR fetch error: {e}")
logging.info("[LRC] No lyrics found from any source")
logging.debug("[LRC] No lyrics found from any source")
return None, "None"
except Exception as e:
logging.error(f"[LRC] Error fetching lyrics: {e}")

View File

@@ -1,11 +1,11 @@
import os
import random
from typing import LiteralString, Optional, Union
import aiosqlite as sqlite3
from fastapi import FastAPI, Depends
from fastapi_throttle import RateLimiter
from fastapi.responses import JSONResponse
from .constructors import RandMsgRequest
import shared # Use shared SQLite pool
class RandMsg(FastAPI):
@@ -103,11 +103,11 @@ class RandMsg(FastAPI):
}
)
async with sqlite3.connect(database=randmsg_db_path, timeout=1) as _db:
async with await _db.execute(db_query) as _cursor:
if not isinstance(_cursor, sqlite3.Cursor):
return JSONResponse(content={"err": True})
result: Optional[sqlite3.Row] = await _cursor.fetchone()
# Use shared SQLite pool for connection reuse
sqlite_pool = shared.get_sqlite_pool()
async with sqlite_pool.connection(randmsg_db_path, timeout=1) as _db:
async with _db.execute(db_query) as _cursor:
result = await _cursor.fetchone()
if not result:
return JSONResponse(content={"err": True})
(result_id, result_msg) = result