refactor(database): isolate configuration transactions

This commit is contained in:
InfinityPacer
2026-08-23 02:29:22 +08:00
parent e99289a07b
commit 3d7fb44dc7
23 changed files with 1071 additions and 153 deletions
+64 -69
View File
@@ -1,4 +1,3 @@
import asyncio
import copy
import threading
from typing import Any, Optional, Union
@@ -14,15 +13,38 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
系统配置管理
"""
def __init__(self):
"""
加载配置到内存
"""
"""初始化空快照,数据库加载由启动组合根显式执行。"""
super().__init__()
self.__SYSTEMCONF = {}
self._rlock = threading.RLock()
self._alock = asyncio.Lock()
for item in SystemConfig.list(self._db):
self.__SYSTEMCONF[item.key] = item.value
self._snapshot_lock = threading.RLock()
self._write_lock = threading.RLock()
self._loaded = False
def load_snapshot(self) -> None:
"""从数据库加载完整配置,并一次性发布新的内存快照。"""
with self._write_lock:
snapshot = {
item.key: copy.deepcopy(item.value)
for item in SystemConfig.list(self._db)
}
with self._snapshot_lock:
self.__SYSTEMCONF = snapshot
self._loaded = True
def _require_loaded(self) -> None:
"""阻止消费者读取尚未完成启动加载的半成品快照。"""
if not self._loaded:
raise RuntimeError("系统配置快照尚未加载")
def _publish_value(self, key: str, value: Any) -> None:
"""在事务成功后短暂持锁发布单项配置。"""
with self._snapshot_lock:
self.__SYSTEMCONF[key] = copy.deepcopy(value)
def _publish_delete(self, key: str) -> None:
"""在事务成功后短暂持锁移除单项配置。"""
with self._snapshot_lock:
self.__SYSTEMCONF.pop(key, None)
def set(self, key: Union[str, SystemConfigKey], value: Any) -> Optional[bool]:
"""
@@ -33,59 +55,25 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
"""
if isinstance(key, SystemConfigKey):
key = key.value
with self._rlock:
# 旧值
old_value = self.__SYSTEMCONF.get(key)
# 更新内存(deepcopy避免内存共享)
self.__SYSTEMCONF[key] = copy.deepcopy(value)
conf = SystemConfig.get_by_key(self._db, key)
if conf:
if old_value != value:
# 假值(False/0/None/空容器)同样落库而不是删除记录:
# 读取端以「无记录」表示未配置并回落默认值,删除会使布尔开关的关闭态无法持久化
self._stage_update(conf, {"value": value})
return True
return None
else:
conf = SystemConfig(key=key, value=value)
self._stage_create(conf)
self._require_loaded()
with self._write_lock:
def write(db):
"""在当前事务中创建或更新配置记录。"""
conf = SystemConfig.get_by_key(db, key)
if conf:
if conf.value == value:
return None
# 假值同样是有效配置;删除记录会使读取端错误回落默认值。
conf.value = copy.deepcopy(value)
else:
db.add(SystemConfig(key=key, value=copy.deepcopy(value)))
return True
async def async_set(self, key: Union[str, SystemConfigKey], value: Any) -> Optional[bool]:
"""
异步设置系统设置
:param key: 配置键
:param value: 配置值
:return: 是否设置成功(True 成功/False 失败/None 无需更新)
"""
if isinstance(key, SystemConfigKey):
key = key.value
async with self._alock:
conf = await SystemConfig.async_get_by_key(self._db, key)
# 确定是否需要更新数据库
needs_db_update = False
if conf:
if conf.value != value:
needs_db_update = True
else: # 记录不存在,总是需要创建/更新
needs_db_update = True
if not needs_db_update:
# 即使数据库值相同,也要确保缓存同步
with self._rlock:
self.__SYSTEMCONF[key] = copy.deepcopy(value)
return None
# 执行数据库更新
if conf:
# 假值(False/0/None/空容器)同样落库而不是删除记录:
# 读取端以「无记录」表示未配置并回落默认值,删除会使布尔开关的关闭态无法持久化
await self._stage_async_update(conf, {"value": value})
else:
conf = SystemConfig(key=key, value=value)
await self._stage_async_create(conf)
# 数据库更新成功后,再更新缓存
with self._rlock:
self.__SYSTEMCONF[key] = copy.deepcopy(value)
return True
result = self._execute_sync_write(write)
# 数据库操作返回时事务已经提交,读取方不会看到尚未持久化的配置。
self._publish_value(key, value)
return result
def get(self, key: Optional[Union[str, SystemConfigKey]] = None) -> Any:
"""
@@ -95,7 +83,8 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
key = key.value
if not key:
return self.all()
with self._rlock:
with self._snapshot_lock:
self._require_loaded()
# 避免将__SYSTEMCONF内的值引用出去,会导致set时误判没有变动
return copy.deepcopy(self.__SYSTEMCONF.get(key))
@@ -107,7 +96,8 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
:param step: 递增步长
:return: 递增后的整数值
"""
with self._rlock:
self._require_loaded()
with self._write_lock:
value = int(self.get(key) or 0) + step
self.set(key, value)
return value
@@ -116,7 +106,8 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
"""
获取所有系统设置
"""
with self._rlock:
with self._snapshot_lock:
self._require_loaded()
# 避免将__SYSTEMCONF内的值引用出去,会导致set时误判没有变动
return copy.deepcopy(self.__SYSTEMCONF)
@@ -126,11 +117,15 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
"""
if isinstance(key, SystemConfigKey):
key = key.value
with self._rlock:
# 更新内存
self.__SYSTEMCONF.pop(key, None)
# 写入数据库
conf = SystemConfig.get_by_key(self._db, key)
if conf:
self._stage_delete(SystemConfig, conf.id)
self._require_loaded()
with self._write_lock:
def delete(db):
"""在当前事务中删除配置记录。"""
conf = SystemConfig.get_by_key(db, key)
if conf:
db.delete(conf)
self._execute_sync_write(delete)
self._publish_delete(key)
return True
+59 -33
View File
@@ -1,3 +1,5 @@
import copy
import threading
from typing import Any, Union, Dict, Optional
from app.db.base import DbOper
@@ -11,13 +13,30 @@ class UserConfigOper(DbOper, metaclass=Singleton):
用户配置管理
"""
def __init__(self):
"""
加载配置到内存
"""
"""初始化空快照,数据库加载由启动组合根显式执行。"""
super().__init__()
self.__USERCONF = {}
for item in UserConfig.list(self._db):
self.__set_config_cache(username=item.username, key=item.key, value=item.value)
self._snapshot_lock = threading.RLock()
self._write_lock = threading.RLock()
self._loaded = False
def load_snapshot(self) -> None:
"""从数据库加载完整用户配置,并一次性发布新的内存快照。"""
with self._write_lock:
snapshot: dict[str, dict[str, Any]] = {}
for item in UserConfig.list(self._db):
if item.username and item.key:
snapshot.setdefault(item.username, {})[item.key] = copy.deepcopy(
item.value
)
with self._snapshot_lock:
self.__USERCONF = snapshot
self._loaded = True
def _require_loaded(self) -> None:
"""阻止消费者读取尚未完成启动加载的半成品快照。"""
if not self._loaded:
raise RuntimeError("用户配置快照尚未加载")
def set(self, username: str, key: Union[str, UserConfigKey], value: Any):
"""
@@ -25,30 +44,43 @@ class UserConfigOper(DbOper, metaclass=Singleton):
"""
if isinstance(key, UserConfigKey):
key = key.value
# 更新内存
self.__set_config_cache(username=username, key=key, value=value)
# 写入数据库
conf = UserConfig.get_by_key(db=self._db, username=username, key=key)
if conf:
if value:
self._stage_update(conf, {"value": value})
else:
self._stage_delete(UserConfig, conf.id)
else:
conf = UserConfig(username=username, key=key, value=value)
self._stage_create(conf)
self._require_loaded()
with self._write_lock:
def write(db):
"""在当前事务中按用户配置的假值规则写入记录。"""
conf = UserConfig.get_by_key(db=db, username=username, key=key)
if conf:
if value:
conf.value = copy.deepcopy(value)
else:
db.delete(conf)
else:
db.add(
UserConfig(
username=username,
key=key,
value=copy.deepcopy(value),
)
)
self._execute_sync_write(write)
# 既有运行时语义会保留刚写入的假值,即使其数据库记录被删除。
self.__set_config_cache(username=username, key=key, value=value)
def get(self, username: str, key: Optional[Union[str, UserConfigKey]] = None) -> Any:
"""
获取用户配置
"""
if not username:
return self.__USERCONF
if isinstance(key, UserConfigKey):
key = key.value
if not key:
return self.__get_config_caches(username=username)
return self.__get_config_cache(username=username, key=key)
with self._snapshot_lock:
self._require_loaded()
if not username:
return copy.deepcopy(self.__USERCONF)
if isinstance(key, UserConfigKey):
key = key.value
if not key:
return copy.deepcopy(self.__get_config_caches(username=username))
return copy.deepcopy(self.__get_config_cache(username=username, key=key))
def __set_config_cache(self, username: str, key: str, value: Any):
"""
@@ -56,15 +88,9 @@ class UserConfigOper(DbOper, metaclass=Singleton):
"""
if not username or not key:
return
cache = self.__USERCONF
if not cache:
cache = {}
user_cache = cache.get(username)
if not user_cache:
user_cache = {}
cache[username] = user_cache
user_cache[key] = value
self.__USERCONF = cache
with self._snapshot_lock:
user_cache = self.__USERCONF.setdefault(username, {})
user_cache[key] = copy.deepcopy(value)
def __get_config_caches(self, username: str) -> Optional[Dict[str, Any]]:
"""