mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-11-07 00:02:54 +08:00
Merge pull request #2768 from langbot-app/fix/sqlite-startup-wal-checkpoint
fix(sqlite): reclaim startup WAL and ship patched SQLite
This commit is contained in:
@@ -6,6 +6,19 @@ import sqlalchemy.ext.asyncio as sqlalchemy_asyncio
|
||||
from .. import database
|
||||
|
||||
|
||||
WAL_RETAIN_BYTES = 64 * 1024 * 1024
|
||||
|
||||
|
||||
def _configure_connection(dbapi_connection, _connection_record):
|
||||
# This limit applies when a WAL resets, not while a transaction or reader
|
||||
# still needs its frames. Keep SQLite's automatic checkpoint policy.
|
||||
cursor = dbapi_connection.cursor()
|
||||
try:
|
||||
cursor.execute(f'PRAGMA journal_size_limit={WAL_RETAIN_BYTES}')
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
|
||||
@database.manager_class('sqlite')
|
||||
class SQLiteDatabaseManager(database.BaseDatabaseManager):
|
||||
"""SQLite database manager"""
|
||||
@@ -14,6 +27,7 @@ class SQLiteDatabaseManager(database.BaseDatabaseManager):
|
||||
db_file_path = self.ap.instance_config.data.get('database', {}).get('sqlite', {}).get('path', 'data/langbot.db')
|
||||
engine_url = f'sqlite+aiosqlite:///{db_file_path}'
|
||||
self.engine = sqlalchemy_asyncio.create_async_engine(engine_url)
|
||||
sqlalchemy.event.listen(self.engine.sync_engine, 'connect', _configure_connection)
|
||||
# Reader transactions must not block independent plugin callback commits.
|
||||
# Configure the persistent journal before serving requests, not per write.
|
||||
async with self.engine.connect() as connection:
|
||||
|
||||
@@ -198,6 +198,12 @@ class PersistenceManager:
|
||||
return
|
||||
|
||||
await self._initialize_managed_schema()
|
||||
from .sqlite_startup import checkpoint_after_migration
|
||||
|
||||
if engine.dialect.name == 'sqlite':
|
||||
self.ap.logger.info('Completing SQLite startup WAL checkpoint before starting producers...')
|
||||
await checkpoint_after_migration(engine)
|
||||
self.ap.logger.info('SQLite startup WAL checkpoint completed.')
|
||||
await self._enable_sqlite_foreign_keys_after_migration()
|
||||
from .resource_urls import install
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Finish SQLite WAL maintenance before application producers start."""
|
||||
|
||||
STARTUP_BUSY_TIMEOUT_MS = 60_000
|
||||
|
||||
|
||||
async def checkpoint_after_migration(engine):
|
||||
"""Require a complete WAL checkpoint, including on a retry at schema head."""
|
||||
if engine.dialect.name != 'sqlite':
|
||||
return
|
||||
async with engine.connect() as connection:
|
||||
connection = await connection.execution_options(isolation_level='AUTOCOMMIT')
|
||||
if (await connection.exec_driver_sql('PRAGMA journal_mode')).scalar_one().lower() != 'wal':
|
||||
return
|
||||
timeout = (await connection.exec_driver_sql('PRAGMA busy_timeout')).scalar_one()
|
||||
try:
|
||||
await connection.exec_driver_sql(f'PRAGMA busy_timeout={STARTUP_BUSY_TIMEOUT_MS}')
|
||||
busy, frames, checkpointed = (
|
||||
await connection.exec_driver_sql('PRAGMA main.wal_checkpoint(TRUNCATE)')
|
||||
).one()
|
||||
if (busy, frames, checkpointed) != (0, 0, 0):
|
||||
raise RuntimeError(
|
||||
'SQLite startup WAL checkpoint did not complete: '
|
||||
f'busy={busy}, frames={frames}, checkpointed={checkpointed}. '
|
||||
'Stop other database users and restart LangBot to retry.'
|
||||
)
|
||||
finally:
|
||||
await connection.exec_driver_sql(f'PRAGMA busy_timeout={timeout}')
|
||||
Reference in New Issue
Block a user