Skip to content

Commit 3026f0d

Browse files
authored
Merge pull request #4210 from RedisInsight/be/feature/CR-263-recommendations
CR-263: Optimize recommendations lookup and creation
2 parents 5b3e574 + cd0611c commit 3026f0d

9 files changed

+146
-92
lines changed

redisinsight/api/src/__mocks__/database-recommendation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const mockDatabaseRecommendationService = () => ({
3434
create: jest.fn(),
3535
list: jest.fn(),
3636
check: jest.fn(),
37+
checkMulti: jest.fn(),
3738
read: jest.fn(),
3839
});
3940

redisinsight/api/src/modules/browser/keys/keys.service.spec.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,13 @@ describe('KeysService', () => {
153153
getKeyInfoResponse.name,
154154
);
155155

156-
expect(recommendationService.check).toBeCalledWith(
157-
mockBrowserClientMetadata,
158-
RECOMMENDATION_NAMES.BIG_SETS,
159-
result,
160-
);
161-
expect(recommendationService.check).toBeCalledWith(
156+
expect(recommendationService.checkMulti).toBeCalledWith(
162157
mockBrowserClientMetadata,
163-
RECOMMENDATION_NAMES.BIG_STRINGS,
164-
result,
165-
);
166-
expect(recommendationService.check).toBeCalledWith(
167-
mockBrowserClientMetadata,
168-
RECOMMENDATION_NAMES.COMPRESSION_FOR_LIST,
158+
[
159+
RECOMMENDATION_NAMES.BIG_SETS,
160+
RECOMMENDATION_NAMES.BIG_STRINGS,
161+
RECOMMENDATION_NAMES.COMPRESSION_FOR_LIST,
162+
],
169163
result,
170164
);
171165
});

