Perf investigation - Time qgl query (#18911)

Added a new IS_GRAPHQL_QUERY_TIMING_ENABLED feature flag that, when
activated per workspace, logs the execution time and operation name of
every GraphQL query across both the standard Yoga pipeline and the
direct execution fast path. The timing context propagates via
AsyncLocalStorage to also instrument buildColumnsToSelect and
formatResult — giving a breakdown of column selection and result
transformation costs without any caller changes.
This commit is contained in:
Etienne
2026-03-24 14:41:15 +01:00
committed by GitHub
parent d3c7b0131d
commit 56ea79d98c
13 changed files with 181 additions and 11 deletions
@@ -1,3 +1,4 @@
import { Logger } from '@nestjs/common';
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
import { isNonEmptyString, isNull } from '@sniptt/guards';
@@ -26,6 +27,10 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { isQueryTimingEnabled } from 'src/engine/core-modules/graphql/storage/query-timing-context.storage';
const formatResultLogger = new Logger('formatResult');
export function formatResult<T>(
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
data: any,
@@ -33,6 +38,36 @@ export function formatResult<T>(
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
fieldMapsForObject?: FieldMapsForObject,
): T {
const timingEnabled = isQueryTimingEnabled();
const startTime = timingEnabled ? performance.now() : 0;
const result = formatResultInternal<T>(
data,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
fieldMapsForObject,
);
if (timingEnabled && isDefined(flatObjectMetadata)) {
const durationMs = (performance.now() - startTime).toFixed(2);
const recordCount = Array.isArray(data) ? data.length : 1;
formatResultLogger.log(
`${flatObjectMetadata.nameSingular}${durationMs}ms (${recordCount} records)`,
);
}
return result;
}
function formatResultInternal<T>(
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
data: any,
flatObjectMetadata: FlatObjectMetadata | undefined,
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
fieldMapsForObject?: FieldMapsForObject,
): T {
if (!isDefined(data)) {
return data;
@@ -41,7 +76,7 @@ export function formatResult<T>(
if (!isPlainObject(data)) {
if (Array.isArray(data)) {
return data.map((item) =>
formatResult(
formatResultInternal(
item,
flatObjectMetadata,
flatObjectMetadataMaps,
@@ -93,7 +128,7 @@ export function formatResult<T>(
if (!compositePropertyArgs && !isRelation) {
if (isPlainObject(value)) {
// @ts-expect-error legacy noImplicitAny
newData[key] = formatResult(
newData[key] = formatResultInternal(
value,
flatObjectMetadata,
flatObjectMetadataMaps,
@@ -130,7 +165,7 @@ export function formatResult<T>(
}
// @ts-expect-error legacy noImplicitAny
newData[key] = formatResult(
newData[key] = formatResultInternal(
value,
targetObjectMetadata,
flatObjectMetadataMaps,