Performance: add database indexes and optimize history queries with aggregation and pagination
This commit is contained in:
1 parent
285b94692b
commit
dd7f3aeeda
2 files changed
+46
-18
No files matched your search
@@ -57,7 +57,9 @@ db.exec(`
|
|||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_checks_resource_id ON checks(resource_id);
|
CREATE INDEX IF NOT EXISTS idx_checks_resource_id ON checks(resource_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_checks_checked_at ON checks(checked_at);
|
CREATE INDEX IF NOT EXISTS idx_checks_checked_at ON checks(checked_at);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_checks_resource_checked ON checks(resource_id, checked_at DESC);
|
||||||
CREATE INDEX IF NOT EXISTS idx_incidents_resource_id ON incidents(resource_id);
|
CREATE INDEX IF NOT EXISTS idx_incidents_resource_id ON incidents(resource_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_incidents_resolved ON incidents(resource_id, resolved_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_resources_group_id ON resources(group_id);
|
CREATE INDEX IF NOT EXISTS idx_resources_group_id ON resources(group_id);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|||||||
+44
-18
@@ -276,16 +276,25 @@ TIMEOUT=${timeout}
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get historical check data for a resource
|
// Get historical check data for a resource (paginated)
|
||||||
app.get('/api/resources/:id/history', (req, res) => {
|
app.get('/api/resources/:id/history', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { days = 7 } = req.query;
|
const { days = 7, page = 1, limit = 100 } = req.query;
|
||||||
|
const pageNum = Math.max(1, parseInt(page));
|
||||||
|
const pageLimit = Math.min(500, Math.max(10, parseInt(limit)));
|
||||||
|
const offset = (pageNum - 1) * pageLimit;
|
||||||
|
|
||||||
const resource = db.prepare('SELECT * FROM resources WHERE id = ?').get(id);
|
const resource = db.prepare('SELECT * FROM resources WHERE id = ?').get(id);
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return res.status(404).json({ error: 'Resource not found' });
|
return res.status(404).json({ error: 'Resource not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get total count
|
||||||
|
const countResult = db.prepare(`
|
||||||
|
SELECT COUNT(*) as total FROM checks
|
||||||
|
WHERE resource_id = ? AND checked_at > datetime('now', ?)
|
||||||
|
`).get(id, `-${days} days`);
|
||||||
|
|
||||||
const checks = db.prepare(`
|
const checks = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
@@ -297,44 +306,61 @@ app.get('/api/resources/:id/history', (req, res) => {
|
|||||||
checked_at
|
checked_at
|
||||||
FROM checks
|
FROM checks
|
||||||
WHERE resource_id = ? AND checked_at > datetime('now', ?)
|
WHERE resource_id = ? AND checked_at > datetime('now', ?)
|
||||||
ORDER BY checked_at ASC
|
ORDER BY checked_at DESC
|
||||||
`).all(id, `-${days} days`);
|
LIMIT ? OFFSET ?
|
||||||
|
`).all(id, `-${days} days`, pageLimit, offset);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
resource,
|
resource,
|
||||||
checks,
|
checks: checks.reverse(), // Reverse to get ASC order for charting
|
||||||
count: checks.length,
|
pagination: {
|
||||||
|
page: pageNum,
|
||||||
|
limit: pageLimit,
|
||||||
|
total: countResult.total,
|
||||||
|
totalPages: Math.ceil(countResult.total / pageLimit),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all resources' check history for dashboard
|
// Get all resources' check history for dashboard (optimized with aggregation)
|
||||||
app.get('/api/history/overview', (req, res) => {
|
app.get('/api/history/overview', (req, res) => {
|
||||||
const { days = 7 } = req.query;
|
const { days = 7, limit = 12 } = req.query; // Only return last 12 checks per resource for charting
|
||||||
|
|
||||||
const resources = db.prepare('SELECT * FROM resources WHERE enabled = 1 ORDER BY name').all();
|
const resources = db.prepare('SELECT * FROM resources WHERE enabled = 1 ORDER BY name').all();
|
||||||
|
|
||||||
const overview = resources.map(resource => {
|
const overview = resources.map(resource => {
|
||||||
const checks = db.prepare(`
|
// Use aggregation query to get stats without loading all rows
|
||||||
|
const stats = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total,
|
||||||
|
SUM(CASE WHEN status = 'up' THEN 1 ELSE 0 END) as up_count,
|
||||||
|
AVG(response_time) as avg_response,
|
||||||
|
MIN(response_time) as min_response,
|
||||||
|
MAX(response_time) as max_response
|
||||||
|
FROM checks
|
||||||
|
WHERE resource_id = ? AND checked_at > datetime('now', ?)
|
||||||
|
`).get(resource.id, `-${days} days`);
|
||||||
|
|
||||||
|
const uptime = stats.total > 0 ? (stats.up_count / stats.total * 100) : 0;
|
||||||
|
const avgResponseTime = stats.avg_response || 0;
|
||||||
|
|
||||||
|
// Only load recent checks for sparklines
|
||||||
|
const recentChecks = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
status,
|
status,
|
||||||
response_time,
|
response_time,
|
||||||
checked_at
|
checked_at
|
||||||
FROM checks
|
FROM checks
|
||||||
WHERE resource_id = ? AND checked_at > datetime('now', ?)
|
WHERE resource_id = ? AND checked_at > datetime('now', ?)
|
||||||
ORDER BY checked_at ASC
|
ORDER BY checked_at DESC
|
||||||
`).all(resource.id, `-${days} days`);
|
LIMIT ?
|
||||||
|
`).all(resource.id, `-${days} days`, parseInt(limit));
|
||||||
const upCount = checks.filter(c => c.status === 'up').length;
|
|
||||||
const uptime = checks.length > 0 ? (upCount / checks.length * 100) : 0;
|
|
||||||
const avgResponseTime = checks.length > 0
|
|
||||||
? checks.reduce((sum, c) => sum + (c.response_time || 0), 0) / checks.length
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: resource.id,
|
id: resource.id,
|
||||||
name: resource.name,
|
name: resource.name,
|
||||||
type: resource.type,
|
type: resource.type,
|
||||||
checks,
|
checks: recentChecks.reverse(), // For charting in chronological order
|
||||||
uptime: uptime.toFixed(2),
|
uptime: uptime.toFixed(2),
|
||||||
avgResponseTime: avgResponseTime.toFixed(0),
|
avgResponseTime: avgResponseTime.toFixed(0),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user