redisinsight/api/src/modules/browser/keys/keys.service.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -145,21 +145,17 @@ export class KeysService {
145145

146146
const result = await this.keyInfoProvider.getStrategy(type).getInfo(client, key, type);
147147
this.logger.debug('Succeed to get key info', clientMetadata);
148-
this.recommendationService.check(
149-
clientMetadata,
150-
RECOMMENDATION_NAMES.BIG_SETS,
151-
result,
152-
);
153-
this.recommendationService.check(
154-
clientMetadata,
155-
RECOMMENDATION_NAMES.BIG_STRINGS,
156-
result,
157-
);
158-
this.recommendationService.check(
148+
149+
this.recommendationService.checkMulti(
159150
clientMetadata,
160-
RECOMMENDATION_NAMES.COMPRESSION_FOR_LIST,
151+
[
152+
RECOMMENDATION_NAMES.BIG_SETS,
153+
RECOMMENDATION_NAMES.BIG_STRINGS,
154+
RECOMMENDATION_NAMES.COMPRESSION_FOR_LIST,
155+
],
161156
result,
162157
);
158+
163159
return plainToClass(GetKeyInfoResponse, result);
164160
} catch (error) {
165161
this.logger.error('Failed to get key info.', error, clientMetadata);

redisinsight/api/src/modules/database-recommendation/database-recommendation.service.ts

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,52 +52,84 @@ export class DatabaseRecommendationService {
5252
return this.databaseRecommendationRepository.list({ ...clientMetadata, db });
5353
}
5454

55-
/**
56-
* Check recommendation condition
57-
* @param clientMetadata
58-
* @param recommendationName
59-
* @param data
60-
*/
61-
public async check(
62-
clientMetadata: ClientMetadata,
55+
private async checkRecommendation(
6356
recommendationName: string,
64-
data: any,
57+
exists: boolean,
58+
clientMetadata: ClientMetadata, data: any,
6559
): Promise<DatabaseRecommendation> {
60+
if (!exists) {
61+
const recommendation = await this.scanner.determineRecommendation(
62+
clientMetadata.sessionMetadata,
63+
recommendationName,
64+
data,
65+
);
66+
67+
if (recommendation) {
68+
const entity = plainToClass(
69+
DatabaseRecommendation,
70+
{ databaseId: clientMetadata?.databaseId, ...recommendation },
71+
);
72+
73+
return await this.create(clientMetadata, entity);
74+
}
75+
}
76+
77+
return null;
78+
}
79+
80+
public async checkMulti(
81+
clientMetadata: ClientMetadata,
82+
recommendationNames: string[],
83+
data: any,
84+
): Promise<Map<string, DatabaseRecommendation[]>> {
6685
try {
6786
const newClientMetadata = {
6887
...clientMetadata,
6988
db: clientMetadata.db
7089
?? (await this.databaseService.get(clientMetadata.sessionMetadata, clientMetadata.databaseId))?.db
7190
?? 0,
7291
};
73-
const isRecommendationExist = await this.databaseRecommendationRepository.isExist(
92+
const isRecommendationExist = await this.databaseRecommendationRepository.isExistMulti(
7493
newClientMetadata,
75-
recommendationName,
94+
recommendationNames,
7695
);
77-
if (!isRecommendationExist) {
78-
const recommendation = await this.scanner.determineRecommendation(
79-
clientMetadata.sessionMetadata,
80-
recommendationName,
81-
data,
82-
);
83-
84-
if (recommendation) {
85-
const entity = plainToClass(
86-
DatabaseRecommendation,
87-
{ databaseId: newClientMetadata?.databaseId, ...recommendation },
88-
);
8996

90-
return await this.create(newClientMetadata, entity);
91-
}
92-
}
97+
const results = await Promise.all(
98+
recommendationNames.map(
99+
(name) => this.checkRecommendation(
100+
name,
101+
isRecommendationExist[name],
102+
newClientMetadata,
103+
data,
104+
),
105+
),
106+
);
93107

94-
return null;
108+
return results.reduce((acc, result, idx) => ({
109+
...acc,
110+
[recommendationNames[idx]]: result,
111+
}), {} as Map<string, DatabaseRecommendation[]>);
95112
} catch (e) {
96113
this.logger.warn('Unable to check recommendation', e, clientMetadata);
97114
return null;
98115
}
99116
}
100117

118+
/**
119+
* Check recommendation condition
120+
* @param clientMetadata
121+
* @param recommendationName
122+
* @param data
123+
*/
124+
public async check(
125+
clientMetadata: ClientMetadata,
126+
recommendationName: string,
127+
data: any,
128+
): Promise<DatabaseRecommendation> {
129+
const result = await this.checkMulti(clientMetadata, [recommendationName], data);
130+
return result[recommendationName];
131+
}
132+
101133
/**
102134
* Mark all recommendations as read for particular database
103135
* @param clientMetadata

redisinsight/api/src/modules/database-recommendation/repositories/database-recommendation.repository.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,20 @@ export abstract class DatabaseRecommendationRepository {
4242
): Promise<DatabaseRecommendation>;
4343

4444
/**
45-
* Check is recommendation already exist in repository
45+
* Check if recommendation already exist in repository
4646
* @param clientMetadata
4747
* @param name
4848
* @return Boolean
4949
*/
5050
abstract isExist(clientMetadata: ClientMetadata, name: string): Promise<boolean>;
5151

52+
/**
53+
* Check if one or more recommendations exist in repository
54+
* @param clientMetadata
55+
* @param names
56+
*/
57+
abstract isExistMulti(clientMetadata: ClientMetadata, names: string[]): Promise<Map<string, boolean>>;
58+
5259
/**
5360
* Get database recommendation by id
5461
* @param sessionMetadata

redisinsight/api/src/modules/database-recommendation/repositories/local.database.recommendation.repository.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ describe('LocalDatabaseRecommendationRepository', () => {
7878
});
7979
});
8080

81+
describe('isExistMulti', () => {
82+
it('should return results for multiple recommendation names', async () => {
83+
repository.findOneBy.mockResolvedValueOnce(null);
84+
repository.findOneBy.mockResolvedValueOnce({});
85+
expect(await service.isExistMulti(mockClientMetadata, ['test1', 'test2'])).toEqual({
86+
test1: false,
87+
test2: true,
88+
});
89+
});
90+
91+
it('should return empty Map when received error', async () => {
92+
repository.findOneBy.mockRejectedValueOnce(new Error());
93+
expect(await service.isExistMulti(mockClientMetadata, ['test1', 'test2'])).toEqual({});
94+
});
95+
});
96+
8197
describe('create', () => {
8298
it('should create recommendation', async () => {
8399
const result = await service.create(mockClientMetadata.sessionMetadata, mockDatabaseRecommendation);

redisinsight/api/src/modules/database-recommendation/repositories/local.database.recommendation.repository.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class LocalDatabaseRecommendationRepository extends DatabaseRecommendatio
4141

4242
/**
4343
* Save entire entity
44-
* @param _
44+
* @param sessionMetadata
4545
* @param entity
4646
*/
4747
async create(sessionMetadata: SessionMetadata, entity: DatabaseRecommendation): Promise<DatabaseRecommendation> {
@@ -102,7 +102,7 @@ export class LocalDatabaseRecommendationRepository extends DatabaseRecommendatio
102102
}
103103

104104
/**
105-
* Read all recommendations recommendations
105+
* Read all recommendations
106106
* @param clientMetadata
107107
*/
108108
async read(clientMetadata: ClientMetadata): Promise<void> {
@@ -166,6 +166,31 @@ export class LocalDatabaseRecommendationRepository extends DatabaseRecommendatio
166166
}
167167
}
168168

169+
/**
170+
* Check if one or more recommendations exist in database
171+
* @param clientMetadata
172+
* @param names
173+
*/
174+
async isExistMulti(clientMetadata: ClientMetadata, names: string[]): Promise<Map<string, boolean>> {
175+
const { databaseId } = clientMetadata;
176+
177+
try {
178+
this.logger.debug('Checking if recommendations exist', names, clientMetadata);
179+
180+
const results = await Promise.all(
181+
names.map((name) => this.repository.findOneBy({ databaseId, name })),
182+
);
183+
184+
return results.reduce((acc, result, idx) => ({
185+
...acc,
186+
[names[idx]]: !!result,
187+
}), {} as Map<string, boolean>);
188+
} catch (err) {
189+
this.logger.error('Failed to check existence of recommendations', err, names, clientMetadata);
190+
return {} as Map<string, boolean>;
191+
}
192+
}
193+
169194
/**
170195
* Get recommendation by id
171196
* @param sessionMetadata

redisinsight/api/src/modules/database/database-connection.service.spec.ts

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,15 @@ describe('DatabaseConnectionService', () => {
9090
it('should call recommendationService', async () => {
9191
expect(await service.connect(mockCommonClientMetadata)).toEqual(undefined);
9292

93-
expect(recommendationService.check).toHaveBeenCalledTimes(3);
93+
expect(recommendationService.checkMulti).toHaveBeenCalledTimes(1);
9494

95-
expect(recommendationService.check).toBeCalledWith(
96-
mockCommonClientMetadata,
97-
RECOMMENDATION_NAMES.REDIS_VERSION,
98-
mockRedisGeneralInfo,
99-
);
100-
expect(recommendationService.check).toBeCalledWith(
101-
mockCommonClientMetadata,
102-
RECOMMENDATION_NAMES.LUA_SCRIPT,
103-
mockRedisGeneralInfo,
104-
);
105-
expect(recommendationService.check).toBeCalledWith(
95+
expect(recommendationService.checkMulti).toBeCalledWith(
10696
mockCommonClientMetadata,
107-
RECOMMENDATION_NAMES.BIG_AMOUNT_OF_CONNECTED_CLIENTS,
97+
[
98+
RECOMMENDATION_NAMES.REDIS_VERSION,
99+
RECOMMENDATION_NAMES.LUA_SCRIPT,
100+
RECOMMENDATION_NAMES.BIG_AMOUNT_OF_CONNECTED_CLIENTS,
101+
],
108102
mockRedisGeneralInfo,
109103
);
110104
});
@@ -116,21 +110,16 @@ describe('DatabaseConnectionService', () => {
116110

117111
expect(await service.connect(mockCommonClientMetadata)).toEqual(undefined);
118112

119-
expect(recommendationService.check).toHaveBeenCalledTimes(4);
113+
expect(recommendationService.check).toHaveBeenCalledTimes(1);
114+
expect(recommendationService.checkMulti).toHaveBeenCalledTimes(1);
120115

121-
expect(recommendationService.check).toBeCalledWith(
122-
mockCommonClientMetadata,
123-
RECOMMENDATION_NAMES.REDIS_VERSION,
124-
mockRedisGeneralInfo,
125-
);
126-
expect(recommendationService.check).toBeCalledWith(
127-
mockCommonClientMetadata,
128-
RECOMMENDATION_NAMES.LUA_SCRIPT,
129-
mockRedisGeneralInfo,
130-
);
131-
expect(recommendationService.check).toBeCalledWith(
116+
expect(recommendationService.checkMulti).toBeCalledWith(
132117
mockCommonClientMetadata,
133-
RECOMMENDATION_NAMES.BIG_AMOUNT_OF_CONNECTED_CLIENTS,
118+
[
119+
RECOMMENDATION_NAMES.REDIS_VERSION,
120+
RECOMMENDATION_NAMES.LUA_SCRIPT,
121+
RECOMMENDATION_NAMES.BIG_AMOUNT_OF_CONNECTED_CLIENTS,
122+
],
134123
mockRedisGeneralInfo,
135124
);
136125

redisinsight/api/src/modules/database/database-connection.service.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,13 @@ export class DatabaseConnectionService {
5454

5555
const generalInfo = await this.databaseInfoProvider.getRedisGeneralInfo(client);
5656

57-
this.recommendationService.check(
57+
this.recommendationService.checkMulti(
5858
clientMetadata,
59-
RECOMMENDATION_NAMES.REDIS_VERSION,
60-
generalInfo,
61-
);
62-
this.recommendationService.check(
63-
clientMetadata,
64-
RECOMMENDATION_NAMES.LUA_SCRIPT,
65-
generalInfo,
66-
);
67-
this.recommendationService.check(
68-
clientMetadata,
69-
RECOMMENDATION_NAMES.BIG_AMOUNT_OF_CONNECTED_CLIENTS,
59+
[
60+
RECOMMENDATION_NAMES.REDIS_VERSION,
61+
RECOMMENDATION_NAMES.LUA_SCRIPT,
62+
RECOMMENDATION_NAMES.BIG_AMOUNT_OF_CONNECTED_CLIENTS,
63+
],
7064
generalInfo,
7165
);
7266

0 commit comments

Comments
 (0)