Skip to content

Commit be61838

Browse files
authored
Performance Optimization: CloudFormation Completion Speed Improvement (#192)
1 parent 8a67062 commit be61838

File tree

7 files changed

+108
-58
lines changed

7 files changed

+108
-58
lines changed

src/schema/CombinedSchemas.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
import { PrivateSchemas, PrivateSchemasType } from './PrivateSchemas';
22
import { RegionalSchemas, RegionalSchemasType } from './RegionalSchemas';
33
import { ResourceSchema } from './ResourceSchema';
4+
import { SamSchemas, SamSchemasType } from './SamSchemas';
45

56
export class CombinedSchemas {
67
readonly numSchemas: number;
78
readonly schemas: Map<string, ResourceSchema>;
89

9-
constructor(regionalSchemas?: RegionalSchemas, privateSchemas?: PrivateSchemas, samSchemas?: Map<string, unknown>) {
10-
const samEntries: [string, ResourceSchema][] = samSchemas
11-
? [...samSchemas.entries()].map(([type, schema]) => [type, new ResourceSchema(JSON.stringify(schema))])
12-
: [];
13-
10+
constructor(regionalSchemas?: RegionalSchemas, privateSchemas?: PrivateSchemas, samSchemas?: SamSchemas) {
1411
this.schemas = new Map<string, ResourceSchema>([
1512
...(privateSchemas?.schemas ?? []),
1613
...(regionalSchemas?.schemas ?? []),
17-
...samEntries,
14+
...(samSchemas?.schemas ?? []),
1815
]);
1916
this.numSchemas = this.schemas.size;
2017
}
@@ -29,10 +26,12 @@ export class CombinedSchemas {
2926
static from(
3027
regionalSchemas?: RegionalSchemasType,
3128
privateSchemas?: PrivateSchemasType,
32-
samSchemas?: Map<string, unknown>,
29+
samSchemas?: SamSchemasType,
3330
) {
3431
const regionalSchema = regionalSchemas === undefined ? undefined : RegionalSchemas.from(regionalSchemas);
3532
const privateSchema = privateSchemas === undefined ? undefined : PrivateSchemas.from(privateSchemas);
36-
return new CombinedSchemas(regionalSchema, privateSchema, samSchemas);
33+
const samSchema = samSchemas === undefined ? undefined : SamSchemas.from(samSchemas);
34+
35+
return new CombinedSchemas(regionalSchema, privateSchema, samSchema);
3736
}
3837
}

src/schema/GetSamSchemaTask.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { LoggerFactory } from '../telemetry/LoggerFactory';
33
import { Measure } from '../telemetry/TelemetryDecorator';
44
import { extractErrorMessage } from '../utils/Errors';
55
import { downloadFile } from './RemoteSchemaHelper';
6+
import { SamSchemas, SamSchemasType } from './SamSchemas';
67
import { SamSchemaTransformer, SamSchema } from './SamSchemaTransformer';
78

89
const logger = LoggerFactory.getLogger('GetSamSchemaTask');
910

1011
export class GetSamSchemaTask {
1112
private static readonly SAM_SCHEMA_URL =
1213
'https://raw.githubusercontent.com/aws/serverless-application-model/refs/heads/main/schema_source/sam.schema.json';
13-
private static readonly SAM_SCHEMA_KEY = 'sam-schemas';
1414

1515
@Measure({ name: 'getSamSchema' })
1616
async run(dataStore: DataStore): Promise<void> {
@@ -22,33 +22,26 @@ export class GetSamSchemaTask {
2222

2323
const resourceSchemas = SamSchemaTransformer.transformSamSchema(samSchema as unknown as SamSchema);
2424

25-
// Store each resource schema individually
26-
for (const [resourceType, schema] of resourceSchemas) {
27-
await dataStore.put(`${GetSamSchemaTask.SAM_SCHEMA_KEY}:${resourceType}`, JSON.stringify(schema));
28-
}
25+
// Convert to SamSchemasType format
26+
const schemas = [...resourceSchemas.entries()].map(([resourceType, schema]) => ({
27+
name: resourceType,
28+
content: JSON.stringify(schema),
29+
createdMs: Date.now(),
30+
}));
31+
32+
const samSchemasData: SamSchemasType = {
33+
version: SamSchemas.V1,
34+
schemas: schemas,
35+
firstCreatedMs: Date.now(),
36+
lastModifiedMs: Date.now(),
37+
};
38+
39+
await dataStore.put('sam-schemas', samSchemasData);
2940

3041
logger.info(`Downloaded and stored ${resourceSchemas.size} SAM resource schemas`);
3142
} catch (error) {
3243
logger.error({ error: extractErrorMessage(error) }, 'Failed to download SAM schema');
3344
throw error;
3445
}
3546
}
36-
37-
static getSamSchemas(dataStore: DataStore): Map<string, unknown> {
38-
const schemas = new Map<string, unknown>();
39-
40-
// Get all SAM schema keys
41-
const keys = dataStore.keys(1000);
42-
const samKeys = keys.filter((key: string) => key.startsWith(`${this.SAM_SCHEMA_KEY}:`));
43-
44-
for (const key of samKeys) {
45-
const schemaJson = dataStore.get<string>(key);
46-
if (schemaJson) {
47-
const resourceType = key.replace(`${this.SAM_SCHEMA_KEY}:`, '');
48-
schemas.set(resourceType, JSON.parse(schemaJson) as unknown);
49-
}
50-
}
51-
52-
return schemas;
53-
}
5447
}

src/schema/GetSchemaTaskManager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,16 @@ export class GetSchemaTaskManager implements SettingsConfigurable, Closeable {
7272
}
7373

7474
runPrivateTask() {
75-
this.privateTask.run(this.schemas.privateSchemas, this.log).catch(() => {});
75+
this.privateTask
76+
.run(this.schemas.privateSchemas, this.log)
77+
.then(() => this.schemas.invalidateCombinedSchemas())
78+
.catch(() => {});
7679
}
7780

7881
private async runSamTask(): Promise<void> {
7982
try {
8083
await this.samTask.run(this.schemas.publicSchemas);
84+
this.schemas.invalidateCombinedSchemas();
8185
} catch (error) {
8286
this.log.error({ error }, 'Failed to run SAM schema task');
8387
}
@@ -98,6 +102,7 @@ export class GetSchemaTaskManager implements SettingsConfigurable, Closeable {
98102
const task = this.tasks.shift();
99103
if (task) {
100104
task.run(this.schemas.publicSchemas, this.log)
105+
.then(() => this.schemas.invalidateCombinedSchemas())
101106
.catch(() => {
102107
this.tasks.push(task);
103108
})

src/schema/SamSchemas.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ResourceSchema } from './ResourceSchema';
2+
3+
export type SamSchemasType = {
4+
version: string;
5+
schemas: { name: string; content: string; createdMs: number }[];
6+
firstCreatedMs: number;
7+
lastModifiedMs: number;
8+
};
9+
10+
export class SamSchemas {
11+
static readonly V1 = 'v1';
12+
13+
readonly version: string;
14+
readonly numSchemas: number;
15+
readonly firstCreatedMs: number;
16+
readonly lastModifiedMs: number;
17+
readonly schemas: Map<string, ResourceSchema>;
18+
19+
constructor(
20+
version: string,
21+
schemas: { name: string; content: string; createdMs: number }[],
22+
firstCreatedMs: number,
23+
lastModifiedMs: number,
24+
) {
25+
this.version = version;
26+
this.firstCreatedMs = firstCreatedMs;
27+
this.lastModifiedMs = lastModifiedMs;
28+
this.schemas = new Map(
29+
schemas.map((x) => {
30+
const schema = new ResourceSchema(x.content);
31+
return [schema.typeName, schema];
32+
}),
33+
);
34+
this.numSchemas = this.schemas.size;
35+
}
36+
37+
static from(json: SamSchemasType) {
38+
return new SamSchemas(json.version, json.schemas, json.firstCreatedMs, json.lastModifiedMs);
39+
}
40+
}

src/schema/SchemaRetriever.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { Closeable } from '../utils/Closeable';
66
import { AwsRegion, getRegion } from '../utils/Region';
77
import { CombinedSchemas } from './CombinedSchemas';
88
import { GetSchemaTaskManager } from './GetSchemaTaskManager';
9-
import { PrivateSchemasType } from './PrivateSchemas';
109
import { RegionalSchemasType } from './RegionalSchemas';
1110
import { SchemaStore } from './SchemaStore';
1211

@@ -80,36 +79,19 @@ export class SchemaRetriever implements SettingsConfigurable, Closeable {
8079
this.schemaTaskManager.addTask(region);
8180
}
8281

83-
const privateSchemas = this.schemaStore.privateSchemas.get<PrivateSchemasType>(profile);
84-
const samSchemas = this.getSamSchemasFromStore();
85-
return CombinedSchemas.from(regionalSchemas, privateSchemas, samSchemas);
86-
}
87-
88-
private getSamSchemasFromStore(): Map<string, unknown> | undefined {
89-
try {
90-
const schemas = new Map<string, unknown>();
91-
const keys = this.schemaStore.publicSchemas.keys(1000); // Get up to 1000 keys
92-
const samKeys = keys.filter((key: string) => key.startsWith('sam-schemas:'));
93-
94-
for (const key of samKeys) {
95-
const schemaJson = this.schemaStore.publicSchemas.get<string>(key);
96-
if (schemaJson) {
97-
const resourceType = key.replace('sam-schemas:', '');
98-
schemas.set(resourceType, JSON.parse(schemaJson));
99-
}
100-
}
101-
102-
return schemas.size > 0 ? schemas : undefined;
103-
} catch (error) {
104-
this.log.debug({ error }, 'Failed to load SAM schemas');
105-
return undefined;
106-
}
82+
return this.schemaStore.getCombinedSchemas(region, profile);
10783
}
10884

10985
updatePrivateSchemas() {
86+
this.schemaStore.invalidateCombinedSchemas();
11087
this.schemaTaskManager.runPrivateTask();
11188
}
11289

90+
// Method to invalidate cache when any schemas are updated
91+
invalidateCache() {
92+
this.schemaStore.invalidateCombinedSchemas();
93+
}
94+
11395
private getRegionalSchemasFromStore(region: AwsRegion) {
11496
return this.schemaStore.publicSchemas.get<RegionalSchemasType>(region);
11597
}

src/schema/SchemaStore.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
import { DataStoreFactoryProvider, Persistence } from '../datastore/DataStore';
2+
import { AwsRegion } from '../utils/Region';
3+
import { CombinedSchemas } from './CombinedSchemas';
4+
import { PrivateSchemasType } from './PrivateSchemas';
5+
import { RegionalSchemasType } from './RegionalSchemas';
6+
import { SamSchemasType } from './SamSchemas';
27

38
export class SchemaStore {
49
public readonly publicSchemas = this.dataStoreFactory.get('public_schemas', Persistence.local);
510
public readonly privateSchemas = this.dataStoreFactory.get('private_schemas', Persistence.memory);
11+
public readonly samSchemas = this.dataStoreFactory.get('sam_schemas', Persistence.local);
12+
public readonly combinedSchemas = this.dataStoreFactory.get('combined_schemas', Persistence.memory);
613

714
constructor(private readonly dataStoreFactory: DataStoreFactoryProvider) {}
15+
16+
getCombinedSchemas(region: AwsRegion, profile: string): CombinedSchemas {
17+
const cacheKey = `${region}:${profile}`;
18+
let cached = this.combinedSchemas.get<CombinedSchemas>(cacheKey);
19+
20+
if (!cached) {
21+
const regionalSchemas = this.publicSchemas.get<RegionalSchemasType>(region);
22+
const privateSchemas = this.privateSchemas.get<PrivateSchemasType>(profile);
23+
const samSchemas = this.samSchemas.get<SamSchemasType>('sam-schemas');
24+
25+
cached = CombinedSchemas.from(regionalSchemas, privateSchemas, samSchemas);
26+
void this.combinedSchemas.put(cacheKey, cached);
27+
}
28+
29+
return cached;
30+
}
31+
32+
invalidateCombinedSchemas() {
33+
void this.combinedSchemas.clear();
34+
}
835
}

tst/utils/MockServerComponents.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ export function createMockSchemaTaskManager() {
163163
return stubInterface<GetSchemaTaskManager>();
164164
}
165165

166+
export function createMockSchemaStore() {
167+
return stubInterface<SchemaStore>();
168+
}
169+
166170
export function createMockSchemaRetriever(schemas?: CombinedSchemas) {
167171
const mock = stubInterface<SchemaRetriever>();
168172
if (schemas) {
@@ -359,7 +363,7 @@ export function createMockComponents(o: Partial<CfnLspServerComponentsType> = {}
359363
cfnService: overrides.cfnService ?? createMockCfnService(),
360364
ccapiService: overrides.ccapiService ?? createMockCcapiService(),
361365
iacGeneratorService: overrides.iacGeneratorService ?? createMockIacGeneratorService(),
362-
schemaStore: overrides.schemaStore ?? new SchemaStore(dataStoreFactory),
366+
schemaStore: overrides.schemaStore ?? createMockSchemaStore(),
363367
schemaTaskManager: overrides.schemaTaskManager ?? createMockSchemaTaskManager(),
364368
schemaRetriever: overrides.schemaRetriever ?? createMockSchemaRetriever(),
365369
cfnLintService: overrides.cfnLintService ?? createMockCfnLintService(),

0 commit comments

Comments
 (0)