From 7a1dfa38bd7ac6e2a0475c804f903e87ef554db0 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 3 Feb 2022 13:43:10 +0100 Subject: [PATCH] MOBILE-3977 core: Optimize wscache table --- src/core/classes/site.ts | 88 ++++++++++---------- src/core/initializers/initialize-services.ts | 2 + src/core/services/sites.ts | 49 +++++++++++ 3 files changed, 95 insertions(+), 44 deletions(-) diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index dc702baf9..3182f60ff 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -41,6 +41,7 @@ import { CoreLogger } from '@singletons/logger'; import { Translate } from '@singletons'; import { CoreIonLoadingElement } from './ion-loading'; import { CoreLang } from '@services/lang'; +import { CoreSites } from '@services/sites'; /** * QR Code type enumeration. @@ -920,20 +921,20 @@ export class CoreSite { preSets: CoreSiteWSPreSets, emergency?: boolean, ): Promise { - const db = this.db; - if (!db || !preSets.getFromCache) { + if (!this.db || !preSets.getFromCache) { throw new CoreError('Get from cache is disabled.'); } const id = this.getCacheId(method, data); + const cacheTable = await CoreSites.getCacheTable(this); let entry: CoreSiteWSCacheRecord | undefined; if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) { - const entries = await db.getRecords(CoreSite.WS_CACHE_TABLE, { key: preSets.cacheKey }); + const entries = await cacheTable.all({ key: preSets.cacheKey }); if (!entries.length) { // Cache key not found, get by params sent. - entry = await db.getRecord(CoreSite.WS_CACHE_TABLE, { id }); + entry = await cacheTable.findByPrimaryKey({ id }); } else { if (entries.length > 1) { // More than one entry found. Search the one with same ID as this call. @@ -945,7 +946,7 @@ export class CoreSite { } } } else { - entry = await db.getRecord(CoreSite.WS_CACHE_TABLE, { id }); + entry = await cacheTable.findByPrimaryKey({ id }); } if (entry === undefined) { @@ -990,18 +991,25 @@ export class CoreSite { */ async getComponentCacheSize(component: string, componentId?: number): Promise { const params: Array = [component]; + const cacheTable = await CoreSites.getCacheTable(this); let extraClause = ''; if (componentId !== undefined && componentId !== null) { params.push(componentId); extraClause = ' AND componentId = ?'; } - const size = await this.getDb().getFieldSql( - 'SELECT SUM(length(data)) FROM ' + CoreSite.WS_CACHE_TABLE + ' WHERE component = ?' + extraClause, - params, + return cacheTable.reduce( + { + 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 protected async saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets): Promise { - if (!this.db) { - throw new CoreError('Site DB not initialized.'); - } - if (preSets.uniqueCacheKey) { // Cache key must be unique, delete all entries with same cache key. 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. // We decided to reuse this field to prevent modifying the database table. const id = this.getCacheId(method, data); + const cacheTable = await CoreSites.getCacheTable(this); const entry = { id, 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 protected async deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise { - if (!this.db) { - throw new CoreError('Site DB not initialized.'); - } - const id = this.getCacheId(method, data); + const cacheTable = await CoreSites.getCacheTable(this); if (allCacheKey) { - await this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { key: preSets.cacheKey }); + await cacheTable.delete({ key: preSets.cacheKey }); } else { - await this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { id }); + await cacheTable.deleteByPrimaryKey({ id }); } } @@ -1084,18 +1086,14 @@ export class CoreSite { return; } - if (!this.db) { - throw new CoreError('Site DB not initialized'); - } + const params = { component }; + const cacheTable = await CoreSites.getCacheTable(this); - const params = { - component, - }; if (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. */ async invalidateWsCache(): Promise { - if (!this.db) { - throw new CoreError('Site DB not initialized'); - } - this.logger.debug('Invalidate all the cache for site: ' + this.id); try { - await this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }); + const cacheTable = await CoreSites.getCacheTable(this); + + await cacheTable.update({ expirationTime: 0 }); } finally { CoreEvents.trigger(CoreEvents.WS_CACHE_INVALIDATED, {}, this.getId()); } @@ -1147,16 +1143,15 @@ export class CoreSite { * @return Promise resolved when the cache entries are invalidated. */ async invalidateWsCacheForKey(key: string): Promise { - if (!this.db) { - throw new CoreError('Site DB not initialized'); - } if (!key) { return; } 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. */ async invalidateWsCacheForKeyStartingWith(key: string): Promise { - if (!this.db) { - throw new CoreError('Site DB not initialized'); - } if (!key) { return; } 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) */ async getCacheUsage(): Promise { - const size = 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, + }); } /** diff --git a/src/core/initializers/initialize-services.ts b/src/core/initializers/initialize-services.ts index bef39b3a1..6b172b93c 100644 --- a/src/core/initializers/initialize-services.ts +++ b/src/core/initializers/initialize-services.ts @@ -15,11 +15,13 @@ import { CoreFilepool } from '@services/filepool'; import { CoreLang } from '@services/lang'; import { CoreLocalNotifications } from '@services/local-notifications'; +import { CoreSites } from '@services/sites'; import { CoreUpdateManager } from '@services/update-manager'; export default async function(): Promise { await Promise.all([ CoreFilepool.initialize(), + CoreSites.initialize(), CoreLang.initialize(), CoreLocalNotifications.initialize(), CoreUpdateManager.initialize(), diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index c20644f22..9de9c1998 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -31,6 +31,7 @@ import { CoreSiteConfig, CoreSitePublicConfigResponse, CoreSiteInfoResponse, + CoreSiteWSCacheRecord, } from '@classes/site'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreError } from '@classes/errors/error'; @@ -57,6 +58,9 @@ import { CoreErrorWithTitle } from '@classes/errors/errorwithtitle'; import { CoreAjaxError } from '@classes/errors/ajaxerror'; import { CoreAjaxWSError } from '@classes/errors/ajaxwserror'; 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('CORE_SITE_SCHEMAS'); @@ -85,6 +89,7 @@ export class CoreSitesProvider { // Variables for DB. protected appDB: Promise; protected resolveAppDB!: (appDB: SQLiteDB) => void; + protected cacheTables: Record>> = {}; constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) { 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. */ @@ -112,6 +134,33 @@ export class CoreSitesProvider { this.resolveAppDB(CoreApp.getDB()); } + /** + * Get cache table. + * + * @param siteId Site id. + * @returns cache table. + */ + async getCacheTable(site: CoreSite): Promise> { + 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( + { 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. *