cluster-shared-cache
Advanced tools
+375
| const cluster = require('cluster'); | ||
| const EventEmitter = require('events'); | ||
| class ClusterSharedCache extends EventEmitter { | ||
| constructor(options = {}) { | ||
| super(); | ||
| this.options = { | ||
| maxSize: options.maxSize || 1000, | ||
| ttl: options.ttl || 300000, | ||
| checkPeriod: options.checkPeriod || 60000, | ||
| ...options | ||
| }; | ||
| this.cache = new Map(); | ||
| this.timers = new Map(); | ||
| this.setupIPC(); | ||
| this.startCleanup(); | ||
| } | ||
| setupIPC() { | ||
| if (cluster.isMaster) { | ||
| this.masterCache = new Map(); | ||
| this.masterTimers = new Map(); | ||
| cluster.on('message', (worker, message) => { | ||
| if (message.type && message.type.startsWith('CACHE_')) { | ||
| this.handleMasterMessage(worker, message); | ||
| } | ||
| }); | ||
| this.on('masterCacheUpdate', (data) => { | ||
| Object.values(cluster.workers).forEach(worker => { | ||
| if (worker && worker.send) { | ||
| worker.send(data); | ||
| } | ||
| }); | ||
| }); | ||
| } else { | ||
| process.on('message', (message) => { | ||
| if (message.type && message.type.startsWith('CACHE_')) { | ||
| this.handleWorkerMessage(message); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| handleMasterMessage(worker, message) { | ||
| const { type, key, value, ttl, requestId } = message; | ||
| switch (type) { | ||
| case 'CACHE_SET': | ||
| this.masterSet(key, value, ttl); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_SET', | ||
| key, | ||
| value, | ||
| ttl, | ||
| timestamp: Date.now() | ||
| }); | ||
| break; | ||
| case 'CACHE_GET': | ||
| const result = this.masterGet(key); | ||
| worker.send({ | ||
| type: 'CACHE_GET_RESPONSE', | ||
| requestId, | ||
| key, | ||
| value: result | ||
| }); | ||
| break; | ||
| case 'CACHE_DELETE': | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| break; | ||
| case 'CACHE_CLEAR': | ||
| this.masterClear(); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_CLEAR' | ||
| }); | ||
| break; | ||
| } | ||
| } | ||
| handleWorkerMessage(message) { | ||
| const { type, key, value, ttl, timestamp, requestId } = message; | ||
| switch (type) { | ||
| case 'CACHE_SYNC_SET': | ||
| this.localSet(key, value, ttl, timestamp); | ||
| break; | ||
| case 'CACHE_SYNC_DELETE': | ||
| this.localDelete(key); | ||
| break; | ||
| case 'CACHE_SYNC_CLEAR': | ||
| this.localClear(); | ||
| break; | ||
| case 'CACHE_GET_RESPONSE': | ||
| this.emit(`get_response_${requestId}`, value); | ||
| break; | ||
| } | ||
| } | ||
| // Master cache operations | ||
| masterSet(key, value, ttl) { | ||
| if (this.masterCache.size >= this.options.maxSize) { | ||
| const firstKey = this.masterCache.keys().next().value; | ||
| this.masterDelete(firstKey); | ||
| } | ||
| const expiresAt = ttl ? Date.now() + ttl : null; | ||
| this.masterCache.set(key, { value, expiresAt }); | ||
| if (ttl) { | ||
| if (this.masterTimers.has(key)) { | ||
| clearTimeout(this.masterTimers.get(key)); | ||
| } | ||
| const timer = setTimeout(() => { | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| }, ttl); | ||
| this.masterTimers.set(key, timer); | ||
| } | ||
| } | ||
| masterGet(key) { | ||
| const item = this.masterCache.get(key); | ||
| if (!item) return undefined; | ||
| if (item.expiresAt && Date.now() > item.expiresAt) { | ||
| this.masterDelete(key); | ||
| return undefined; | ||
| } | ||
| return item.value; | ||
| } | ||
| masterDelete(key) { | ||
| this.masterCache.delete(key); | ||
| if (this.masterTimers.has(key)) { | ||
| clearTimeout(this.masterTimers.get(key)); | ||
| this.masterTimers.delete(key); | ||
| } | ||
| } | ||
| masterClear() { | ||
| this.masterCache.clear(); | ||
| this.masterTimers.forEach(timer => clearTimeout(timer)); | ||
| this.masterTimers.clear(); | ||
| } | ||
| localSet(key, value, ttl, timestamp) { | ||
| if (this.cache.size >= this.options.maxSize) { | ||
| const firstKey = this.cache.keys().next().value; | ||
| this.localDelete(firstKey); | ||
| } | ||
| const expiresAt = ttl ? (timestamp || Date.now()) + ttl : null; | ||
| this.cache.set(key, { value, expiresAt }); | ||
| if (ttl) { | ||
| if (this.timers.has(key)) { | ||
| clearTimeout(this.timers.get(key)); | ||
| } | ||
| const remainingTtl = expiresAt - Date.now(); | ||
| if (remainingTtl > 0) { | ||
| const timer = setTimeout(() => { | ||
| this.localDelete(key); | ||
| }, remainingTtl); | ||
| this.timers.set(key, timer); | ||
| } | ||
| } | ||
| } | ||
| localDelete(key) { | ||
| this.cache.delete(key); | ||
| if (this.timers.has(key)) { | ||
| clearTimeout(this.timers.get(key)); | ||
| this.timers.delete(key); | ||
| } | ||
| } | ||
| localClear() { | ||
| this.cache.clear(); | ||
| this.timers.forEach(timer => clearTimeout(timer)); | ||
| this.timers.clear(); | ||
| } | ||
| async set(key, value, ttl = this.options.ttl) { | ||
| if (cluster.isMaster) { | ||
| this.masterSet(key, value, ttl); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_SET', | ||
| key, | ||
| value, | ||
| ttl, | ||
| timestamp: Date.now() | ||
| }); | ||
| } else { | ||
| process.send({ | ||
| type: 'CACHE_SET', | ||
| key, | ||
| value, | ||
| ttl | ||
| }); | ||
| } | ||
| } | ||
| async get(key) { | ||
| if (cluster.isMaster) { | ||
| return this.masterGet(key); | ||
| } else { | ||
| const localItem = this.cache.get(key); | ||
| if (localItem) { | ||
| if (localItem.expiresAt && Date.now() > localItem.expiresAt) { | ||
| this.localDelete(key); | ||
| } else { | ||
| return localItem.value; | ||
| } | ||
| } | ||
| const requestId = `${Date.now()}_${Math.random()}`; | ||
| return new Promise((resolve) => { | ||
| const timeout = setTimeout(() => { | ||
| this.removeListener(`get_response_${requestId}`, responseHandler); | ||
| resolve(undefined); | ||
| }, 5000); | ||
| const responseHandler = (value) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| }; | ||
| this.once(`get_response_${requestId}`, responseHandler); | ||
| process.send({ | ||
| type: 'CACHE_GET', | ||
| key, | ||
| requestId | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| async delete(key) { | ||
| if (cluster.isMaster) { | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| } else { | ||
| process.send({ | ||
| type: 'CACHE_DELETE', | ||
| key | ||
| }); | ||
| } | ||
| } | ||
| async clear() { | ||
| if (cluster.isMaster) { | ||
| this.masterClear(); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_CLEAR' | ||
| }); | ||
| } else { | ||
| process.send({ | ||
| type: 'CACHE_CLEAR' | ||
| }); | ||
| } | ||
| } | ||
| has(key) { | ||
| const item = this.cache.get(key); | ||
| if (!item) return false; | ||
| if (item.expiresAt && Date.now() > item.expiresAt) { | ||
| this.localDelete(key); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| size() { | ||
| return this.cache.size; | ||
| } | ||
| keys() { | ||
| return Array.from(this.cache.keys()); | ||
| } | ||
| values() { | ||
| return Array.from(this.cache.values()).map(item => item.value); | ||
| } | ||
| entries() { | ||
| return Array.from(this.cache.entries()).map(([key, item]) => [key, item.value]); | ||
| } | ||
| startCleanup() { | ||
| if (this.cleanupInterval) { | ||
| clearInterval(this.cleanupInterval); | ||
| } | ||
| this.cleanupInterval = setInterval(() => { | ||
| const now = Date.now(); | ||
| const expiredKeys = []; | ||
| for (const [key, item] of this.cache.entries()) { | ||
| if (item.expiresAt && now > item.expiresAt) { | ||
| expiredKeys.push(key); | ||
| } | ||
| } | ||
| expiredKeys.forEach(key => this.localDelete(key)); | ||
| // Master cleanup | ||
| if (cluster.isMaster && this.masterCache) { | ||
| const expiredMasterKeys = []; | ||
| for (const [key, item] of this.masterCache.entries()) { | ||
| if (item.expiresAt && now > item.expiresAt) { | ||
| expiredMasterKeys.push(key); | ||
| } | ||
| } | ||
| expiredMasterKeys.forEach(key => { | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| }); | ||
| } | ||
| }, this.options.checkPeriod); | ||
| } | ||
| destroy() { | ||
| if (this.cleanupInterval) { | ||
| clearInterval(this.cleanupInterval); | ||
| } | ||
| this.timers.forEach(timer => clearTimeout(timer)); | ||
| this.timers.clear(); | ||
| if (this.masterTimers) { | ||
| this.masterTimers.forEach(timer => clearTimeout(timer)); | ||
| this.masterTimers.clear(); | ||
| } | ||
| this.removeAllListeners(); | ||
| } | ||
| } | ||
| module.exports = ClusterSharedCache; |
@@ -1,2 +0,2 @@ | ||
| const ClusterSharedCache = require('../src/index'); | ||
| const ClusterSharedCache = require('../index'); | ||
| const cluster = require('cluster'); | ||
@@ -3,0 +3,0 @@ |
+1
-1
| { | ||
| "name": "cluster-shared-cache", | ||
| "version": "1.0.1", | ||
| "version": "1.0.2", | ||
| "description": "Handling shared in memory cache in node js cluster module", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
-375
| const cluster = require('cluster'); | ||
| const EventEmitter = require('events'); | ||
| class ClusterSharedCache extends EventEmitter { | ||
| constructor(options = {}) { | ||
| super(); | ||
| this.options = { | ||
| maxSize: options.maxSize || 1000, | ||
| ttl: options.ttl || 300000, | ||
| checkPeriod: options.checkPeriod || 60000, | ||
| ...options | ||
| }; | ||
| this.cache = new Map(); | ||
| this.timers = new Map(); | ||
| this.setupIPC(); | ||
| this.startCleanup(); | ||
| } | ||
| setupIPC() { | ||
| if (cluster.isMaster) { | ||
| this.masterCache = new Map(); | ||
| this.masterTimers = new Map(); | ||
| cluster.on('message', (worker, message) => { | ||
| if (message.type && message.type.startsWith('CACHE_')) { | ||
| this.handleMasterMessage(worker, message); | ||
| } | ||
| }); | ||
| this.on('masterCacheUpdate', (data) => { | ||
| Object.values(cluster.workers).forEach(worker => { | ||
| if (worker && worker.send) { | ||
| worker.send(data); | ||
| } | ||
| }); | ||
| }); | ||
| } else { | ||
| process.on('message', (message) => { | ||
| if (message.type && message.type.startsWith('CACHE_')) { | ||
| this.handleWorkerMessage(message); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| handleMasterMessage(worker, message) { | ||
| const { type, key, value, ttl, requestId } = message; | ||
| switch (type) { | ||
| case 'CACHE_SET': | ||
| this.masterSet(key, value, ttl); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_SET', | ||
| key, | ||
| value, | ||
| ttl, | ||
| timestamp: Date.now() | ||
| }); | ||
| break; | ||
| case 'CACHE_GET': | ||
| const result = this.masterGet(key); | ||
| worker.send({ | ||
| type: 'CACHE_GET_RESPONSE', | ||
| requestId, | ||
| key, | ||
| value: result | ||
| }); | ||
| break; | ||
| case 'CACHE_DELETE': | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| break; | ||
| case 'CACHE_CLEAR': | ||
| this.masterClear(); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_CLEAR' | ||
| }); | ||
| break; | ||
| } | ||
| } | ||
| handleWorkerMessage(message) { | ||
| const { type, key, value, ttl, timestamp, requestId } = message; | ||
| switch (type) { | ||
| case 'CACHE_SYNC_SET': | ||
| this.localSet(key, value, ttl, timestamp); | ||
| break; | ||
| case 'CACHE_SYNC_DELETE': | ||
| this.localDelete(key); | ||
| break; | ||
| case 'CACHE_SYNC_CLEAR': | ||
| this.localClear(); | ||
| break; | ||
| case 'CACHE_GET_RESPONSE': | ||
| this.emit(`get_response_${requestId}`, value); | ||
| break; | ||
| } | ||
| } | ||
| // Master cache operations | ||
| masterSet(key, value, ttl) { | ||
| if (this.masterCache.size >= this.options.maxSize) { | ||
| const firstKey = this.masterCache.keys().next().value; | ||
| this.masterDelete(firstKey); | ||
| } | ||
| const expiresAt = ttl ? Date.now() + ttl : null; | ||
| this.masterCache.set(key, { value, expiresAt }); | ||
| if (ttl) { | ||
| if (this.masterTimers.has(key)) { | ||
| clearTimeout(this.masterTimers.get(key)); | ||
| } | ||
| const timer = setTimeout(() => { | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| }, ttl); | ||
| this.masterTimers.set(key, timer); | ||
| } | ||
| } | ||
| masterGet(key) { | ||
| const item = this.masterCache.get(key); | ||
| if (!item) return undefined; | ||
| if (item.expiresAt && Date.now() > item.expiresAt) { | ||
| this.masterDelete(key); | ||
| return undefined; | ||
| } | ||
| return item.value; | ||
| } | ||
| masterDelete(key) { | ||
| this.masterCache.delete(key); | ||
| if (this.masterTimers.has(key)) { | ||
| clearTimeout(this.masterTimers.get(key)); | ||
| this.masterTimers.delete(key); | ||
| } | ||
| } | ||
| masterClear() { | ||
| this.masterCache.clear(); | ||
| this.masterTimers.forEach(timer => clearTimeout(timer)); | ||
| this.masterTimers.clear(); | ||
| } | ||
| localSet(key, value, ttl, timestamp) { | ||
| if (this.cache.size >= this.options.maxSize) { | ||
| const firstKey = this.cache.keys().next().value; | ||
| this.localDelete(firstKey); | ||
| } | ||
| const expiresAt = ttl ? (timestamp || Date.now()) + ttl : null; | ||
| this.cache.set(key, { value, expiresAt }); | ||
| if (ttl) { | ||
| if (this.timers.has(key)) { | ||
| clearTimeout(this.timers.get(key)); | ||
| } | ||
| const remainingTtl = expiresAt - Date.now(); | ||
| if (remainingTtl > 0) { | ||
| const timer = setTimeout(() => { | ||
| this.localDelete(key); | ||
| }, remainingTtl); | ||
| this.timers.set(key, timer); | ||
| } | ||
| } | ||
| } | ||
| localDelete(key) { | ||
| this.cache.delete(key); | ||
| if (this.timers.has(key)) { | ||
| clearTimeout(this.timers.get(key)); | ||
| this.timers.delete(key); | ||
| } | ||
| } | ||
| localClear() { | ||
| this.cache.clear(); | ||
| this.timers.forEach(timer => clearTimeout(timer)); | ||
| this.timers.clear(); | ||
| } | ||
| async set(key, value, ttl = this.options.ttl) { | ||
| if (cluster.isMaster) { | ||
| this.masterSet(key, value, ttl); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_SET', | ||
| key, | ||
| value, | ||
| ttl, | ||
| timestamp: Date.now() | ||
| }); | ||
| } else { | ||
| process.send({ | ||
| type: 'CACHE_SET', | ||
| key, | ||
| value, | ||
| ttl | ||
| }); | ||
| } | ||
| } | ||
| async get(key) { | ||
| if (cluster.isMaster) { | ||
| return this.masterGet(key); | ||
| } else { | ||
| const localItem = this.cache.get(key); | ||
| if (localItem) { | ||
| if (localItem.expiresAt && Date.now() > localItem.expiresAt) { | ||
| this.localDelete(key); | ||
| } else { | ||
| return localItem.value; | ||
| } | ||
| } | ||
| const requestId = `${Date.now()}_${Math.random()}`; | ||
| return new Promise((resolve) => { | ||
| const timeout = setTimeout(() => { | ||
| this.removeListener(`get_response_${requestId}`, responseHandler); | ||
| resolve(undefined); | ||
| }, 5000); | ||
| const responseHandler = (value) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| }; | ||
| this.once(`get_response_${requestId}`, responseHandler); | ||
| process.send({ | ||
| type: 'CACHE_GET', | ||
| key, | ||
| requestId | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| async delete(key) { | ||
| if (cluster.isMaster) { | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| } else { | ||
| process.send({ | ||
| type: 'CACHE_DELETE', | ||
| key | ||
| }); | ||
| } | ||
| } | ||
| async clear() { | ||
| if (cluster.isMaster) { | ||
| this.masterClear(); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_CLEAR' | ||
| }); | ||
| } else { | ||
| process.send({ | ||
| type: 'CACHE_CLEAR' | ||
| }); | ||
| } | ||
| } | ||
| has(key) { | ||
| const item = this.cache.get(key); | ||
| if (!item) return false; | ||
| if (item.expiresAt && Date.now() > item.expiresAt) { | ||
| this.localDelete(key); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| size() { | ||
| return this.cache.size; | ||
| } | ||
| keys() { | ||
| return Array.from(this.cache.keys()); | ||
| } | ||
| values() { | ||
| return Array.from(this.cache.values()).map(item => item.value); | ||
| } | ||
| entries() { | ||
| return Array.from(this.cache.entries()).map(([key, item]) => [key, item.value]); | ||
| } | ||
| startCleanup() { | ||
| if (this.cleanupInterval) { | ||
| clearInterval(this.cleanupInterval); | ||
| } | ||
| this.cleanupInterval = setInterval(() => { | ||
| const now = Date.now(); | ||
| const expiredKeys = []; | ||
| for (const [key, item] of this.cache.entries()) { | ||
| if (item.expiresAt && now > item.expiresAt) { | ||
| expiredKeys.push(key); | ||
| } | ||
| } | ||
| expiredKeys.forEach(key => this.localDelete(key)); | ||
| // Master cleanup | ||
| if (cluster.isMaster && this.masterCache) { | ||
| const expiredMasterKeys = []; | ||
| for (const [key, item] of this.masterCache.entries()) { | ||
| if (item.expiresAt && now > item.expiresAt) { | ||
| expiredMasterKeys.push(key); | ||
| } | ||
| } | ||
| expiredMasterKeys.forEach(key => { | ||
| this.masterDelete(key); | ||
| this.emit('masterCacheUpdate', { | ||
| type: 'CACHE_SYNC_DELETE', | ||
| key | ||
| }); | ||
| }); | ||
| } | ||
| }, this.options.checkPeriod); | ||
| } | ||
| destroy() { | ||
| if (this.cleanupInterval) { | ||
| clearInterval(this.cleanupInterval); | ||
| } | ||
| this.timers.forEach(timer => clearTimeout(timer)); | ||
| this.timers.clear(); | ||
| if (this.masterTimers) { | ||
| this.masterTimers.forEach(timer => clearTimeout(timer)); | ||
| this.masterTimers.clear(); | ||
| } | ||
| this.removeAllListeners(); | ||
| } | ||
| } | ||
| module.exports = ClusterSharedCache; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
12031
-0.03%1
Infinity%