-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(express): add lru cache for wallets in v2 /sendcoins
B2B2C clients exprience high latencies for get wallet requests. To mitigate, we will cache these requests TICKET: CS-4503
- Loading branch information
Showing
5 changed files
with
169 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
export interface LRUCacheOptions { | ||
// maximum number of records this cache can hold | ||
maxSize: number; | ||
|
||
// duration in milliseconds after which a record is considered expired | ||
ttl: number; | ||
} | ||
|
||
export default class LRUCache<K, V> { | ||
private cache: Map<K, { value: V; expiry: number }>; | ||
private readonly maxSize: number; | ||
private readonly ttl: number; | ||
|
||
/** | ||
* Creates a new LRU cache | ||
* @param options configurable options for this cache | ||
*/ | ||
constructor(options: LRUCacheOptions = { maxSize: 10, ttl: 60000 }) { | ||
this.cache = new Map(); | ||
this.maxSize = options.maxSize; | ||
this.ttl = options.ttl; | ||
} | ||
|
||
/** | ||
* Retrieves value from cache if it exists and has not expired | ||
* @param key key | ||
*/ | ||
get(key: K): V | undefined { | ||
const cacheItem = this.cache.get(key); | ||
if (cacheItem) { | ||
// If expired, delete the cache item | ||
if (Date.now() > cacheItem.expiry) { | ||
this.cache.delete(key); | ||
return undefined; | ||
} | ||
|
||
// Move the item to the end to mark it as recently used | ||
this.cache.delete(key); | ||
this.cache.set(key, cacheItem); | ||
return cacheItem.value; | ||
} | ||
return undefined; | ||
} | ||
|
||
/** | ||
* Adds a new item to the cache. | ||
* If the cache is full, it will try to evict expired items first. | ||
* If no expired items are found, it will evict the least recently used item. | ||
* | ||
* @param key key | ||
* @param value value | ||
*/ | ||
set(key: K, value: V): void { | ||
const expiry = Date.now() + this.ttl; | ||
|
||
if (this.cache.has(key)) { | ||
this.cache.delete(key); | ||
} | ||
|
||
// If cache is full, remove expired items, then remove the least recently used (LRU) item | ||
if (this.cache.size >= this.maxSize) { | ||
const numExpiredEvicted = this.cleanUpExpiredItems(); | ||
if (numExpiredEvicted === 0) { | ||
const firstKey = this.cache.keys().next().value; // Get the first key (LRU) | ||
this.cache.delete(firstKey); | ||
} | ||
} | ||
|
||
// Add the new item to the cache | ||
this.cache.set(key, { value, expiry }); | ||
} | ||
|
||
delete(key: K): boolean { | ||
return this.cache.delete(key); | ||
} | ||
|
||
// Check if an item has expired | ||
private cleanUpExpiredItems(): number { | ||
const now = Date.now(); | ||
|
||
let numDeleted = 0; | ||
for (const [key, { expiry }] of this.cache.entries()) { | ||
if (now > expiry) { | ||
this.cache.delete(key); | ||
numDeleted++; | ||
} | ||
} | ||
|
||
return numDeleted; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import * as should from 'should'; | ||
import LRUCache from '../../src/LRUCache'; | ||
|
||
describe('LRUCache', function () { | ||
let cache: LRUCache<string, string>; | ||
|
||
beforeEach(() => { | ||
cache = new LRUCache<string, string>({ | ||
maxSize: 3, | ||
ttl: 5000, | ||
}); | ||
}); | ||
|
||
it('should store and retrieve values correctly', () => { | ||
cache.set('a', 'apple'); | ||
cache.set('b', 'banana'); | ||
|
||
'apple'.should.equal(cache.get('a')); | ||
'banana'.should.equal(cache.get('b')); | ||
}); | ||
|
||
it('should return undefined for non-existing keys', () => { | ||
should.equal(cache.get('does-not-exist'), undefined); | ||
}); | ||
|
||
it('should evict the least recently used item when the cache exceeds the max size', () => { | ||
cache.set('a', 'apple'); | ||
cache.set('b', 'banana'); | ||
cache.set('c', 'cherry'); | ||
cache.set('d', 'date'); | ||
|
||
should.equal(cache.get('a'), undefined); | ||
'banana'.should.equal(cache.get('b')); | ||
'cherry'.should.equal(cache.get('c')); | ||
'date'.should.equal(cache.get('d')); | ||
}); | ||
}); |