Skip to content

Commit 8eb9e14

Browse files
authored
fix: oom for deprecated schema explorer (#7074)
1 parent daa2496 commit 8eb9e14

File tree

7 files changed

+111
-40
lines changed

7 files changed

+111
-40
lines changed

.changeset/wild-bags-know.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'hive': patch
3+
---
4+
5+
Prevent potential resource exhaustion in the deprecated schema explorer for large
6+
schemas.

packages/services/api/src/modules/operations/providers/operations-manager.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -907,15 +907,6 @@ export class OperationsManager {
907907
typename: string;
908908
} & TargetSelector,
909909
) {
910-
await this.session.assertPerformAction({
911-
action: 'project:describe',
912-
organizationId: args.organizationId,
913-
params: {
914-
organizationId: args.organizationId,
915-
projectId: args.projectId,
916-
},
917-
});
918-
919910
const loader = this.getClientNamesPerCoordinateOfTypeLoader({
920911
target: args.targetId,
921912
period: args.period,
@@ -1118,6 +1109,14 @@ export class OperationsManager {
11181109
}: {
11191110
period: DateRange;
11201111
} & TargetSelector) {
1112+
this.logger.debug(
1113+
'Count coordinates of target. (organizationId=%s, projectId=%s, targetId=%s, period=%o)',
1114+
organization,
1115+
project,
1116+
target,
1117+
period,
1118+
);
1119+
11211120
await this.session.assertPerformAction({
11221121
action: 'project:describe',
11231122
organizationId: organization,
@@ -1132,6 +1131,15 @@ export class OperationsManager {
11321131
period,
11331132
});
11341133

1134+
this.logger.debug(
1135+
'%d coordinates found. (organizationId=%s, projectId=%s, targetId=%s, period=%o)',
1136+
rows.length,
1137+
organization,
1138+
project,
1139+
target,
1140+
period,
1141+
);
1142+
11351143
const records: {
11361144
[coordinate: string]: {
11371145
total: number;

packages/services/api/src/modules/operations/providers/operations-reader.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,6 +2140,8 @@ export class OperationsReader {
21402140
}
21412141

21422142
async countCoordinatesOfTarget({ target, period }: { target: string; period: DateRange }) {
2143+
this.logger.debug('Count coordinates of target. (targetId=%s, period=%o)', target, period);
2144+
21432145
const result = await this.clickHouse.query<{
21442146
coordinate: string;
21452147
total: number;
@@ -2159,6 +2161,8 @@ export class OperationsReader {
21592161
}),
21602162
);
21612163

2164+
this.logger.debug('%d rows found. (targetId=%s, period=%o)', result.rows, target, period);
2165+
21622166
return result.data.map(row => ({
21632167
coordinate: row.coordinate,
21642168
total: ensureNumber(row.total),

packages/services/api/src/modules/schema/resolvers/DeprecatedSchemaExplorer.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
import { OperationsManager } from '../../operations/providers/operations-manager';
2-
import { buildGraphQLTypesFromSDL, withUsedByClients } from '../utils';
2+
import { buildGraphQLTypesFromSDL, memo, withUsedByClients } from '../utils';
33
import type { DeprecatedSchemaExplorerResolvers } from './../../../__generated__/types';
44

55
export const DeprecatedSchemaExplorer: DeprecatedSchemaExplorerResolvers = {
66
types: ({ sdl, supergraph, usage }, _, { injector }) => {
77
const operationsManager = injector.get(OperationsManager);
88

9-
async function getStats(typename: string) {
10-
const stats = await operationsManager.countCoordinatesOfTarget({
11-
targetId: usage.targetId,
12-
organizationId: usage.organizationId,
13-
projectId: usage.projectId,
14-
period: usage.period,
15-
});
9+
// NOTE: this function is super heavy.
10+
const getStats = memo(
11+
async function _getStats(typename: string) {
12+
const stats = await operationsManager.countCoordinatesOfTarget({
13+
targetId: usage.targetId,
14+
organizationId: usage.organizationId,
15+
projectId: usage.projectId,
16+
period: usage.period,
17+
});
1618

17-
return withUsedByClients(stats, {
18-
selector: usage,
19-
period: usage.period,
20-
operationsManager,
21-
typename,
22-
});
23-
}
19+
return withUsedByClients(stats, {
20+
selector: usage,
21+
period: usage.period,
22+
operationsManager,
23+
typename,
24+
});
25+
},
26+
arg => arg,
27+
);
2428

2529
return buildGraphQLTypesFromSDL(sdl, getStats, supergraph).sort((a, b) =>
2630
a.entity.name.localeCompare(b.entity.name),

packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
Kind,
2323
} from 'graphql';
2424
import { OperationsManager } from '../../operations/providers/operations-manager';
25-
import { withUsedByClients } from '../utils';
25+
import { memo, withUsedByClients } from '../utils';
2626
import type { SchemaExplorerResolvers } from './../../../__generated__/types';
2727

2828
export const SchemaExplorer: SchemaExplorerResolvers = {
@@ -162,21 +162,24 @@ export const SchemaExplorer: SchemaExplorerResolvers = {
162162
const typeMap = schema.getTypeMap();
163163
const operationsManager = injector.get(OperationsManager);
164164

165-
async function getStats(typename: string) {
166-
const stats = await operationsManager.countCoordinatesOfTarget({
167-
targetId: usage.targetId,
168-
organizationId: usage.organizationId,
169-
projectId: usage.projectId,
170-
period: usage.period,
171-
});
165+
const getStats = memo(
166+
async function _getStats(typename: string) {
167+
const stats = await operationsManager.countCoordinatesOfTarget({
168+
targetId: usage.targetId,
169+
organizationId: usage.organizationId,
170+
projectId: usage.projectId,
171+
period: usage.period,
172+
});
172173

173-
return withUsedByClients(stats, {
174-
selector: usage,
175-
period: usage.period,
176-
operationsManager,
177-
typename,
178-
});
179-
}
174+
return withUsedByClients(stats, {
175+
selector: usage,
176+
period: usage.period,
177+
operationsManager,
178+
typename,
179+
});
180+
},
181+
arg => arg,
182+
);
180183

181184
for (const typename in typeMap) {
182185
if (typename.startsWith('__')) {

packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createPeriod, parseDateRangeInput } from '../../../shared/helpers';
22
import { buildASTSchema } from '../../../shared/schema';
33
import { OperationsManager } from '../../operations/providers/operations-manager';
4+
import { Logger } from '../../shared/providers/logger';
45
import { onlyDeprecatedDocumentNode } from '../lib/deprecated-graphql';
56
import { extractSuperGraphInformation } from '../lib/federation-super-graph';
67
import { stripUsedSchemaCoordinatesFromDocumentNode } from '../lib/unused-graphql';
@@ -163,6 +164,16 @@ export const SchemaVersion: SchemaVersionResolvers = {
163164
};
164165
},
165166
deprecatedSchema: async (version, args, { injector }) => {
167+
const logger = injector.get(Logger);
168+
169+
logger.debug(
170+
'Build deprecated schema explorer. (organizationId=%s, projectId=%s, targetId=%s, schemaVersionId=%s)',
171+
version.organizationId,
172+
version.projectId,
173+
version.targetId,
174+
version.id,
175+
);
176+
166177
const [schemaAst, supergraphAst] = await Promise.all([
167178
injector.get(SchemaVersionHelper).getCompositeSchemaAst(version),
168179
injector.get(SchemaVersionHelper).getSupergraphAst(version),
@@ -178,8 +189,26 @@ export const SchemaVersion: SchemaVersionResolvers = {
178189
? parseDateRangeInput(args.period.absoluteRange)
179190
: createPeriod('30d');
180191

192+
logger.debug(
193+
'Start filtering full schema SDL into deprecated schema SDL. (organizationId=%s, projectId=%s, targetId=%s, schemaVersionId=%s)',
194+
version.organizationId,
195+
version.projectId,
196+
version.targetId,
197+
version.id,
198+
);
199+
200+
const filteredSdl = onlyDeprecatedDocumentNode(schemaAst);
201+
202+
logger.debug(
203+
'Finished filtering full schema SDL into deprecated schema SDL. (organizationId=%s, projectId=%s, targetId=%s, schemaVersionId=%s)',
204+
version.organizationId,
205+
version.projectId,
206+
version.targetId,
207+
version.id,
208+
);
209+
181210
return {
182-
sdl: onlyDeprecatedDocumentNode(schemaAst),
211+
sdl: filteredSdl,
183212
usage: {
184213
period,
185214
organizationId: version.organizationId,

packages/services/api/src/modules/schema/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,20 @@ export function stringifyDefaultValue(value: unknown): string | null {
385385
}
386386
return null;
387387
}
388+
389+
export function memo<R, A, K>(fn: (arg: A) => R, cacheKeyFn: (arg: A) => K): (arg: A) => R {
390+
let memoizedResult: R | null = null;
391+
let memoizedKey: K | null = null;
392+
393+
return (arg: A) => {
394+
const currentKey = cacheKeyFn(arg);
395+
if (memoizedKey === currentKey) {
396+
return memoizedResult!;
397+
}
398+
399+
memoizedKey = currentKey;
400+
memoizedResult = fn(arg);
401+
402+
return memoizedResult;
403+
};
404+
}

0 commit comments

Comments
 (0)