Skip to content

Commit ce85557

Browse files
Yerazeclaude
andauthored
fix: use bulk query for getAllNodes to prevent PG pool exhaustion (#2404)
* fix: use bulk query for getAllNodes to prevent PG pool exhaustion Two callers in server.ts used meshtasticManager.getAllNodes() which fires one telemetry query per node via Promise.all. With 216+ nodes this saturates the PostgreSQL connection pool (default 10), causing timeouts. Switch to getAllNodesAsync() which uses a single bulk query for all node uptimes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove N+1 getAllNodes method, keep only bulk getAllNodesAsync Remove the old getAllNodes() that fired one telemetry query per node. Update test mocks to use getAllNodesAsync. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 72ec14e commit ce85557

File tree

3 files changed

+11
-19
lines changed

3 files changed

+11
-19
lines changed

src/server/meshtasticManager.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10870,14 +10870,6 @@ class MeshtasticManager {
1087010870
return dbNodes.map(node => this.mapDbNodeToDeviceInfo(node, uptimeMap.get(node.nodeId)));
1087110871
}
1087210872

10873-
// Get data from database instead of maintaining in-memory state
10874-
async getAllNodes(): Promise<DeviceInfo[]> {
10875-
const dbNodes = await databaseService.nodes.getAllNodes();
10876-
return await Promise.all(dbNodes.map(async node => {
10877-
const uptimeTelemetry = await databaseService.telemetry.getLatestTelemetryForType(node.nodeId, 'uptimeSeconds');
10878-
return this.mapDbNodeToDeviceInfo(node, uptimeTelemetry?.value);
10879-
}));
10880-
}
1088110873

1088210874
// Shared mapping logic for converting a DB node to DeviceInfo
1088310875
private mapDbNodeToDeviceInfo(node: any, uptimeSeconds?: number): DeviceInfo {

src/server/server.estimatedposition.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
1111
};
1212

1313
meshtasticManager = {
14-
getAllNodes: vi.fn()
14+
getAllNodesAsync: vi.fn()
1515
};
1616
});
1717

@@ -28,7 +28,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
2828
position: null
2929
};
3030

31-
meshtasticManager.getAllNodes.mockReturnValue([nodeWithoutPosition]);
31+
meshtasticManager.getAllNodesAsync.mockReturnValue([nodeWithoutPosition]);
3232

3333
// Mock telemetry with estimated position
3434
databaseService.getTelemetryByNode.mockReturnValue([
@@ -55,7 +55,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
5555
]);
5656

5757
// Act - Simulate the enhancement logic from server.ts
58-
const nodes = meshtasticManager.getAllNodes();
58+
const nodes = meshtasticManager.getAllNodesAsync();
5959
const enhancedNodes = nodes.map((node: any) => {
6060
if (!node.user?.id) return { ...node, isMobile: false };
6161

@@ -99,7 +99,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
9999
}
100100
};
101101

102-
meshtasticManager.getAllNodes.mockReturnValue([nodeWithPosition]);
102+
meshtasticManager.getAllNodesAsync.mockReturnValue([nodeWithPosition]);
103103

104104
// Mock telemetry with estimated position
105105
databaseService.getTelemetryByNode.mockReturnValue([
@@ -126,7 +126,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
126126
]);
127127

128128
// Act - Simulate the enhancement logic
129-
const nodes = meshtasticManager.getAllNodes();
129+
const nodes = meshtasticManager.getAllNodesAsync();
130130
const enhancedNodes = nodes.map((node: any) => {
131131
if (!node.user?.id) return { ...node, isMobile: false };
132132

@@ -166,7 +166,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
166166
position: null
167167
};
168168

169-
meshtasticManager.getAllNodes.mockReturnValue([nodeWithoutPosition]);
169+
meshtasticManager.getAllNodesAsync.mockReturnValue([nodeWithoutPosition]);
170170

171171
// Mock telemetry without estimated position
172172
databaseService.getTelemetryByNode.mockReturnValue([
@@ -182,7 +182,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
182182
]);
183183

184184
// Act
185-
const nodes = meshtasticManager.getAllNodes();
185+
const nodes = meshtasticManager.getAllNodesAsync();
186186
const enhancedNodes = nodes.map((node: any) => {
187187
if (!node.user?.id) return { ...node, isMobile: false };
188188

@@ -220,7 +220,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
220220
position: null
221221
};
222222

223-
meshtasticManager.getAllNodes.mockReturnValue([nodeWithoutPosition]);
223+
meshtasticManager.getAllNodesAsync.mockReturnValue([nodeWithoutPosition]);
224224

225225
// Mock telemetry with multiple estimated positions (most recent first)
226226
const now = Date.now();
@@ -268,7 +268,7 @@ describe('Server /api/nodes endpoint - Estimated Position Integration', () => {
268268
]);
269269

270270
// Act
271-
const nodes = meshtasticManager.getAllNodes();
271+
const nodes = meshtasticManager.getAllNodesAsync();
272272
const enhancedNodes = nodes.map((node: any) => {
273273
if (!node.user?.id) return { ...node, isMobile: false };
274274

src/server/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ setSettingsCallbacks({
836836
*/
837837
apiRouter.get('/nodes', optionalAuth(), async (req, res) => {
838838
try {
839-
const allNodes = await meshtasticManager.getAllNodes();
839+
const allNodes = await meshtasticManager.getAllNodesAsync();
840840
const estimatedPositions = await databaseService.getAllNodesEstimatedPositionsAsync();
841841

842842
// Filter nodes based on channel read permissions
@@ -2150,7 +2150,7 @@ apiRouter.get('/messages/unread-counts', optionalAuth(), async (req, res) => {
21502150
// Get DM unread counts if user has messages permission (batch query)
21512151
if (hasMessagesRead && localNodeInfo) {
21522152
const allUnreadDMs = await databaseService.getBatchUnreadDMCountsAsync(localNodeInfo.nodeId, userId);
2153-
const allNodes = await meshtasticManager.getAllNodes();
2153+
const allNodes = await meshtasticManager.getAllNodesAsync();
21542154
const visibleNodes = await filterNodesByChannelPermission(allNodes, req.user);
21552155
const visibleNodeIds = new Set(visibleNodes.map(n => n.user?.id).filter(Boolean));
21562156
const directMessages: { [nodeId: string]: number } = {};

0 commit comments

Comments
 (0)