MOBILE-3977 core: Optimize wscache table

main
Noel De Martin 2022-02-03 13:43:10 +01:00
parent a65919debc
commit 7a1dfa38bd
3 changed files with 95 additions and 44 deletions

View File

@ -41,6 +41,7 @@ import { CoreLogger } from '@singletons/logger';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreIonLoadingElement } from './ion-loading'; import { CoreIonLoadingElement } from './ion-loading';
import { CoreLang } from '@services/lang'; import { CoreLang } from '@services/lang';
import { CoreSites } from '@services/sites';
/** /**
* QR Code type enumeration. * QR Code type enumeration.
@ -920,20 +921,20 @@ export class CoreSite {
preSets: CoreSiteWSPreSets, preSets: CoreSiteWSPreSets,
emergency?: boolean, emergency?: boolean,
): Promise<T> { ): Promise<T> {
const db = this.db; if (!this.db || !preSets.getFromCache) {
if (!db || !preSets.getFromCache) {
throw new CoreError('Get from cache is disabled.'); throw new CoreError('Get from cache is disabled.');
} }
const id = this.getCacheId(method, data); const id = this.getCacheId(method, data);
const cacheTable = await CoreSites.getCacheTable(this);
let entry: CoreSiteWSCacheRecord | undefined; let entry: CoreSiteWSCacheRecord | undefined;
if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) { if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) {
const entries = await db.getRecords<CoreSiteWSCacheRecord>(CoreSite.WS_CACHE_TABLE, { key: preSets.cacheKey }); const entries = await cacheTable.all({ key: preSets.cacheKey });
if (!entries.length) { if (!entries.length) {
// Cache key not found, get by params sent. // Cache key not found, get by params sent.
entry = await db.getRecord(CoreSite.WS_CACHE_TABLE, { id }); entry = await cacheTable.findByPrimaryKey({ id });
} else { } else {
if (entries.length > 1) { if (entries.length > 1) {
// More than one entry found. Search the one with same ID as this call. // More than one entry found. Search the one with same ID as this call.
@ -945,7 +946,7 @@ export class CoreSite {
} }
} }
} else { } else {
entry = await db.getRecord(CoreSite.WS_CACHE_TABLE, { id }); entry = await cacheTable.findByPrimaryKey({ id });
} }
if (entry === undefined) { if (entry === undefined) {
@ -990,18 +991,25 @@ export class CoreSite {
*/ */
async getComponentCacheSize(component: string, componentId?: number): Promise<number> { async getComponentCacheSize(component: string, componentId?: number): Promise<number> {
const params: Array<string | number> = [component]; const params: Array<string | number> = [component];
const cacheTable = await CoreSites.getCacheTable(this);
let extraClause = ''; let extraClause = '';
if (componentId !== undefined && componentId !== null) { if (componentId !== undefined && componentId !== null) {
params.push(componentId); params.push(componentId);
extraClause = ' AND componentId = ?'; extraClause = ' AND componentId = ?';
} }
const size = <number> await this.getDb().getFieldSql( return cacheTable.reduce(
'SELECT SUM(length(data)) FROM ' + CoreSite.WS_CACHE_TABLE + ' WHERE component = ?' + extraClause, {
params, sql: 'SUM(length(data))',
js: (size, record) => size + record.data.length,
jsInitialValue: 0,
},
{
sql: 'WHERE component = ?' + extraClause,
sqlParams: params,
js: record => record.component === component && (params.length === 1 || record.componentId === componentId),
},
); );
return size;
} }
/** /**
@ -1015,10 +1023,6 @@ export class CoreSite {
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
protected async saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets): Promise<void> { protected async saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets): Promise<void> {
if (!this.db) {
throw new CoreError('Site DB not initialized.');
}
if (preSets.uniqueCacheKey) { if (preSets.uniqueCacheKey) {
// Cache key must be unique, delete all entries with same cache key. // Cache key must be unique, delete all entries with same cache key.
await CoreUtils.ignoreErrors(this.deleteFromCache(method, data, preSets, true)); await CoreUtils.ignoreErrors(this.deleteFromCache(method, data, preSets, true));
@ -1027,6 +1031,7 @@ export class CoreSite {
// Since 3.7, the expiration time contains the time the entry is modified instead of the expiration time. // Since 3.7, the expiration time contains the time the entry is modified instead of the expiration time.
// We decided to reuse this field to prevent modifying the database table. // We decided to reuse this field to prevent modifying the database table.
const id = this.getCacheId(method, data); const id = this.getCacheId(method, data);
const cacheTable = await CoreSites.getCacheTable(this);
const entry = { const entry = {
id, id,
data: JSON.stringify(response), data: JSON.stringify(response),
@ -1044,7 +1049,7 @@ export class CoreSite {
} }
} }
await this.db.insertRecord(CoreSite.WS_CACHE_TABLE, entry); await cacheTable.insert(entry);
} }
/** /**
@ -1058,16 +1063,13 @@ export class CoreSite {
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
protected async deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise<void> { protected async deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise<void> {
if (!this.db) {
throw new CoreError('Site DB not initialized.');
}
const id = this.getCacheId(method, data); const id = this.getCacheId(method, data);
const cacheTable = await CoreSites.getCacheTable(this);
if (allCacheKey) { if (allCacheKey) {
await this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { key: preSets.cacheKey }); await cacheTable.delete({ key: preSets.cacheKey });
} else { } else {
await this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { id }); await cacheTable.deleteByPrimaryKey({ id });
} }
} }
@ -1084,18 +1086,14 @@ export class CoreSite {
return; return;
} }
if (!this.db) { const params = { component };
throw new CoreError('Site DB not initialized'); const cacheTable = await CoreSites.getCacheTable(this);
}
const params = {
component,
};
if (componentId) { if (componentId) {
params['componentId'] = componentId; params['componentId'] = componentId;
} }
await this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, params); await cacheTable.delete(params);
} }
/* /*
@ -1127,14 +1125,12 @@ export class CoreSite {
* @return Promise resolved when the cache entries are invalidated. * @return Promise resolved when the cache entries are invalidated.
*/ */
async invalidateWsCache(): Promise<void> { async invalidateWsCache(): Promise<void> {
if (!this.db) {
throw new CoreError('Site DB not initialized');
}
this.logger.debug('Invalidate all the cache for site: ' + this.id); this.logger.debug('Invalidate all the cache for site: ' + this.id);
try { try {
await this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }); const cacheTable = await CoreSites.getCacheTable(this);
await cacheTable.update({ expirationTime: 0 });
} finally { } finally {
CoreEvents.trigger(CoreEvents.WS_CACHE_INVALIDATED, {}, this.getId()); CoreEvents.trigger(CoreEvents.WS_CACHE_INVALIDATED, {}, this.getId());
} }
@ -1147,16 +1143,15 @@ export class CoreSite {
* @return Promise resolved when the cache entries are invalidated. * @return Promise resolved when the cache entries are invalidated.
*/ */
async invalidateWsCacheForKey(key: string): Promise<void> { async invalidateWsCacheForKey(key: string): Promise<void> {
if (!this.db) {
throw new CoreError('Site DB not initialized');
}
if (!key) { if (!key) {
return; return;
} }
this.logger.debug('Invalidate cache for key: ' + key); this.logger.debug('Invalidate cache for key: ' + key);
await this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }, { key }); const cacheTable = await CoreSites.getCacheTable(this);
await cacheTable.update({ expirationTime: 0 }, { key });
} }
/** /**
@ -1184,18 +1179,19 @@ export class CoreSite {
* @return Promise resolved when the cache entries are invalidated. * @return Promise resolved when the cache entries are invalidated.
*/ */
async invalidateWsCacheForKeyStartingWith(key: string): Promise<void> { async invalidateWsCacheForKeyStartingWith(key: string): Promise<void> {
if (!this.db) {
throw new CoreError('Site DB not initialized');
}
if (!key) { if (!key) {
return; return;
} }
this.logger.debug('Invalidate cache for key starting with: ' + key); this.logger.debug('Invalidate cache for key starting with: ' + key);
const sql = 'UPDATE ' + CoreSite.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?'; const cacheTable = await CoreSites.getCacheTable(this);
await this.db.execute(sql, [key + '%']); await cacheTable.updateWhere({ expirationTime: 0 }, {
sql: 'key LIKE ?',
sqlParams: [key],
js: record => !!record.key?.startsWith(key),
});
} }
/** /**
@ -1270,9 +1266,13 @@ export class CoreSite {
* @return Promise resolved with the total size of all data in the cache table (bytes) * @return Promise resolved with the total size of all data in the cache table (bytes)
*/ */
async getCacheUsage(): Promise<number> { async getCacheUsage(): Promise<number> {
const size = <number> await this.getDb().getFieldSql('SELECT SUM(length(data)) FROM ' + CoreSite.WS_CACHE_TABLE); const cacheTable = await CoreSites.getCacheTable(this);
return size; return cacheTable.reduce({
sql: 'SUM(length(data))',
js: (size, record) => size + record.data.length,
jsInitialValue: 0,
});
} }
/** /**

View File

@ -15,11 +15,13 @@
import { CoreFilepool } from '@services/filepool'; import { CoreFilepool } from '@services/filepool';
import { CoreLang } from '@services/lang'; import { CoreLang } from '@services/lang';
import { CoreLocalNotifications } from '@services/local-notifications'; import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreSites } from '@services/sites';
import { CoreUpdateManager } from '@services/update-manager'; import { CoreUpdateManager } from '@services/update-manager';
export default async function(): Promise<void> { export default async function(): Promise<void> {
await Promise.all([ await Promise.all([
CoreFilepool.initialize(), CoreFilepool.initialize(),
CoreSites.initialize(),
CoreLang.initialize(), CoreLang.initialize(),
CoreLocalNotifications.initialize(), CoreLocalNotifications.initialize(),
CoreUpdateManager.initialize(), CoreUpdateManager.initialize(),

View File

@ -31,6 +31,7 @@ import {
CoreSiteConfig, CoreSiteConfig,
CoreSitePublicConfigResponse, CoreSitePublicConfigResponse,
CoreSiteInfoResponse, CoreSiteInfoResponse,
CoreSiteWSCacheRecord,
} from '@classes/site'; } from '@classes/site';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
@ -57,6 +58,9 @@ import { CoreErrorWithTitle } from '@classes/errors/errorwithtitle';
import { CoreAjaxError } from '@classes/errors/ajaxerror'; import { CoreAjaxError } from '@classes/errors/ajaxerror';
import { CoreAjaxWSError } from '@classes/errors/ajaxwserror'; import { CoreAjaxWSError } from '@classes/errors/ajaxwserror';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreDatabaseTable } from '@classes/database/database-table';
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS'); export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
@ -85,6 +89,7 @@ export class CoreSitesProvider {
// Variables for DB. // Variables for DB.
protected appDB: Promise<SQLiteDB>; protected appDB: Promise<SQLiteDB>;
protected resolveAppDB!: (appDB: SQLiteDB) => void; protected resolveAppDB!: (appDB: SQLiteDB) => void;
protected cacheTables: Record<string, CorePromisedValue<CoreDatabaseTable<CoreSiteWSCacheRecord>>> = {};
constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) { constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) {
this.appDB = new Promise(resolve => this.resolveAppDB = resolve); this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
@ -99,6 +104,23 @@ export class CoreSitesProvider {
); );
} }
/**
* Initialize.
*/
initialize(): void {
CoreEvents.on(CoreEvents.SITE_DELETED, async ({ siteId }) => {
if (!siteId || !(siteId in this.cacheTables)) {
return;
}
const cacheTable = await this.cacheTables[siteId];
delete this.cacheTables[siteId];
await cacheTable.destroy();
});
}
/** /**
* Initialize database. * Initialize database.
*/ */
@ -112,6 +134,33 @@ export class CoreSitesProvider {
this.resolveAppDB(CoreApp.getDB()); this.resolveAppDB(CoreApp.getDB());
} }
/**
* Get cache table.
*
* @param siteId Site id.
* @returns cache table.
*/
async getCacheTable(site: CoreSite): Promise<CoreDatabaseTable<CoreSiteWSCacheRecord>> {
if (!site.id) {
throw new CoreError('Can\'t get cache table for site without id');
}
if (!(site.id in this.cacheTables)) {
const promisedTable = this.cacheTables[site.id] = new CorePromisedValue();
const table = new CoreDatabaseTableProxy<CoreSiteWSCacheRecord>(
{ cachingStrategy: CoreDatabaseCachingStrategy.None },
site.getDb(),
CoreSite.WS_CACHE_TABLE,
);
await table.initialize();
promisedTable.resolve(table);
}
return this.cacheTables[site.id];
}
/** /**
* Get the demo data for a certain "name" if it is a demo site. * Get the demo data for a certain "name" if it is a demo site.
* *