mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-09 18:23:11 +02:00
perf: answer /api/status with one targeted query
/api/status is polled by every open page (on load, every 60s, and on visibility resume) and cost ~266-450ms in-container while doing no device I/O at all — check_connection() is just an attribute read, and /health returns in ~1ms, so all of it was SQLite. Two calls were over-fetching: - get_stats() ran COUNT(*) over 11 tables to answer a question about 2. COUNT(*) FROM echoes alone was ~200ms over 19k rows and the result was discarded. That table only grows, so the endpoint kept getting slower. - get_channel_messages(limit=1) was a SELECT * fetching every column, raw_packet included, to read one timestamp. ORDER BY timestamp has no usable index (idx_cm_channel_ts leads with channel_idx), so the plan was a full SCAN through two temp B-trees. MAX(timestamp) is served off that index as a covering scan instead. Replaced with Database.get_status_summary(): three scalar subqueries on one connection. ~371ms -> ~46ms for the DB work; endpoint median ~57ms in-container, ~78ms from the page. Response verified byte-identical. This does not address the multi-tab congestion — at one poll per tab per 60s /api/status was never that cause — but it removes a recurring cost and re-baselines the probe those measurements are taken with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1480,6 +1480,35 @@ class Database:
|
||||
# Maintenance
|
||||
# ================================================================
|
||||
|
||||
def get_status_summary(self) -> Dict[str, Any]:
|
||||
"""Message count + newest channel timestamp, for /api/status.
|
||||
|
||||
/api/status is polled by every open tab, so it gets a dedicated query
|
||||
instead of reusing get_stats() + get_channel_messages(limit=1):
|
||||
|
||||
- get_stats() counts 11 tables to answer a question about 2. The
|
||||
COUNT(*) over echoes alone costs ~200ms on a 20k-row table and its
|
||||
result was discarded — and echoes only ever grow.
|
||||
- get_channel_messages(limit=1) was a `SELECT *` whose ORDER BY
|
||||
timestamp has no usable index (idx_cm_channel_ts leads with
|
||||
channel_idx), so it scanned the table through two temp B-trees and
|
||||
materialized every column, raw_packet included, to read one field.
|
||||
MAX(timestamp) is served straight off that index instead.
|
||||
|
||||
Together with folding both into a single connection this is ~46ms
|
||||
where the pair cost ~371ms.
|
||||
"""
|
||||
with self._connect() as conn:
|
||||
row = conn.execute(
|
||||
"""SELECT (SELECT COUNT(*) FROM channel_messages) AS channel_count,
|
||||
(SELECT COUNT(*) FROM direct_messages) AS dm_count,
|
||||
(SELECT MAX(timestamp) FROM channel_messages) AS latest_timestamp"""
|
||||
).fetchone()
|
||||
return {
|
||||
'message_count': (row['channel_count'] or 0) + (row['dm_count'] or 0),
|
||||
'latest_message_timestamp': row['latest_timestamp'],
|
||||
}
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
"""Get row counts for all tables."""
|
||||
tables = ['device', 'contacts', 'channels', 'channel_messages',
|
||||
|
||||
Reference in New Issue
Block a user