Add in-memory caching strategy for aggregated queries with TTL and automatic invalidation on data changes
This commit is contained in:
1 parent
a4206b9a0f
commit
a4295d8081
4 files changed
+110
-5
No files matched your search
@@ -0,0 +1,54 @@
|
||||
// Simple in-memory cache with TTL support
|
||||
class CacheManager {
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
set(key, value, ttlSeconds = 300) {
|
||||
const expiresAt = Date.now() + (ttlSeconds * 1000);
|
||||
this.cache.set(key, { value, expiresAt });
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const item = this.cache.get(key);
|
||||
if (!item) return null;
|
||||
|
||||
if (Date.now() > item.expiresAt) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return item.value;
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this.get(key) !== null;
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
// Invalidate all cache keys matching a pattern
|
||||
invalidatePattern(pattern) {
|
||||
for (const key of this.cache.keys()) {
|
||||
if (key.includes(pattern)) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get cache statistics
|
||||
getStats() {
|
||||
return {
|
||||
size: this.cache.size,
|
||||
keys: Array.from(this.cache.keys()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CacheManager();
|
||||
@@ -100,6 +100,10 @@ try {
|
||||
db.prepare("ALTER TABLE resources ADD COLUMN sla_target REAL DEFAULT 99.9").run();
|
||||
} catch (err) {}
|
||||
|
||||
try {
|
||||
db.prepare("ALTER TABLE resources ADD COLUMN email_to TEXT").run();
|
||||
} catch (err) {}
|
||||
|
||||
try {
|
||||
db.prepare("ALTER TABLE incidents ADD COLUMN acknowledged INTEGER DEFAULT 0").run();
|
||||
} catch (err) {}
|
||||
|
||||
+45
-2
@@ -7,6 +7,7 @@ const db = require('./database');
|
||||
const scheduler = require('./scheduler');
|
||||
const monitorService = require('./monitorService');
|
||||
const notificationService = require('./notificationService');
|
||||
const cache = require('./cache');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
@@ -99,6 +100,10 @@ app.post('/api/resources', (req, res) => {
|
||||
email_to || null
|
||||
);
|
||||
|
||||
// Invalidate related cache entries
|
||||
cache.invalidatePattern('history:');
|
||||
cache.invalidatePattern('sla:');
|
||||
|
||||
res.json({ id: result.lastInsertRowid, message: 'Resource created' });
|
||||
});
|
||||
|
||||
@@ -129,12 +134,22 @@ app.put('/api/resources/:id', (req, res) => {
|
||||
email_to || null,
|
||||
req.params.id
|
||||
);
|
||||
|
||||
// Invalidate related cache entries
|
||||
cache.invalidatePattern('history:');
|
||||
cache.invalidatePattern('sla:');
|
||||
|
||||
res.json({ message: 'Resource updated' });
|
||||
});
|
||||
|
||||
// Delete resource
|
||||
app.delete('/api/resources/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM resources WHERE id = ?').run(req.params.id);
|
||||
|
||||
// Invalidate related cache entries
|
||||
cache.invalidatePattern('history:');
|
||||
cache.invalidatePattern('sla:');
|
||||
|
||||
res.json({ message: 'Resource deleted' });
|
||||
});
|
||||
|
||||
@@ -331,6 +346,15 @@ app.get('/api/history/overview', (req, res) => {
|
||||
const currentPage = Math.max(1, parseInt(pageParam || page || 1));
|
||||
const offset = (currentPage - 1) * pageLimit;
|
||||
|
||||
// Create cache key based on query parameters
|
||||
const cacheKey = `history:days=${days}:page=${currentPage}:limit=${pageLimit}`;
|
||||
|
||||
// Check cache first
|
||||
const cachedResult = cache.get(cacheKey);
|
||||
if (cachedResult) {
|
||||
return res.json(cachedResult);
|
||||
}
|
||||
|
||||
// Get total count of enabled resources
|
||||
const totalCount = db.prepare('SELECT COUNT(*) as count FROM resources WHERE enabled = 1').get();
|
||||
const total = totalCount.count;
|
||||
@@ -377,7 +401,12 @@ app.get('/api/history/overview', (req, res) => {
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ resources: overview, total, page: currentPage, limit: pageLimit });
|
||||
const result = { resources: overview, total, page: currentPage, limit: pageLimit };
|
||||
|
||||
// Cache result for 2 minutes (120 seconds)
|
||||
cache.set(cacheKey, result, 120);
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
// Acknowledge incident
|
||||
@@ -407,6 +436,15 @@ app.get('/api/sla', (req, res) => {
|
||||
const currentPage = Math.max(1, parseInt(page));
|
||||
const offset = (currentPage - 1) * pageLimit;
|
||||
|
||||
// Create cache key
|
||||
const cacheKey = `sla:days=${days}:page=${currentPage}:limit=${pageLimit}`;
|
||||
|
||||
// Check cache first
|
||||
const cachedResult = cache.get(cacheKey);
|
||||
if (cachedResult) {
|
||||
return res.json(cachedResult);
|
||||
}
|
||||
|
||||
// Get total count
|
||||
const totalCount = db.prepare('SELECT COUNT(*) as count FROM resources WHERE enabled = 1').get();
|
||||
const total = totalCount.count;
|
||||
@@ -448,7 +486,12 @@ app.get('/api/sla', (req, res) => {
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ resources: slaData, total, page: currentPage, limit: pageLimit });
|
||||
const result = { resources: slaData, total, page: currentPage, limit: pageLimit };
|
||||
|
||||
// Cache result for 3 minutes (180 seconds)
|
||||
cache.set(cacheKey, result, 180);
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
// Test email
|
||||
|
||||
@@ -69,7 +69,8 @@ class NotificationService {
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (this.emailEnabled) {
|
||||
// Use resource-specific email if provided, otherwise fall back to global config
|
||||
if (this.emailEnabled && (resource.email_to || this.config.email_to)) {
|
||||
promises.push(this.sendEmail(resource, message, incident.type, stats));
|
||||
}
|
||||
|
||||
@@ -141,14 +142,17 @@ class NotificationService {
|
||||
|
||||
htmlContent += `</div>`;
|
||||
|
||||
// Use resource-specific email if provided, otherwise use global config
|
||||
const emailTo = resource.email_to || this.config.email_to;
|
||||
|
||||
await this.transporter.sendMail({
|
||||
from: this.config.email_from,
|
||||
to: this.config.email_to,
|
||||
to: emailTo,
|
||||
subject: `Alert: ${resource.name} is ${type === 'started' ? 'DOWN' : 'UP'}`,
|
||||
text: message,
|
||||
html: htmlContent,
|
||||
});
|
||||
console.log(`Email sent for ${resource.name}`);
|
||||
console.log(`Email sent for ${resource.name} to ${emailTo}`);
|
||||
} catch (error) {
|
||||
console.error('Email error:', error.message);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user