Skip to content

Commit 473ecb2

Browse files
committed
feat: disable chunking for metric charts
1 parent 3e73289 commit 473ecb2

File tree

3 files changed

+152
-13
lines changed

3 files changed

+152
-13
lines changed

packages/app/src/hooks/__tests__/useChartConfig.test.tsx

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React from 'react';
22
import { ResponseJSON } from '@hyperdx/common-utils/dist/clickhouse';
33
import { ClickhouseClient } from '@hyperdx/common-utils/dist/clickhouse/browser';
4-
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
4+
import {
5+
ChartConfigWithOptDateRange,
6+
MetricsDataType,
7+
} from '@hyperdx/common-utils/dist/types';
58
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
69
import { renderHook, waitFor } from '@testing-library/react';
710

@@ -97,6 +100,48 @@ describe('useChartConfig', () => {
97100
).toEqual([undefined]);
98101
});
99102

103+
it('returns windows aligned to the granularity if the granularity is auto', () => {
104+
expect(
105+
getGranularityAlignedTimeWindows(
106+
{
107+
dateRange: [
108+
new Date('2023-01-10 00:00:00'),
109+
new Date('2023-01-10 01:00:00'),
110+
],
111+
granularity: 'auto', // will be 1 minute
112+
timestampValueExpression: 'TimestampTime',
113+
} as ChartConfigWithOptDateRange,
114+
[
115+
30, // 30s
116+
5 * 60, // 5m
117+
60 * 60, // 1hr
118+
],
119+
),
120+
).toEqual([
121+
{
122+
dateRange: [
123+
new Date('2023-01-10 00:59:00'), // Aligned to minute, the auto-inferred granularity
124+
new Date('2023-01-10 01:00:00'),
125+
],
126+
dateRangeEndInclusive: undefined,
127+
},
128+
{
129+
dateRange: [
130+
new Date('2023-01-10 00:54:00'),
131+
new Date('2023-01-10 00:59:00'),
132+
],
133+
dateRangeEndInclusive: false,
134+
},
135+
{
136+
dateRange: [
137+
new Date('2023-01-10 00:00:00'),
138+
new Date('2023-01-10 00:54:00'),
139+
],
140+
dateRangeEndInclusive: false,
141+
},
142+
]);
143+
});
144+
100145
it('returns windows aligned to the granularity if the granularity is larger than the window size', () => {
101146
expect(
102147
getGranularityAlignedTimeWindows(
@@ -436,6 +481,81 @@ describe('useChartConfig', () => {
436481
});
437482
});
438483

484+
it('fetches data without chunking for metric chart configs', async () => {
485+
const config: ChartConfigWithOptDateRange = {
486+
select: [
487+
{
488+
aggFn: 'min',
489+
aggCondition: '',
490+
aggConditionLanguage: 'lucene',
491+
valueExpression: 'Value',
492+
metricName: 'system.network.io',
493+
metricType: MetricsDataType.Sum,
494+
},
495+
],
496+
where: '',
497+
whereLanguage: 'lucene',
498+
granularity: '1 minute',
499+
from: {
500+
databaseName: 'default',
501+
tableName: '',
502+
},
503+
timestampValueExpression: 'TimeUnix',
504+
dateRange: [
505+
new Date('2025-10-06T18:35:47.599Z'),
506+
new Date('2025-10-10T19:35:47.599Z'),
507+
],
508+
connection: 'foo',
509+
metricTables: {
510+
gauge: 'otel_metrics_gauge',
511+
histogram: 'otel_metrics_histogram',
512+
sum: 'otel_metrics_sum',
513+
summary: '',
514+
'exponential histogram': '',
515+
},
516+
limit: {
517+
limit: 100000,
518+
},
519+
};
520+
521+
const mockResponse = createMockQueryResponse([
522+
{
523+
'count()': '71',
524+
SeverityText: 'info',
525+
__hdx_time_bucket: '2025-10-01T00:00:00Z',
526+
},
527+
{
528+
'count()': '73',
529+
SeverityText: 'info',
530+
__hdx_time_bucket: '2025-10-02T00:00:00Z',
531+
},
532+
]);
533+
534+
mockClickhouseClient.queryChartConfig.mockResolvedValue(mockResponse);
535+
536+
const { result } = renderHook(() => useQueriedChartConfig(config), {
537+
wrapper,
538+
});
539+
540+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
541+
await waitFor(() => expect(result.current.isFetching).toBe(false));
542+
543+
// Should only be called once since chunking is disabled without timestampValueExpression
544+
expect(mockClickhouseClient.queryChartConfig).toHaveBeenCalledTimes(1);
545+
expect(mockClickhouseClient.queryChartConfig).toHaveBeenCalledWith({
546+
config,
547+
metadata: expect.any(Object),
548+
opts: {
549+
abort_signal: expect.any(AbortSignal),
550+
},
551+
});
552+
expect(result.current.data).toEqual({
553+
data: mockResponse.data,
554+
meta: mockResponse.meta,
555+
rows: mockResponse.rows,
556+
});
557+
});
558+
439559
it('fetches data without chunking when disableQueryChunking is true', async () => {
440560
const config = createMockChartConfig({
441561
dateRange: [

packages/app/src/hooks/useChartConfig.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import {
77
import { ClickhouseClient } from '@hyperdx/common-utils/dist/clickhouse/browser';
88
import {
99
DEFAULT_AUTO_GRANULARITY_MAX_BUCKETS,
10+
isMetricChartConfig,
1011
isUsingGranularity,
1112
renderChartConfig,
1213
} from '@hyperdx/common-utils/dist/renderChartConfig';
1314
import { format } from '@hyperdx/common-utils/dist/sqlFormatter';
14-
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
15+
import {
16+
ChartConfigWithDateRange,
17+
ChartConfigWithOptDateRange,
18+
} from '@hyperdx/common-utils/dist/types';
1519
import {
1620
experimental_streamedQuery as streamedQuery,
1721
useQuery,
@@ -46,15 +50,31 @@ type TimeWindow = {
4650

4751
type TQueryFnData = Pick<ResponseJSON<any>, 'data' | 'meta' | 'rows'> & {};
4852

49-
export const getGranularityAlignedTimeWindows = (
53+
const shouldUseChunking = (
5054
config: ChartConfigWithOptDateRange,
51-
windowDurationsSeconds?: number[],
52-
): TimeWindow[] | [undefined] => {
55+
): config is ChartConfigWithDateRange & {
56+
granularity: string;
57+
} => {
5358
// Granularity is required for chunking, otherwise we could break other group-bys.
54-
if (!isUsingGranularity(config)) return [undefined];
59+
if (!isUsingGranularity(config)) return false;
5560

5661
// Date range is required for chunking, otherwise we'd have infinite chunks, or some unbounded chunk(s).
57-
if (!config.dateRange) return [undefined];
62+
if (!config.dateRange) return false;
63+
64+
// TODO: enable chunking for metric charts when we're confident chunking will not break
65+
// complex metric queries.
66+
if (isMetricChartConfig(config)) return false;
67+
68+
console.log('Chunking enabled for chart config:', config);
69+
70+
return true;
71+
};
72+
73+
export const getGranularityAlignedTimeWindows = (
74+
config: ChartConfigWithOptDateRange,
75+
windowDurationsSeconds?: number[],
76+
): TimeWindow[] | [undefined] => {
77+
if (!shouldUseChunking(config)) return [undefined];
5878

5979
const [startDate, endDate] = config.dateRange;
6080
const windowsUnaligned = generateTimeWindowsDescending(
@@ -64,7 +84,7 @@ export const getGranularityAlignedTimeWindows = (
6484
);
6585

6686
const granularity =
67-
config.granularity === 'auto' // TODO test this
87+
config.granularity === 'auto'
6888
? convertDateRangeToGranularityString(
6989
config.dateRange,
7090
DEFAULT_AUTO_GRANULARITY_MAX_BUCKETS,
@@ -142,15 +162,14 @@ async function* fetchDataInChunks(
142162
* A hook providing data queried based on the provided chart config.
143163
*
144164
* If all of the following are true, the query will be chunked into multiple smaller queries:
145-
* - The config includes a date range
146-
* - The config includes a granularity
165+
* - The config includes a dateRange, granularity, and timestampValueExpression
147166
* - `options.disableQueryChunking` is falsy
148167
*
149168
* For chunked queries, note the following:
150-
* - The config's limit, if provided, is applied to each chunk, so the total number
169+
* - `config.limit`, if provided, is applied to each chunk, so the total number
151170
* of rows returned may be up to `limit * number_of_chunks`.
152171
* - The returned data will be ordered within each chunk, and chunks will
153-
* be ordered oldest-first, by the timestampValueExpression.
172+
* be ordered oldest-first, by the `timestampValueExpression`.
154173
*/
155174
export function useQueriedChartConfig(
156175
config: ChartConfigWithOptDateRange,

packages/app/src/hooks/usePatterns.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ function usePatterns({
145145
configWithPrimaryAndPartitionKey ?? config, // `config` satisfying type, never used due to `enabled` check
146146
{
147147
enabled: configWithPrimaryAndPartitionKey != null && enabled,
148-
// Disable chunking so that we get a uniform sample across the entire date range
148+
// Disable chunking to ensure we get the desired sample size
149149
disableQueryChunking: true,
150150
},
151151
);

0 commit comments

Comments
 (0)