From cb4d16f98cded52c65600ba55e20c6017152e46c Mon Sep 17 00:00:00 2001 From: Seio Date: Sat, 9 May 2026 21:59:04 +0800 Subject: [PATCH] fix: add SQLite busy_timeout and WAL autocheckpoint to prevent CPU 100% epoll busy wait When SQLite WAL grows large (e.g., 6MB), checkpoint operations cause lock contention. Without busy_timeout and wal_autocheckpoint PRAGMAs, aiosqlite enters a busy retry loop that manifests as epoll_wait 100% CPU. Changes across all three SQLite databases in AstrBot: 1. Main DB (astrbot/core/db/sqlite.py): - Add PRAGMA busy_timeout=5000 (5s lock wait) - Add PRAGMA wal_autocheckpoint=500 (checkpoint every ~2MB) 2. KB DB (astrbot/core/knowledge_base/kb_db_sqlite.py): - Add connect_args={'timeout': 30} for engine - Add all PRAGMA settings (WAL, synchronous, busy_timeout, wal_autocheckpoint, cache_size, temp_store) - Remove duplicate/redundant old PRAGMA block 3. Document storage (astrbot/core/db/vec_db/faiss_impl/document_storage.py): - Add connect_args={'timeout': 30} for engine - Add _apply_pragma() method with PRAGMA settings - Call _apply_pragma() during initialize() Closes #8056 --- astrbot/core/db/sqlite.py | 2 ++ astrbot/core/db/vec_db/faiss_impl/document_storage.py | 11 +++++++++++ astrbot/core/knowledge_base/kb_db_sqlite.py | 9 ++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index d79ac9d703..dc3dfa3039 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -56,6 +56,8 @@ async def initialize(self) -> None: await conn.execute(text("PRAGMA cache_size=20000")) await conn.execute(text("PRAGMA temp_store=MEMORY")) await conn.execute(text("PRAGMA mmap_size=134217728")) + await conn.execute(text("PRAGMA busy_timeout=5000")) + await conn.execute(text("PRAGMA wal_autocheckpoint=500")) await conn.execute(text("PRAGMA optimize")) # 确保 personas 表有 folder_id、sort_order、skills 列(前向兼容) await self._ensure_persona_folder_columns(conn) diff --git a/astrbot/core/db/vec_db/faiss_impl/document_storage.py b/astrbot/core/db/vec_db/faiss_impl/document_storage.py index d0310d750a..0288c7f26d 100644 --- a/astrbot/core/db/vec_db/faiss_impl/document_storage.py +++ b/astrbot/core/db/vec_db/faiss_impl/document_storage.py @@ -59,6 +59,7 @@ def __init__(self, db_path: str) -> None: async def initialize(self) -> None: """Initialize the SQLite database and create the documents table if it doesn't exist.""" await self.connect() + await self._apply_pragma() async with self.engine.begin() as conn: # type: ignore # Create tables using SQLModel await conn.run_sync(BaseDocModel.metadata.create_all) @@ -197,6 +198,7 @@ async def connect(self) -> None: self.DATABASE_URL, echo=False, future=True, + connect_args={"timeout": 30}, ) self.async_session_maker = sessionmaker( self.engine, # type: ignore @@ -204,6 +206,15 @@ async def connect(self) -> None: expire_on_commit=False, ) # type: ignore + async def _apply_pragma(self) -> None: + """Apply SQLite PRAGMAs for performance and concurrency.""" + async with self.engine.begin() as conn: + await conn.execute(text("PRAGMA journal_mode=WAL")) + await conn.execute(text("PRAGMA synchronous=NORMAL")) + await conn.execute(text("PRAGMA busy_timeout=5000")) + await conn.execute(text("PRAGMA wal_autocheckpoint=500")) + await conn.commit() + @asynccontextmanager async def get_session(self): """Context manager for database sessions.""" diff --git a/astrbot/core/knowledge_base/kb_db_sqlite.py b/astrbot/core/knowledge_base/kb_db_sqlite.py index 6a2cb5e0a8..925ceaf794 100644 --- a/astrbot/core/knowledge_base/kb_db_sqlite.py +++ b/astrbot/core/knowledge_base/kb_db_sqlite.py @@ -42,6 +42,7 @@ def __init__(self, db_path: str | None = None) -> None: echo=False, pool_pre_ping=True, pool_recycle=3600, + connect_args={"timeout": 30}, ) # 创建会话工厂 @@ -68,15 +69,13 @@ async def initialize(self) -> None: async with self.engine.begin() as conn: # 创建所有知识库相关表 await conn.run_sync(BaseKBModel.metadata.create_all) - - # 配置 SQLite 性能优化参数 + # 配置 SQLite 参数以优化并发性能 await conn.execute(text("PRAGMA journal_mode=WAL")) await conn.execute(text("PRAGMA synchronous=NORMAL")) await conn.execute(text("PRAGMA cache_size=20000")) + await conn.execute(text("PRAGMA busy_timeout=5000")) + await conn.execute(text("PRAGMA wal_autocheckpoint=500")) await conn.execute(text("PRAGMA temp_store=MEMORY")) - await conn.execute(text("PRAGMA mmap_size=134217728")) - await conn.execute(text("PRAGMA optimize")) - await conn.commit() self.inited = True