Skip to content

Commit 494b01b

Browse files
committed
perf(core): implement Redis metadata caching and connection pooling
1 parent 8fe54b0 commit 494b01b

1 file changed

Lines changed: 64 additions & 18 deletions

File tree

packages/common/src/utils/connection.manager.js

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,82 @@ const { getPublicIp } = require("./network");
33
const Project = require("../models/Project");
44
const { decrypt } = require("./encryption");
55
const mongoose = require("mongoose");
6-
6+
const redis = require("../config/redis");
77

88
async function getConnection(projectId) {
99
const key = projectId.toString();
1010

11+
// 1. Instant Cache Hit (Fastest Path for UX)
1112
if (registry.has(key)) {
1213
const cachedConn = registry.get(key);
1314
if (cachedConn.readyState === 1) {
14-
cachedConn.lastAccessed = new Date();
15+
cachedConn.lastAccessed = new Date(); // Update access timestamp instantly
1516
return cachedConn;
1617
}
1718
}
1819

19-
const projectDoc = await Project.findById(projectId)
20-
.select("+resources.db.config.encrypted +resources.db.config.iv +resources.db.config.tag resources.db.isExternal");
20+
let dbUri;
21+
let plan = 'free'; // default
2122

22-
if (!projectDoc) throw new Error("Project not found");
23+
// 2. Redis Cache Check (Saves Main DB load and Decryption CPU cycles)
24+
const redisKey = `project:uri:${key}`;
25+
const cachedDataStr = await redis.get(redisKey);
2326

24-
if (!projectDoc.resources.db.isExternal) {
25-
return mongoose.connection;
27+
if (cachedDataStr) {
28+
try {
29+
const cachedData = JSON.parse(cachedDataStr);
30+
dbUri = cachedData.dbUri;
31+
plan = cachedData.plan || 'free';
32+
} catch (e) {
33+
console.error("Failed to parse cached project data from Redis", e);
34+
}
2635
}
2736

28-
let config;
29-
try {
30-
const decryptedConfig = decrypt(projectDoc.resources.db.config);
31-
config = JSON.parse(decryptedConfig);
32-
} catch (err) {
33-
console.error("Decryption Error:", err);
34-
throw new Error("Invalid or corrupted external config");
37+
// 3. Database Lookup & Decryption (Only runs if Redis is empty)
38+
if (!dbUri) {
39+
const projectDoc = await Project.findById(projectId)
40+
.select("+resources.db.config.encrypted +resources.db.config.iv +resources.db.config.tag resources.db.isExternal plan");
41+
42+
if (!projectDoc) throw new Error("Project not found");
43+
44+
if (!projectDoc.resources.db.isExternal) {
45+
return mongoose.connection;
46+
}
47+
48+
try {
49+
const decryptedConfig = decrypt(projectDoc.resources.db.config);
50+
dbUri = JSON.parse(decryptedConfig).dbUri;
51+
plan = projectDoc.plan || 'free';
52+
} catch (err) {
53+
console.error("Decryption Error:", err);
54+
throw new Error("Invalid or corrupted external config");
55+
}
56+
57+
// Save to Redis for 1 Hour so subsequent server processes can grab it instantly
58+
await redis.set(redisKey, JSON.stringify({ dbUri, plan }), 'EX', 3600);
3559
}
3660

37-
const connection = mongoose.createConnection(config.dbUri);
61+
// 4. ENTERPRISE CONFIGURATION FIX
62+
// We dynamically allocate pooling size depending on usage tiers to balance RAM & Throughput
63+
const isPremiumUser = plan === 'premium';
64+
65+
const connectionOptions = {
66+
maxPoolSize: isPremiumUser ? 50 : 15, // Cap free/hobby projects at 15 to prevent crashing your server
67+
minPoolSize: 2, // Keeps 2 warm sockets alive for instant response time
68+
maxIdleTimeMS: 15000, // Native driver automatically kills idle sockets after 15 seconds
69+
connectTimeoutMS: 5000, // Fail fast (5s) if user provides a bad/dead connection string
70+
socketTimeoutMS: 45000,
71+
waitQueueTimeoutMS: 5000 // Prevents requests from stalling forever if pool is exhausted
72+
};
73+
74+
// Initialize the pool with custom options
75+
const connection = mongoose.createConnection(dbUri, connectionOptions);
76+
77+
// Track active query load on this connection to prevent overflow crashes
78+
connection.lastAccessed = new Date();
3879

3980
connection.on("connected", () => {
40-
console.log(`✅ External DB connected: ${projectId}`);
81+
console.log(`✅ External DB pool opened for: ${projectId} (maxPoolSize: ${connectionOptions.maxPoolSize})`);
4182
});
4283

4384
try {
@@ -52,7 +93,12 @@ async function getConnection(projectId) {
5293
}
5394

5495
connection.on("error", (err) => {
55-
console.error("❌ Connection error [%s]:", projectId, err);
96+
console.error(`❌ DB Connection Error for project ${projectId}:`, err);
97+
registry.delete(key); // Clear bad connections immediately
98+
});
99+
100+
connection.on("disconnected", () => {
101+
console.log(`🔌 External DB disconnected: ${projectId}`);
56102
registry.delete(key);
57103
});
58104

@@ -61,7 +107,7 @@ async function getConnection(projectId) {
61107
console.log(`🔌 Connection closed: ${key}`);
62108
});
63109

64-
connection.lastAccessed = new Date();
110+
// Save back to your central in-memory store
65111
registry.set(key, connection);
66112

67113
return connection;

0 commit comments

Comments
 (0)