mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-09 08:26:16 +00:00
fix: Replace SSE with polling for WebSocket status to prevent connection pool exhaustion
- Replace persistent SSE connections with lightweight polling (10s interval) - Optimize WebSocket status fetching using bulk endpoint instead of N individual calls - Fix N+1 query problem in /dashboard/hosts endpoint (39 queries → 4 queries) - Increase database connection pool limit from 5 to 50 via environment variables - Increase Axios timeout from 10s to 30s for complex operations - Fix malformed WebSocket routes causing 404 on bulk status endpoint Fixes timeout issues when adding hosts with multiple WebSocket agents connected. Reduces database connections from 19 persistent SSE + retries to 1 poll every 10 seconds.
This commit is contained in:
@@ -193,11 +193,16 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get hosts with their update status
|
||||
// Get hosts with their update status - OPTIMIZED
|
||||
router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => {
|
||||
try {
|
||||
// Get settings once (outside the loop)
|
||||
const settings = await prisma.settings.findFirst();
|
||||
const updateIntervalMinutes = settings?.update_interval || 60;
|
||||
const thresholdMinutes = updateIntervalMinutes * 2;
|
||||
|
||||
// Fetch hosts with groups
|
||||
const hosts = await prisma.hosts.findMany({
|
||||
// Show all hosts regardless of status
|
||||
select: {
|
||||
id: true,
|
||||
machine_id: true,
|
||||
@@ -223,61 +228,65 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
host_packages: {
|
||||
where: {
|
||||
needs_update: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { last_update: "desc" },
|
||||
});
|
||||
|
||||
// Get update counts for each host separately
|
||||
const hostsWithUpdateInfo = await Promise.all(
|
||||
hosts.map(async (host) => {
|
||||
const updatesCount = await prisma.host_packages.count({
|
||||
where: {
|
||||
host_id: host.id,
|
||||
needs_update: true,
|
||||
},
|
||||
});
|
||||
// OPTIMIZATION: Get all package counts in 2 batch queries instead of N*2 queries
|
||||
const hostIds = hosts.map((h) => h.id);
|
||||
|
||||
// Get total packages count for this host
|
||||
const totalPackagesCount = await prisma.host_packages.count({
|
||||
where: {
|
||||
host_id: host.id,
|
||||
},
|
||||
});
|
||||
|
||||
// Get the agent update interval setting for stale calculation
|
||||
const settings = await prisma.settings.findFirst();
|
||||
const updateIntervalMinutes = settings?.update_interval || 60;
|
||||
const thresholdMinutes = updateIntervalMinutes * 2;
|
||||
|
||||
// Calculate effective status based on reporting interval
|
||||
const isStale = moment(host.last_update).isBefore(
|
||||
moment().subtract(thresholdMinutes, "minutes"),
|
||||
);
|
||||
let effectiveStatus = host.status;
|
||||
|
||||
// Override status if host hasn't reported within threshold
|
||||
if (isStale && host.status === "active") {
|
||||
effectiveStatus = "inactive";
|
||||
}
|
||||
|
||||
return {
|
||||
...host,
|
||||
updatesCount,
|
||||
totalPackagesCount,
|
||||
isStale,
|
||||
effectiveStatus,
|
||||
};
|
||||
const [updateCounts, totalCounts] = await Promise.all([
|
||||
// Get update counts for all hosts at once
|
||||
prisma.host_packages.groupBy({
|
||||
by: ["host_id"],
|
||||
where: {
|
||||
host_id: { in: hostIds },
|
||||
needs_update: true,
|
||||
},
|
||||
_count: { id: true },
|
||||
}),
|
||||
// Get total counts for all hosts at once
|
||||
prisma.host_packages.groupBy({
|
||||
by: ["host_id"],
|
||||
where: {
|
||||
host_id: { in: hostIds },
|
||||
},
|
||||
_count: { id: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Create lookup maps for O(1) access
|
||||
const updateCountMap = new Map(
|
||||
updateCounts.map((item) => [item.host_id, item._count.id]),
|
||||
);
|
||||
const totalCountMap = new Map(
|
||||
totalCounts.map((item) => [item.host_id, item._count.id]),
|
||||
);
|
||||
|
||||
// Process hosts with counts from maps (no more DB queries!)
|
||||
const hostsWithUpdateInfo = hosts.map((host) => {
|
||||
const updatesCount = updateCountMap.get(host.id) || 0;
|
||||
const totalPackagesCount = totalCountMap.get(host.id) || 0;
|
||||
|
||||
// Calculate effective status based on reporting interval
|
||||
const isStale = moment(host.last_update).isBefore(
|
||||
moment().subtract(thresholdMinutes, "minutes"),
|
||||
);
|
||||
let effectiveStatus = host.status;
|
||||
|
||||
// Override status if host hasn't reported within threshold
|
||||
if (isStale && host.status === "active") {
|
||||
effectiveStatus = "inactive";
|
||||
}
|
||||
|
||||
return {
|
||||
...host,
|
||||
updatesCount,
|
||||
totalPackagesCount,
|
||||
isStale,
|
||||
effectiveStatus,
|
||||
};
|
||||
});
|
||||
|
||||
res.json(hostsWithUpdateInfo);
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,7 +11,31 @@ const {
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get WebSocket connection status by api_id (no database access - pure memory lookup)
|
||||
// Get WebSocket connection status for multiple hosts at once (bulk endpoint)
|
||||
router.get("/status", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { apiIds } = req.query; // Comma-separated list of api_ids
|
||||
const idArray = apiIds ? apiIds.split(",").filter((id) => id.trim()) : [];
|
||||
|
||||
const statusMap = {};
|
||||
idArray.forEach((apiId) => {
|
||||
statusMap[apiId] = getConnectionInfo(apiId);
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: statusMap,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching bulk WebSocket status:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Failed to fetch WebSocket status",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get WebSocket connection status by api_id (single endpoint)
|
||||
router.get("/status/:apiId", authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { apiId } = req.params;
|
||||
|
||||
Reference in New Issue
Block a user