add thumbnail data caching
This commit is contained in:
@ -68,6 +68,11 @@ type Migration = {
|
|||||||
statement: string;
|
statement: string;
|
||||||
};
|
};
|
||||||
const db: sqlite3.Database = new sqlite3.cached.Database('moshingmammut.db');
|
const db: sqlite3.Database = new sqlite3.cached.Database('moshingmammut.db');
|
||||||
|
const maxThumbnailCacheSize = 100;
|
||||||
|
const maxThumbnailCacheAge = 1 * 60 * 60 * 1000; //1h in ms
|
||||||
|
const thumbnailCache = new Map<string, { data: SongThumbnailImage[]; ts: number }>();
|
||||||
|
let thumbnailCacheHits = 0;
|
||||||
|
let thumbnailCacheMisses = 0;
|
||||||
|
|
||||||
export function close() {
|
export function close() {
|
||||||
try {
|
try {
|
||||||
@ -803,6 +808,7 @@ async function getPostsInternal(
|
|||||||
.flatMap((x) => x)
|
.flatMap((x) => x)
|
||||||
.map((x) => x.thumbnailUrl)
|
.map((x) => x.thumbnailUrl)
|
||||||
.filter((x) => x !== undefined)
|
.filter((x) => x !== undefined)
|
||||||
|
.filter((x) => getCachedThumbnail(x) === null)
|
||||||
.toArray();
|
.toArray();
|
||||||
if (turls) {
|
if (turls) {
|
||||||
const tMap = await getSongThumbnailData('?, '.repeat(turls.length).slice(0, -2), turls);
|
const tMap = await getSongThumbnailData('?, '.repeat(turls.length).slice(0, -2), turls);
|
||||||
@ -811,7 +817,11 @@ async function getPostsInternal(
|
|||||||
if (songInfo.thumbnailUrl === undefined) {
|
if (songInfo.thumbnailUrl === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const thumbs = tMap.get(songInfo.thumbnailUrl) ?? [];
|
let thumbs = getCachedThumbnail(songInfo.thumbnailUrl);
|
||||||
|
if (thumbs === null) {
|
||||||
|
thumbs = tMap.get(songInfo.thumbnailUrl) ?? [];
|
||||||
|
cacheThumbnail(songInfo.thumbnailUrl, thumbs);
|
||||||
|
}
|
||||||
songInfo.resizedThumbnails = thumbs;
|
songInfo.resizedThumbnails = thumbs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -969,6 +979,73 @@ export async function getSongThumbnails(song: SongInfo): Promise<SongThumbnailIm
|
|||||||
if (!song.thumbnailUrl) {
|
if (!song.thumbnailUrl) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const rows = await getSongThumbnailData('?', [song.thumbnailUrl]);
|
const cachedThumbnail = getCachedThumbnail(song.thumbnailUrl);
|
||||||
return rows.get(song.thumbnailUrl) ?? [];
|
if (cachedThumbnail !== null) {
|
||||||
|
return cachedThumbnail;
|
||||||
|
}
|
||||||
|
const rows = await getSongThumbnailData('?', [song.thumbnailUrl]);
|
||||||
|
const data = rows.get(song.thumbnailUrl) ?? [];
|
||||||
|
cacheThumbnail(song.thumbnailUrl, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedThumbnail(thumbnailUrl: string): SongThumbnailImage[] | null {
|
||||||
|
const cachedThumbnail = thumbnailCache.get(thumbnailUrl);
|
||||||
|
if (cachedThumbnail == undefined) {
|
||||||
|
thumbnailCacheMisses++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const age = new Date().getTime() - cachedThumbnail.ts;
|
||||||
|
if (age <= maxThumbnailCacheAge) {
|
||||||
|
thumbnailCacheHits++;
|
||||||
|
return cachedThumbnail.data;
|
||||||
|
}
|
||||||
|
thumbnailCache.delete(thumbnailUrl);
|
||||||
|
thumbnailCacheMisses++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheThumbnail(thumbnailUrl: string, thumbnails: SongThumbnailImage[]) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const initialSize = thumbnailCache.size;
|
||||||
|
if (initialSize >= maxThumbnailCacheSize) {
|
||||||
|
logger.debug('Sweeping cache. Current size', initialSize);
|
||||||
|
const deleteTimestampsOlderThan =
|
||||||
|
thumbnailCache
|
||||||
|
.values()
|
||||||
|
.map((x) => x.ts)
|
||||||
|
.toArray()
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.slice(0, 20)
|
||||||
|
.pop() ?? 0;
|
||||||
|
const timestampExpired = now - maxThumbnailCacheAge;
|
||||||
|
const threshold = Math.max(timestampExpired, deleteTimestampsOlderThan);
|
||||||
|
thumbnailCache.forEach((v, k) => {
|
||||||
|
if (v.ts <= threshold) {
|
||||||
|
thumbnailCache.delete(k);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Logger.isDebugEnabled()) {
|
||||||
|
logger.debug(
|
||||||
|
'Swept cache. New size',
|
||||||
|
thumbnailCache.size,
|
||||||
|
'deleted',
|
||||||
|
initialSize - thumbnailCache.size,
|
||||||
|
'entries'
|
||||||
|
);
|
||||||
|
logger.debug(
|
||||||
|
'Current hitrate',
|
||||||
|
thumbnailCacheHits,
|
||||||
|
'/',
|
||||||
|
thumbnailCacheHits + thumbnailCacheMisses,
|
||||||
|
'=',
|
||||||
|
((100.0 * thumbnailCacheHits) / (thumbnailCacheHits + thumbnailCacheMisses)).toFixed(2),
|
||||||
|
'%'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbnailCache.set(thumbnailUrl, {
|
||||||
|
data: thumbnails,
|
||||||
|
ts: now
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user