Browse Source

fix: cache pipeline & source delete (#8688)

* fix: avoid duplicate refresh

* fix: pipe nested refresh

* fix: wrong reference for source

* fix: base list

* fix: unit test base list

* fix: disable meta info for ee

* fix: improve readability
pull/8706/head
Mert E 7 months ago committed by GitHub
parent
commit
9a621e4958
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 31
      packages/nocodb/src/cache/CacheMgr.ts
  2. 3
      packages/nocodb/src/models/Base.ts
  3. 4
      packages/nocodb/src/models/Source.ts
  4. 2
      packages/nocodb/src/models/User.ts
  5. 4
      packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.controller.ts
  6. 2
      packages/nocodb/src/services/bases.service.ts
  7. 3
      packages/nocodb/src/services/utils.service.ts
  8. 14
      packages/nocodb/tests/unit/init/cleanupMeta.ts
  9. 4
      packages/nocodb/tests/unit/rest/tests/base.test.ts

31
packages/nocodb/src/cache/CacheMgr.ts vendored

@ -1,5 +1,6 @@
import debug from 'debug'; import debug from 'debug';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import type { ChainableCommander } from 'ioredis';
import type IORedis from 'ioredis'; import type IORedis from 'ioredis';
import { CacheDelDirection, CacheGetType } from '~/utils/globals'; import { CacheDelDirection, CacheGetType } from '~/utils/globals';
@ -77,7 +78,7 @@ export default abstract class CacheMgr {
if (!skipTTL && o.timestamp) { if (!skipTTL && o.timestamp) {
const diff = Date.now() - o.timestamp; const diff = Date.now() - o.timestamp;
if (diff > NC_REDIS_GRACE_TTL * 1000) { if (diff > NC_REDIS_GRACE_TTL * 1000) {
await this.refreshTTL(key); await this.execRefreshTTL(key);
} }
} }
@ -166,7 +167,7 @@ export default abstract class CacheMgr {
NC_REDIS_TTL, NC_REDIS_TTL,
) )
.then(async () => { .then(async () => {
await this.refreshTTL(key, timestamp); await this.execRefreshTTL(key, timestamp);
return true; return true;
}); });
} else { } else {
@ -317,7 +318,7 @@ export default abstract class CacheMgr {
if (typeof o === 'object') { if (typeof o === 'object') {
const diff = Date.now() - o.timestamp; const diff = Date.now() - o.timestamp;
if (diff > NC_REDIS_GRACE_TTL * 1000) { if (diff > NC_REDIS_GRACE_TTL * 1000) {
await this.refreshTTL(key); await this.execRefreshTTL(key);
} }
} }
} catch (e) { } catch (e) {
@ -510,10 +511,7 @@ export default abstract class CacheMgr {
}); });
list.push(key); list.push(key);
return this.set(listKey, list).then(async (res) => { return this.set(listKey, list);
await this.refreshTTL(listKey);
return res;
});
} }
async update(key: string, value: any): Promise<boolean> { async update(key: string, value: any): Promise<boolean> {
@ -564,7 +562,16 @@ export default abstract class CacheMgr {
} }
} }
async refreshTTL(key: string, timestamp?: number): Promise<void> { async execRefreshTTL(keys: string, timestamp?: number): Promise<void> {
const p = await this.refreshTTL(this.client.pipeline(), keys, timestamp);
await p.exec();
}
async refreshTTL(
pipeline: ChainableCommander,
key: string,
timestamp?: number,
): Promise<ChainableCommander> {
log(`${this.context}::refreshTTL: refreshing TTL for ${key}`); log(`${this.context}::refreshTTL: refreshing TTL for ${key}`);
const isParent = /:list$/.test(key); const isParent = /:list$/.test(key);
timestamp = timestamp || Date.now(); timestamp = timestamp || Date.now();
@ -573,7 +580,6 @@ export default abstract class CacheMgr {
(await this.getRaw(key, CacheGetType.TYPE_ARRAY, true)) || []; (await this.getRaw(key, CacheGetType.TYPE_ARRAY, true)) || [];
if (list && list.length) { if (list && list.length) {
const listValues = await this.client.mget(list); const listValues = await this.client.mget(list);
const pipeline = this.client.pipeline();
for (const [i, v] of listValues.entries()) { for (const [i, v] of listValues.entries()) {
const key = list[i]; const key = list[i];
if (v) { if (v) {
@ -602,19 +608,18 @@ export default abstract class CacheMgr {
} }
} }
pipeline.expire(key, NC_REDIS_TTL - 60); pipeline.expire(key, NC_REDIS_TTL - 60);
await pipeline.exec();
} }
} else { } else {
const rawValue = await this.getRaw(key, null, true); const rawValue = await this.getRaw(key, null, true);
if (rawValue) { if (rawValue) {
if (rawValue.parentKeys && rawValue.parentKeys.length) { if (rawValue.parentKeys && rawValue.parentKeys.length) {
for (const parent of rawValue.parentKeys) { for (const parent of rawValue.parentKeys) {
await this.refreshTTL(parent, timestamp); pipeline = await this.refreshTTL(pipeline, parent, timestamp);
} }
} else { } else {
if (rawValue.timestamp !== timestamp) { if (rawValue.timestamp !== timestamp) {
rawValue.timestamp = timestamp; rawValue.timestamp = timestamp;
await this.client.set( pipeline.set(
key, key,
JSON.stringify(rawValue, this.getCircularReplacer()), JSON.stringify(rawValue, this.getCircularReplacer()),
'EX', 'EX',
@ -624,6 +629,8 @@ export default abstract class CacheMgr {
} }
} }
} }
return pipeline;
} }
async destroy(): Promise<boolean> { async destroy(): Promise<boolean> {

3
packages/nocodb/src/models/Base.ts

@ -105,8 +105,7 @@ export default class Base implements BaseType {
} }
static async list( static async list(
// @ts-ignore workspaceId?: string,
param,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
): Promise<Base[]> { ): Promise<Base[]> {
// todo: pagination // todo: pagination

4
packages/nocodb/src/models/Source.ts

@ -488,9 +488,9 @@ export default class Source implements SourceType {
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
{ force }: { force?: boolean } = {}, { force }: { force?: boolean } = {},
) { ) {
const bases = await Base.list({ baseId: this.base_id }, ncMeta); const sources = await Source.list(context, { baseId: this.id }, ncMeta);
if (bases[0].id === this.id && !force) { if (sources[0].id === this.id && !force) {
NcError.badRequest('Cannot delete first base'); NcError.badRequest('Cannot delete first base');
} }

2
packages/nocodb/src/models/User.ts

@ -75,7 +75,7 @@ export default class User implements UserType {
await NocoCache.del(CacheScope.INSTANCE_META); await NocoCache.del(CacheScope.INSTANCE_META);
// clear all base user related cache for instance // clear all base user related cache for instance
const bases = await Base.list({}, ncMeta); const bases = await Base.list(null, ncMeta);
for (const base of bases) { for (const base of bases) {
await NocoCache.deepDel( await NocoCache.deepDel(
`${CacheScope.BASE_USER}:${base.id}:list`, `${CacheScope.BASE_USER}:${base.id}:list`,

4
packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.controller.ts

@ -69,7 +69,7 @@ export class DuplicateController {
throw new Error(`Source not found!`); throw new Error(`Source not found!`);
} }
const bases = await Base.list({}); const bases = await Base.list(context.workspace_id);
const uniqueTitle = generateUniqueName( const uniqueTitle = generateUniqueName(
`${base.title} copy`, `${base.title} copy`,
@ -145,7 +145,7 @@ export class DuplicateController {
throw new Error(`Source not found!`); throw new Error(`Source not found!`);
} }
const bases = await Base.list({}); const bases = await Base.list(context.workspace_id);
const uniqueTitle = generateUniqueName( const uniqueTitle = generateUniqueName(
`${base.title} copy`, `${base.title} copy`,

2
packages/nocodb/src/services/bases.service.ts

@ -45,7 +45,7 @@ export class BasesService {
}, },
) { ) {
const bases = extractRolesObj(param.user?.roles)[OrgUserRoles.SUPER_ADMIN] const bases = extractRolesObj(param.user?.roles)[OrgUserRoles.SUPER_ADMIN]
? await Base.list(param.query) ? await Base.list()
: await BaseUser.getProjectsList(param.user.id, param.query); : await BaseUser.getProjectsList(param.user.id, param.query);
return bases; return bases;

3
packages/nocodb/src/services/utils.service.ts

@ -212,8 +212,9 @@ export class UtilsService {
} }
async aggregatedMetaInfo() { async aggregatedMetaInfo() {
// TODO: fix or deprecate for EE
const [bases, userCount] = await Promise.all([ const [bases, userCount] = await Promise.all([
Base.list({}), Base.list(),
Noco.ncMeta.metaCount(RootScopes.ROOT, RootScopes.ROOT, MetaTable.USERS), Noco.ncMeta.metaCount(RootScopes.ROOT, RootScopes.ROOT, MetaTable.USERS),
]); ]);

14
packages/nocodb/tests/unit/init/cleanupMeta.ts

@ -1,10 +1,18 @@
import TestDbMngr from '../TestDbMngr';
import { Base, Model } from '~/models'; import { Base, Model } from '~/models';
import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2'; import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2';
import { orderedMetaTables } from '~/utils/globals'; import { MetaTable, orderedMetaTables, RootScopes } from '~/utils/globals';
import TestDbMngr from '../TestDbMngr'; import Noco from '~/Noco';
const dropTablesAllNonExternalProjects = async () => { const dropTablesAllNonExternalProjects = async () => {
const bases = await Base.list({}); const rawBases = await Noco.ncMeta.metaList2(
RootScopes.BASE,
RootScopes.BASE,
MetaTable.PROJECT,
);
const bases = rawBases.map((b) => Base.castType(b));
const userCreatedTableNames: string[] = []; const userCreatedTableNames: string[] = [];
await Promise.all( await Promise.all(
bases bases

4
packages/nocodb/tests/unit/rest/tests/base.test.ts

@ -351,6 +351,10 @@ function baseTest() {
}); });
it('Get all bases meta', async () => { it('Get all bases meta', async () => {
if (process.env.EE === 'true') {
return;
}
await createTable(context, base, { await createTable(context, base, {
table_name: 'table1', table_name: 'table1',
title: 'table1', title: 'table1',

Loading…
Cancel
Save