Skip to content

Commit 4ca0243

Browse files
Improve dynamic configurations by adding cache and simplifying client fetch (#6364) (#6403)
* Improve dynamic config Signed-off-by: Tianle Huang <tianleh@amazon.com> * reset yml Signed-off-by: Tianle Huang <tianleh@amazon.com> * bring back previous changes Signed-off-by: Tianle Huang <tianleh@amazon.com> --------- Signed-off-by: Tianle Huang <tianleh@amazon.com> (cherry picked from commit cd857cb) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> # Conflicts: # CHANGELOG.md Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 71ec0d5 commit 4ca0243

File tree

7 files changed

+248
-30
lines changed

7 files changed

+248
-30
lines changed

src/plugins/application_config/server/opensearch_config_client.test.ts

+92-14
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ describe('OpenSearch Configuration Client', () => {
4848
},
4949
};
5050

51-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
51+
const cache = {};
52+
53+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
5254

5355
const value = await client.getConfig();
5456

@@ -77,7 +79,10 @@ describe('OpenSearch Configuration Client', () => {
7779
}),
7880
},
7981
};
80-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
82+
83+
const cache = {};
84+
85+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
8186

8287
await expect(client.getConfig()).rejects.toThrowError(ERROR_MESSAGE);
8388
});
@@ -99,11 +104,45 @@ describe('OpenSearch Configuration Client', () => {
99104
},
100105
};
101106

102-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
107+
const cache = {
108+
has: jest.fn().mockReturnValue(false),
109+
set: jest.fn(),
110+
};
111+
112+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
103113

104114
const value = await client.getEntityConfig('config1');
105115

106116
expect(value).toBe('value1');
117+
expect(cache.set).toBeCalledWith('config1', 'value1');
118+
});
119+
120+
it('return configuration value from cache', async () => {
121+
const opensearchClient = {
122+
asInternalUser: {
123+
get: jest.fn().mockImplementation(() => {
124+
return {
125+
body: {
126+
_source: {
127+
value: 'value1',
128+
},
129+
},
130+
};
131+
}),
132+
},
133+
};
134+
135+
const cache = {
136+
has: jest.fn().mockReturnValue(true),
137+
get: jest.fn().mockReturnValue('cachedValue'),
138+
};
139+
140+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
141+
142+
const value = await client.getEntityConfig('config1');
143+
144+
expect(value).toBe('cachedValue');
145+
expect(cache.get).toBeCalledWith('config1');
107146
});
108147

109148
it('throws error when input is empty', async () => {
@@ -121,7 +160,9 @@ describe('OpenSearch Configuration Client', () => {
121160
},
122161
};
123162

124-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
163+
const cache = {};
164+
165+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
125166

126167
await expect(client.getEntityConfig(EMPTY_INPUT)).rejects.toThrowError(
127168
ERROR_MESSSAGE_FOR_EMPTY_INPUT
@@ -151,9 +192,16 @@ describe('OpenSearch Configuration Client', () => {
151192
},
152193
};
153194

154-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
195+
const cache = {
196+
has: jest.fn().mockReturnValue(false),
197+
set: jest.fn(),
198+
};
199+
200+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
155201

156202
await expect(client.getEntityConfig('config1')).rejects.toThrowError(ERROR_MESSAGE);
203+
204+
expect(cache.set).toBeCalledWith('config1', undefined);
157205
});
158206
});
159207

@@ -167,11 +215,16 @@ describe('OpenSearch Configuration Client', () => {
167215
},
168216
};
169217

170-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
218+
const cache = {
219+
del: jest.fn(),
220+
};
221+
222+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
171223

172224
const value = await client.deleteEntityConfig('config1');
173225

174226
expect(value).toBe('config1');
227+
expect(cache.del).toBeCalledWith('config1');
175228
});
176229

177230
it('throws error when input entity is empty', async () => {
@@ -183,7 +236,9 @@ describe('OpenSearch Configuration Client', () => {
183236
},
184237
};
185238

186-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
239+
const cache = {};
240+
241+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
187242

188243
await expect(client.deleteEntityConfig(EMPTY_INPUT)).rejects.toThrowError(
189244
ERROR_MESSSAGE_FOR_EMPTY_INPUT
@@ -213,11 +268,16 @@ describe('OpenSearch Configuration Client', () => {
213268
},
214269
};
215270

216-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
271+
const cache = {
272+
del: jest.fn(),
273+
};
274+
275+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
217276

218277
const value = await client.deleteEntityConfig('config1');
219278

220279
expect(value).toBe('config1');
280+
expect(cache.del).toBeCalledWith('config1');
221281
});
222282

223283
it('return deleted document entity when deletion fails due to document not found', async () => {
@@ -241,11 +301,16 @@ describe('OpenSearch Configuration Client', () => {
241301
},
242302
};
243303

244-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
304+
const cache = {
305+
del: jest.fn(),
306+
};
307+
308+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
245309

246310
const value = await client.deleteEntityConfig('config1');
247311

248312
expect(value).toBe('config1');
313+
expect(cache.del).toBeCalledWith('config1');
249314
});
250315

251316
it('throws error when opensearch throws error', async () => {
@@ -271,7 +336,9 @@ describe('OpenSearch Configuration Client', () => {
271336
},
272337
};
273338

274-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
339+
const cache = {};
340+
341+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
275342

276343
await expect(client.deleteEntityConfig('config1')).rejects.toThrowError(ERROR_MESSAGE);
277344
});
@@ -287,11 +354,16 @@ describe('OpenSearch Configuration Client', () => {
287354
},
288355
};
289356

290-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
357+
const cache = {
358+
set: jest.fn(),
359+
};
360+
361+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
291362

292363
const value = await client.updateEntityConfig('config1', 'newValue1');
293364

294365
expect(value).toBe('newValue1');
366+
expect(cache.set).toBeCalledWith('config1', 'newValue1');
295367
});
296368

