cache improvement solves #219

This commit is contained in:
Jan Prochazka 2022-02-10 17:39:47 +01:00
parent 4e221ecd3a
commit b3b7bd0f83
5 changed files with 108 additions and 92 deletions

View File

@ -48,7 +48,7 @@ module.exports = {
async write({ data }) {
const fileName = path.join(datadir(), 'query-history.jsonl');
await fs.appendFile(fileName, JSON.stringify(data) + '\n');
socket.emitChanged('query-history-changed');
socket.emit('query-history-changed');
return 'OK';
},
};

View File

@ -23,7 +23,7 @@ module.exports = {
}
},
emitChanged(key) {
this.emit('clean-cache', key);
this.emit(key);
this.emit('changed-cache', key);
// this.emit(key);
},
};

View File

@ -1,11 +1,11 @@
import resolveApi, { resolveApiHeaders } from './resolveApi';
import { writable } from 'svelte/store';
import { cacheClean } from './cache';
// import { cacheClean } from './cache';
import getElectron from './getElectron';
// import socket from './socket';
let eventSource;
let cacheCleanerRegistered;
// let cacheCleanerRegistered;
function wantEventSource() {
if (!eventSource) {
@ -60,10 +60,10 @@ export function apiOn(event: string, handler: Function) {
eventSource.addEventListener(event, apiHandlers.get(handler));
}
if (!cacheCleanerRegistered) {
cacheCleanerRegistered = true;
apiOn('clean-cache', reloadTrigger => cacheClean(reloadTrigger));
}
// if (!cacheCleanerRegistered) {
// cacheCleanerRegistered = true;
// apiOn('clean-cache', reloadTrigger => cacheClean(reloadTrigger));
// }
}
export function apiOff(event: string, handler: Function) {

View File

@ -1,40 +1,120 @@
import { apiOn } from './api';
import getAsArray from './getAsArray';
let cachedByKey = {};
let cachedPromisesByKey = {};
const cachedByKey = {};
const cachedPromisesByKey = {};
const cachedKeysByReloadTrigger = {};
const subscriptionsByReloadTrigger = {};
const cacheGenerationByKey = {};
export function cacheGet(key) {
let cacheGeneration = 0;
function cacheGet(key) {
return cachedByKey[key];
}
export function cacheSet(key, value, reloadTrigger) {
cachedByKey[key] = value;
function addCacheKeyToReloadTrigger(cacheKey, reloadTrigger) {
for (const item of getAsArray(reloadTrigger)) {
if (!(item in cachedKeysByReloadTrigger)) {
cachedKeysByReloadTrigger[item] = [];
}
cachedKeysByReloadTrigger[item].push(key);
cachedKeysByReloadTrigger[item].push(cacheKey);
}
delete cachedPromisesByKey[key];
}
export function cacheClean(reloadTrigger) {
function cacheSet(cacheKey, value, reloadTrigger, generation) {
cachedByKey[cacheKey] = value;
addCacheKeyToReloadTrigger(cacheKey, reloadTrigger);
delete cachedPromisesByKey[cacheKey];
cacheGenerationByKey[cacheKey] = generation;
}
function cacheClean(reloadTrigger) {
cacheGeneration += 1;
for (const item of getAsArray(reloadTrigger)) {
const keys = cachedKeysByReloadTrigger[item];
if (keys) {
for (const key of keys) {
delete cachedByKey[key];
delete cachedPromisesByKey[key];
cacheGenerationByKey[key] = cacheGeneration;
}
}
delete cachedKeysByReloadTrigger[item];
}
}
export function getCachedPromise(key, func) {
if (key in cachedPromisesByKey) return cachedPromisesByKey[key];
function getCachedPromise(reloadTrigger, cacheKey, func) {
if (cacheKey in cachedPromisesByKey) return cachedPromisesByKey[cacheKey];
const promise = func();
cachedPromisesByKey[key] = promise;
cachedPromisesByKey[cacheKey] = promise;
addCacheKeyToReloadTrigger(cacheKey, reloadTrigger);
return promise;
}
function acquireCacheGeneration() {
cacheGeneration += 1;
return cacheGeneration;
}
function getCacheGenerationForKey(cacheKey) {
return cacheGenerationByKey[cacheKey] || 0;
}
export async function loadCachedValue(reloadTrigger, cacheKey, func) {
const fromCache = cacheGet(cacheKey);
if (fromCache) {
return fromCache;
} else {
const generation = acquireCacheGeneration();
try {
const res = await getCachedPromise(reloadTrigger, cacheKey, func);
if (getCacheGenerationForKey(cacheKey) > generation) {
return cacheGet(cacheKey) || res;
} else {
cacheSet(cacheKey, res, reloadTrigger, generation);
return res;
}
} catch (err) {
console.error('Error when using cached promise', err);
cacheClean(cacheKey);
const res = await func();
cacheSet(cacheKey, res, reloadTrigger, generation);
return res;
}
}
}
export async function subscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
for (const item of getAsArray(reloadTrigger)) {
if (!subscriptionsByReloadTrigger[item]) {
subscriptionsByReloadTrigger[item] = [];
}
subscriptionsByReloadTrigger[item].push(reloadHandler);
}
}
export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
for (const item of getAsArray(reloadTrigger)) {
if (subscriptionsByReloadTrigger[item]) {
subscriptionsByReloadTrigger[item] = subscriptionsByReloadTrigger[item].filter(x => x != reloadHandler);
}
if (subscriptionsByReloadTrigger[item].length == 0) {
delete subscriptionsByReloadTrigger[item];
}
}
}
function dispatchCacheChange(reloadTrigger) {
cacheClean(reloadTrigger);
for (const item of getAsArray(reloadTrigger)) {
if (subscriptionsByReloadTrigger[item]) {
for (const handler of subscriptionsByReloadTrigger[item]) {
handler();
}
}
}
}
apiOn('changed-cache', reloadTrigger => dispatchCacheChange(reloadTrigger));

View File

@ -1,7 +1,6 @@
import _ from 'lodash';
import { cacheGet, cacheSet, getCachedPromise } from './cache';
import { loadCachedValue, subscribeCacheChange, unsubscribeCacheChange } from './cache';
import stableStringify from 'json-stable-stringify';
import { cacheClean } from './cache';
import getAsArray from './getAsArray';
import { DatabaseInfo } from 'dbgate-types';
import { derived } from 'svelte/store';
@ -123,7 +122,7 @@ const appFilesLoader = ({ folder }) => ({
const usedAppsLoader = ({ conid, database }) => ({
url: 'apps/get-used-apps',
params: { },
params: {},
reloadTrigger: `used-apps-changed`,
});
@ -172,11 +171,7 @@ async function getCore(loader, args) {
return res;
}
const fromCache = cacheGet(key);
if (fromCache) return fromCache;
const res = await getCachedPromise(key, doLoad);
cacheSet(key, res, reloadTrigger);
const res = await loadCachedValue(reloadTrigger, key, doLoad);
return res;
}
@ -187,77 +182,20 @@ function useCore(loader, args) {
return {
subscribe: onChange => {
async function handleReload() {
async function doLoad() {
const resp = await apiCall(url, params);
const res = (transform || (x => x))(resp);
if (onLoaded) onLoaded(res);
return res;
}
if (cacheKey) {
const fromCache = cacheGet(cacheKey);
if (fromCache) {
onChange(fromCache);
} else {
try {
const res = await getCachedPromise(cacheKey, doLoad);
cacheSet(cacheKey, res, reloadTrigger);
onChange(res);
} catch (err) {
console.error(`Error when using cached promise ${url}`, err);
cacheClean(cacheKey);
const res = await doLoad();
cacheSet(cacheKey, res, reloadTrigger);
onChange(res);
}
}
} else {
const res = await doLoad();
onChange(res);
}
const res = await getCore(loader, args);
onChange(res);
}
// if (reloadTrigger && !socket) {
// console.error('Socket not available, reloadTrigger not planned');
// }
handleReload();
if (reloadTrigger) {
for (const item of getAsArray(reloadTrigger)) {
apiOn(item, handleReload);
}
subscribeCacheChange(reloadTrigger, cacheKey, handleReload);
return () => {
for (const item of getAsArray(reloadTrigger)) {
apiOff(item, handleReload);
}
unsubscribeCacheChange(reloadTrigger, cacheKey, handleReload);
};
}
},
};
// const useTrack = track => ({
// subscribe: onChange => {
// onChange('TRACK ' + track);
// if (track) {
// const handle = setInterval(() => onChange('TRACK ' + track + ';' + new Date()), 1000);
// // console.log("ON", track);
// const oldTrack = track;
// return () => {
// clearInterval(handle);
// // console.log("OFF", oldTrack);
// };
// }
// },
// });
// const res = useFetch({
// url,
// params,
// reloadTrigger,
// cacheKey,
// transform,
// });
// return res;
}
/** @returns {Promise<import('dbgate-types').DatabaseInfo>} */
@ -439,8 +377,6 @@ export function useAppFolders(args = {}) {
return useCore(appFoldersLoader, args);
}
export function getUsedApps(args = {}) {
return getCore(usedAppsLoader, args);
}