perf(monitoring): load execution details on demand

- Load overview first and fetch secondary sections when expanded
- Add a covering index and select conversation IDs before loading page payloads
- Preserve full-detail API compatibility and add pagination and lazy-loading tests
This commit is contained in:
BiFangKNT
2026-10-17 21:10:55 +08:00
parent b3574efd95
commit f12490bc73
10 changed files with 448 additions and 35 deletions
@@ -423,6 +423,99 @@ async def test_execution_detail_for_agent_and_pipeline(engine, service):
await service.get_execution_detail(WORKSPACE, 'pipeline', 'missing-message')
@pytest.mark.asyncio
async def test_detail_overview_does_not_query_collapsed_sections(engine, service, monkeypatch):
await _seed(engine)
page = service._execution_page
converters = []
async def capture(workspace, statement, convert, offset, limit):
converters.append(convert.__name__)
return await page(workspace, statement, convert, offset, limit)
async def reject_history(*args, **kwargs):
pytest.fail('Collapsed conversation history must not be loaded')
monkeypatch.setattr(service, '_execution_page', capture)
monkeypatch.setattr(service, '_conversation_message_page', reject_history)
detail = await service.get_execution_detail(WORKSPACE, 'pipeline', 'msg-user', section='overview')
assert set(detail['pages']) == {'inputs', 'outputs', 'deliveries', 'errors'}
assert detail['pages']['inputs']['items'][0]['id'] == 'msg-user'
assert len(converters) == 4
assert 'events' not in detail
@pytest.mark.asyncio
async def test_conversation_pages_identities_from_covering_index_before_payloads(engine, service):
import sqlalchemy as sa
await _seed(engine)
message = persistence_monitoring.MonitoringMessage
payload = 'historical inline media ' * 4096
async with engine.begin() as connection:
await connection.execute(
sa.insert(message),
[
dict(
id=f'history-{index:02d}',
workspace_uuid=workspace,
bot_id=bot,
bot_name='Bot',
pipeline_id='pipeline',
pipeline_name='Pipeline',
session_id='session-1',
timestamp=_dt(12),
role='user',
message_content=payload,
variables=payload,
status='success',
level='info',
)
for index, (workspace, bot) in enumerate(
[(WORKSPACE, 'bot-1')] * 5 + [(WORKSPACE, 'other-bot'), ('other-workspace', 'bot-1')]
)
],
)
statements = []
def capture(connection, cursor, statement, parameters, context, executemany):
if statement.startswith('SELECT'):
statements.append((statement, parameters))
sa.event.listen(engine.sync_engine, 'before_cursor_execute', capture)
try:
first = await service.get_execution_detail(WORKSPACE, 'pipeline', 'msg-user', section='conversation', limit=2)
second = await service.get_execution_detail(
WORKSPACE, 'pipeline', 'msg-user', section='conversation', limit=2, offset=2
)
finally:
sa.event.remove(engine.sync_engine, 'before_cursor_execute', capture)
page = first['pages']['conversation']
assert [row['id'] for row in page['items']] == ['history-04', 'history-03']
assert page['has_more'] and page['next_offset'] == 2
assert all(row['content'] == payload for row in page['items'])
assert [row['id'] for row in second['pages']['conversation']['items']] == ['history-02', 'history-01']
identities = [(sql, params) for sql, params in statements if sql.startswith('SELECT monitoring_messages.id \n')]
assert len(identities) == 2
async with engine.connect() as connection:
for sql, params in identities:
assert 'message_content' not in sql and 'variables' not in sql
plan = (await connection.exec_driver_sql('EXPLAIN QUERY PLAN ' + sql, params)).all()
assert any('COVERING INDEX ix_monitoring_messages_conversation_page' in row[3] for row in plan), plan
assert not any('TEMP B-TREE' in row[3] for row in plan), plan
payload_queries = [(sql, params) for sql, params in statements if 'JOIN monitoring_messages ON' in sql]
assert len(payload_queries) == 2
assert {value for value in payload_queries[0][1] if str(value).startswith('history-')} == {
'history-04',
'history-03',
}
for sql, params in payload_queries:
plan = (await connection.exec_driver_sql('EXPLAIN QUERY PLAN ' + sql, params)).all()
assert any('sqlite_autoindex_monitoring_messages_1 (id=?)' in row[3] for row in plan), plan
@pytest.mark.asyncio
async def test_explicit_deliveries_context_and_tenant_isolation(engine, service):
import sqlalchemy as sa
@@ -0,0 +1,42 @@
"""Conversation paging index is safe for existing and fresh databases."""
import importlib
import sqlalchemy as sa
from alembic.migration import MigrationContext
from alembic.operations import Operations
from langbot.pkg.entity.persistence.monitoring import MonitoringMessage
def test_conversation_index_migration_preserves_history_and_matches_metadata():
migration = importlib.import_module('langbot.pkg.persistence.alembic.versions.0040_conversation_page_index')
engine = sa.create_engine('sqlite:///:memory:')
metadata = sa.MetaData()
table = sa.Table(
'monitoring_messages',
metadata,
*[
sa.Column(name, sa.String, primary_key=name == 'id')
for name in ('id', 'workspace_uuid', 'bot_id', 'session_id', 'timestamp', 'message_content')
],
)
sa.Index('existing_index', table.c.workspace_uuid)
try:
with engine.begin() as connection:
metadata.create_all(connection)
connection.execute(table.insert().values(id='history', message_content='inline media'))
with Operations.context(MigrationContext.configure(connection)):
migration.upgrade()
migration.upgrade()
indexes = {
index['name']: index['column_names'] for index in sa.inspect(connection).get_indexes(table.name)
}
expected = next(index for index in MonitoringMessage.__table__.indexes if index.name == migration.INDEX)
assert indexes[migration.INDEX] == [column.name for column in expected.columns]
migration.downgrade()
assert {index['name'] for index in sa.inspect(connection).get_indexes(table.name)} == {'existing_index'}
assert connection.scalar(sa.select(table.c.message_content)) == 'inline media'
migration.upgrade()
finally:
engine.dispose()