mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-13 18:36:01 +00:00
perf: Optimize packages endpoint - reduce from N*3 queries to 3 batch queries
- Replace individual queries per package with batch GROUP BY queries - Reduces from potentially hundreds of queries to just 3 queries total - Creates lookup maps for O(1) access when assembling results - Improves packages page loading time significantly
This commit is contained in:
@@ -101,37 +101,41 @@ router.get("/", async (req, res) => {
|
|||||||
prisma.packages.count({ where }),
|
prisma.packages.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Get additional stats for each package
|
// OPTIMIZATION: Batch query all stats instead of N individual queries
|
||||||
const packagesWithStats = await Promise.all(
|
const packageIds = packages.map((pkg) => pkg.id);
|
||||||
packages.map(async (pkg) => {
|
|
||||||
// Build base where clause for this package
|
|
||||||
const baseWhere = { package_id: pkg.id };
|
|
||||||
|
|
||||||
// If host filter is specified, add host filter to all queries
|
// Get all counts and host data in 3 batch queries instead of N*3 queries
|
||||||
const hostWhere = host ? { ...baseWhere, host_id: host } : baseWhere;
|
const [allUpdatesCounts, allSecurityCounts, allPackageHostsData] =
|
||||||
|
await Promise.all([
|
||||||
const [updatesCount, securityCount, packageHosts] = await Promise.all([
|
// Batch count all packages that need updates
|
||||||
prisma.host_packages.count({
|
prisma.host_packages.groupBy({
|
||||||
|
by: ["package_id"],
|
||||||
where: {
|
where: {
|
||||||
...hostWhere,
|
package_id: { in: packageIds },
|
||||||
needs_update: true,
|
needs_update: true,
|
||||||
|
...(host ? { host_id: host } : {}),
|
||||||
},
|
},
|
||||||
|
_count: { id: true },
|
||||||
}),
|
}),
|
||||||
prisma.host_packages.count({
|
// Batch count all packages with security updates
|
||||||
|
prisma.host_packages.groupBy({
|
||||||
|
by: ["package_id"],
|
||||||
where: {
|
where: {
|
||||||
...hostWhere,
|
package_id: { in: packageIds },
|
||||||
needs_update: true,
|
needs_update: true,
|
||||||
is_security_update: true,
|
is_security_update: true,
|
||||||
|
...(host ? { host_id: host } : {}),
|
||||||
},
|
},
|
||||||
|
_count: { id: true },
|
||||||
}),
|
}),
|
||||||
|
// Batch fetch all host data for packages
|
||||||
prisma.host_packages.findMany({
|
prisma.host_packages.findMany({
|
||||||
where: {
|
where: {
|
||||||
...hostWhere,
|
package_id: { in: packageIds },
|
||||||
// If host filter is specified, include all packages for that host
|
...(host ? { host_id: host } : { needs_update: true }),
|
||||||
// Otherwise, only include packages that need updates
|
|
||||||
...(host ? {} : { needs_update: true }),
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
package_id: true,
|
||||||
hosts: {
|
hosts: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -145,14 +149,27 @@ router.get("/", async (req, res) => {
|
|||||||
needs_update: true,
|
needs_update: true,
|
||||||
is_security_update: true,
|
is_security_update: true,
|
||||||
},
|
},
|
||||||
take: 10, // Limit to first 10 for performance
|
// Limit to first 10 per package
|
||||||
|
take: 100, // Increased from package-based limit
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
// Create lookup maps for O(1) access
|
||||||
...pkg,
|
const updatesCountMap = new Map(
|
||||||
packageHostsCount: pkg._count.host_packages,
|
allUpdatesCounts.map((item) => [item.package_id, item._count.id]),
|
||||||
packageHosts: packageHosts.map((hp) => ({
|
);
|
||||||
|
const securityCountMap = new Map(
|
||||||
|
allSecurityCounts.map((item) => [item.package_id, item._count.id]),
|
||||||
|
);
|
||||||
|
const packageHostsMap = new Map();
|
||||||
|
|
||||||
|
// Group host data by package_id
|
||||||
|
for (const hp of allPackageHostsData) {
|
||||||
|
if (!packageHostsMap.has(hp.package_id)) {
|
||||||
|
packageHostsMap.set(hp.package_id, []);
|
||||||
|
}
|
||||||
|
const hosts = packageHostsMap.get(hp.package_id);
|
||||||
|
hosts.push({
|
||||||
hostId: hp.hosts.id,
|
hostId: hp.hosts.id,
|
||||||
friendlyName: hp.hosts.friendly_name,
|
friendlyName: hp.hosts.friendly_name,
|
||||||
osType: hp.hosts.os_type,
|
osType: hp.hosts.os_type,
|
||||||
@@ -160,15 +177,31 @@ router.get("/", async (req, res) => {
|
|||||||
availableVersion: hp.available_version,
|
availableVersion: hp.available_version,
|
||||||
needsUpdate: hp.needs_update,
|
needsUpdate: hp.needs_update,
|
||||||
isSecurityUpdate: hp.is_security_update,
|
isSecurityUpdate: hp.is_security_update,
|
||||||
})),
|
});
|
||||||
|
|
||||||
|
// Limit to 10 hosts per package
|
||||||
|
if (hosts.length > 10) {
|
||||||
|
packageHostsMap.set(hp.package_id, hosts.slice(0, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map packages with stats from lookup maps (no more DB queries!)
|
||||||
|
const packagesWithStats = packages.map((pkg) => {
|
||||||
|
const updatesCount = updatesCountMap.get(pkg.id) || 0;
|
||||||
|
const securityCount = securityCountMap.get(pkg.id) || 0;
|
||||||
|
const packageHosts = packageHostsMap.get(pkg.id) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pkg,
|
||||||
|
packageHostsCount: pkg._count.host_packages,
|
||||||
|
packageHosts,
|
||||||
stats: {
|
stats: {
|
||||||
totalInstalls: pkg._count.host_packages,
|
totalInstalls: pkg._count.host_packages,
|
||||||
updatesNeeded: updatesCount,
|
updatesNeeded: updatesCount,
|
||||||
securityUpdates: securityCount,
|
securityUpdates: securityCount,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
packages: packagesWithStats,
|
packages: packagesWithStats,
|
||||||
|
|||||||
Reference in New Issue
Block a user