297369
it('throws error when entity is empty ', async () => {
@@ -303,7 +375,9 @@ describe('OpenSearch Configuration Client', () => {
303375
},
304376
};
305377

306-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
378+
const cache = {};
379+
380+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
307381

308382
await expect(client.updateEntityConfig(EMPTY_INPUT, 'newValue1')).rejects.toThrowError(
309383
ERROR_MESSSAGE_FOR_EMPTY_INPUT
@@ -319,7 +393,9 @@ describe('OpenSearch Configuration Client', () => {
319393
},
320394
};
321395

322-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
396+
const cache = {};
397+
398+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
323399

324400
await expect(client.updateEntityConfig('config1', EMPTY_INPUT)).rejects.toThrowError(
325401
ERROR_MESSSAGE_FOR_EMPTY_INPUT
@@ -349,7 +425,9 @@ describe('OpenSearch Configuration Client', () => {
349425
},
350426
};
351427

352-
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
428+
const cache = {};
429+
430+
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);
353431

354432
await expect(client.updateEntityConfig('config1', 'newValue1')).rejects.toThrowError(
355433
ERROR_MESSAGE

src/plugins/application_config/server/opensearch_config_client.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,54 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import LRUCache from 'lru-cache';
67
import { IScopedClusterClient, Logger } from '../../../../src/core/server';
7-
88
import { ConfigurationClient } from './types';
99
import { validate } from './string_utils';
1010

1111
export class OpenSearchConfigurationClient implements ConfigurationClient {
1212
private client: IScopedClusterClient;
1313
private configurationIndexName: string;
1414
private readonly logger: Logger;
15+
private cache: LRUCache<string, string | undefined>;
1516

1617
constructor(
1718
scopedClusterClient: IScopedClusterClient,
1819
configurationIndexName: string,
19-
logger: Logger
20+
logger: Logger,
21+
cache: LRUCache<string, string | undefined>
2022
) {
2123
this.client = scopedClusterClient;
2224
this.configurationIndexName = configurationIndexName;
2325
this.logger = logger;
26+
this.cache = cache;
2427
}
2528

2629
async getEntityConfig(entity: string) {
2730
const entityValidated = validate(entity, this.logger);
2831

32+
if (this.cache.has(entityValidated)) {
33+
return this.cache.get(entityValidated);
34+
}
35+
36+
this.logger.info(`Key ${entityValidated} is not found from cache.`);
37+
2938
try {
3039
const data = await this.client.asInternalUser.get({
3140
index: this.configurationIndexName,
3241
id: entityValidated,
3342
});
43+
const value = data?.body?._source?.value;
3444

35-
return data?.body?._source?.value || '';
45+
this.cache.set(entityValidated, value);
46+
47+
return value;
3648
} catch (e) {
3749
const errorMessage = `Failed to get entity ${entityValidated} due to error ${e}`;
3850

3951
this.logger.error(errorMessage);
4052

53+
this.cache.set(entityValidated, undefined);
4154
throw e;
4255
}
4356
}
@@ -55,6 +68,8 @@ export class OpenSearchConfigurationClient implements ConfigurationClient {
5568
},
5669
});
5770

71+
this.cache.set(entityValidated, newValueValidated);
72+
5873
return newValueValidated;
5974
} catch (e) {
6075
const errorMessage = `Failed to update entity ${entityValidated} with newValue ${newValueValidated} due to error ${e}`;
@@ -74,15 +89,19 @@ export class OpenSearchConfigurationClient implements ConfigurationClient {
7489
id: entityValidated,
7590
});
7691

92+
this.cache.del(entityValidated);
93+
7794
return entityValidated;
7895
} catch (e) {
7996
if (e?.body?.error?.type === 'index_not_found_exception') {
8097
this.logger.info('Attemp to delete a not found index.');
98+
this.cache.del(entityValidated);
8199
return entityValidated;
82100
}
83101

84102
if (e?.body?.result === 'not_found') {
85103
this.logger.info('Attemp to delete a not found document.');
104+
this.cache.del(entityValidated);
86105
return entityValidated;
87106
}
88107

0 commit comments

Comments
 (0)