perf: website_sync 批量查询 + custom_pages origin TTL 缓存
This commit is contained in:
@@ -76,6 +76,11 @@ npm run dev # 代理到 localhost:8000
|
|||||||
|
|
||||||
SQLite 数据库位于 `./data/app.db`,直接复制即可备份:
|
SQLite 数据库位于 `./data/app.db`,直接复制即可备份:
|
||||||
|
|
||||||
|
> **WAL 模式说明**:启用 WAL 后,备份前请先执行 checkpoint 将 WAL 合并入主库:
|
||||||
|
> ```bash
|
||||||
|
> sqlite3 ./data/app.db "PRAGMA wal_checkpoint(TRUNCATE);"
|
||||||
|
> ```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp ./data/app.db ./data/app.db.$(date +%Y%m%d)
|
cp ./data/app.db ./data/app.db.$(date +%Y%m%d)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -321,13 +321,49 @@ def _same_origin(a: str, b: str) -> bool:
|
|||||||
return _origin(a).rstrip("/") == _origin(b).rstrip("/")
|
return _origin(a).rstrip("/") == _origin(b).rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
# ── TTL 缓存:origin → upstream_id(30 秒自动过期,无手动 invalidate)──
|
||||||
|
import time as _time
|
||||||
|
from functools import wraps as _wraps
|
||||||
|
|
||||||
|
def _ttl_cache(ttl_seconds: int = 30, maxsize: int = 64):
|
||||||
|
def decorator(fn):
|
||||||
|
cache: dict = {}
|
||||||
|
@_wraps(fn)
|
||||||
|
def wrapper(*args):
|
||||||
|
key = args
|
||||||
|
now = _time.monotonic()
|
||||||
|
if key in cache:
|
||||||
|
value, ts = cache[key]
|
||||||
|
if now - ts < ttl_seconds:
|
||||||
|
return value
|
||||||
|
value = fn(*args)
|
||||||
|
if len(cache) >= maxsize:
|
||||||
|
cache.pop(next(iter(cache)))
|
||||||
|
cache[key] = (value, now)
|
||||||
|
return value
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def _find_matching_upstream(db: Session, page: CustomPage) -> Optional[Upstream]:
|
def _find_matching_upstream(db: Session, page: CustomPage) -> Optional[Upstream]:
|
||||||
page_origin = _origin(page.url)
|
page_origin = _origin(page.url)
|
||||||
if not page_origin:
|
if not page_origin:
|
||||||
return None
|
return None
|
||||||
for upstream in db.query(Upstream).order_by(Upstream.id).all():
|
upstream_id = _origin_to_upstream_id(page_origin)
|
||||||
if _origin(upstream.base_url) == page_origin:
|
if upstream_id is None:
|
||||||
return upstream
|
return None
|
||||||
|
return db.query(Upstream).filter(Upstream.id == upstream_id).first()
|
||||||
|
|
||||||
|
|
||||||
|
@_ttl_cache(ttl_seconds=30, maxsize=64)
|
||||||
|
def _origin_to_upstream_id(origin: str) -> Optional[int]:
|
||||||
|
"""按 origin 查匹配的上游 ID,结果缓存 30 秒。"""
|
||||||
|
from app.database import SessionLocal
|
||||||
|
from app.models.upstream import Upstream
|
||||||
|
with SessionLocal() as sess:
|
||||||
|
for upstream in sess.query(Upstream).order_by(Upstream.id).all():
|
||||||
|
if _origin(upstream.base_url) == origin:
|
||||||
|
return upstream.id
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,13 +96,27 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True)
|
|||||||
if not website:
|
if not website:
|
||||||
raise WebsiteError("网站不存在")
|
raise WebsiteError("网站不存在")
|
||||||
sources = binding_sources(binding)
|
sources = binding_sources(binding)
|
||||||
|
# ── 批量预查:收集所有上游 ID,一次查询上游名称 ──
|
||||||
|
upstream_ids = {int(s.get("upstream_id") or 0) for s in sources if s.get("upstream_id")}
|
||||||
|
upstreams = {}
|
||||||
|
if upstream_ids:
|
||||||
|
rows = db.query(Upstream).filter(Upstream.id.in_(upstream_ids)).all()
|
||||||
|
upstreams = {u.id: u for u in rows}
|
||||||
|
# ── 同一轮 sync 内的快照缓存(调用级,函数返回即释放)──
|
||||||
|
_snap_cache: dict[int, dict[str, Any]] = {}
|
||||||
|
|
||||||
|
def _get_snap(upstream_id: int) -> dict[str, Any]:
|
||||||
|
if upstream_id not in _snap_cache:
|
||||||
|
_snap_cache[upstream_id] = latest_rate_map(db, upstream_id)
|
||||||
|
return _snap_cache[upstream_id]
|
||||||
|
|
||||||
source_rates: list[dict[str, Any]] = []
|
source_rates: list[dict[str, Any]] = []
|
||||||
for source in sources:
|
for source in sources:
|
||||||
upstream_id = int(source.get("upstream_id") or 0)
|
upstream_id = int(source.get("upstream_id") or 0)
|
||||||
group_id = str(source.get("group_id") or "")
|
group_id = str(source.get("group_id") or "")
|
||||||
groups = latest_rate_map(db, upstream_id)
|
groups = _get_snap(upstream_id)
|
||||||
group = groups.get(group_id) if group_id else None
|
group = groups.get(group_id) if group_id else None
|
||||||
upstream = db.query(Upstream).filter(Upstream.id == upstream_id).first()
|
upstream = upstreams.get(upstream_id)
|
||||||
source_rates.append({
|
source_rates.append({
|
||||||
"upstream_id": upstream_id,
|
"upstream_id": upstream_id,
|
||||||
"upstream_name": source.get("upstream_name") or (upstream.name if upstream else ""),
|
"upstream_name": source.get("upstream_name") or (upstream.name if upstream else ""),
|
||||||
|
|||||||
Reference in New Issue
Block a user