Skip to content

Commit e5c77da

Browse files
committed
perf: Improve getKeyValues query performance for JSON keys
1 parent 0325416 commit e5c77da

File tree

3 files changed

+21
-85
lines changed

3 files changed

+21
-85
lines changed

.changeset/sweet-vans-pump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/common-utils": patch
3+
---
4+
5+
perf: Improve getKeyValues query performance for JSON keys

packages/common-utils/src/__tests__/metadata.test.ts

Lines changed: 5 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -251,22 +251,6 @@ describe('Metadata', () => {
251251
],
252252
}),
253253
});
254-
255-
// Mock getColumns to return columns including materialized ones
256-
mockCache.getOrFetch.mockImplementation((key, queryFn) => {
257-
if (key.includes('.columns')) {
258-
return Promise.resolve([
259-
{ name: 'regular_column', type: 'String', default_type: '' },
260-
{
261-
name: 'materialized_column',
262-
type: 'String',
263-
default_type: 'MATERIALIZED',
264-
},
265-
{ name: 'default_column', type: 'String', default_type: 'DEFAULT' },
266-
]);
267-
}
268-
return queryFn();
269-
});
270254
});
271255

272256
it('should apply row limit when disableRowLimit is false', async () => {
@@ -355,71 +339,20 @@ describe('Metadata', () => {
355339
expect(result).toEqual([{ key: 'column1', value: ['value1', 'value2'] }]);
356340
});
357341

358-
it('should include materialized fields when selecting all columns', async () => {
359-
const renderChartConfigSpy = jest.spyOn(
360-
renderChartConfigModule,
361-
'renderChartConfig',
362-
);
363-
364-
await metadata.getKeyValues({
365-
chartConfig: mockChartConfig,
366-
keys: ['column1'],
367-
limit: 10,
368-
});
369-
370-
// Verify that renderChartConfig was called with the expanded select list
371-
// that includes all columns by name (including materialized ones)
372-
expect(renderChartConfigSpy).toHaveBeenCalledWith(
373-
expect.objectContaining({
374-
with: [
375-
expect.objectContaining({
376-
name: 'sampledData',
377-
chartConfig: expect.objectContaining({
378-
// Should expand to all column names instead of using '*'
379-
select:
380-
'`regular_column`, `materialized_column`, `default_column`',
381-
}),
382-
}),
383-
],
384-
}),
385-
metadata,
386-
);
387-
});
388-
389-
it('should fallback to * when no columns are found', async () => {
390-
// Mock getColumns to return empty array
391-
mockCache.getOrFetch.mockImplementation((key, queryFn) => {
392-
if (key.includes('.columns')) {
393-
return Promise.resolve([]);
394-
}
395-
return queryFn();
396-
});
397-
342+
it('should return an empty list when no keys are provided', async () => {
398343
const renderChartConfigSpy = jest.spyOn(
399344
renderChartConfigModule,
400345
'renderChartConfig',
401346
);
402347

403-
await metadata.getKeyValues({
348+
const results = await metadata.getKeyValues({
404349
chartConfig: mockChartConfig,
405-
keys: ['column1'],
350+
keys: [],
406351
limit: 10,
407352
});
408353

409-
// Should fallback to '*' when no columns are found
410-
expect(renderChartConfigSpy).toHaveBeenCalledWith(
411-
expect.objectContaining({
412-
with: [
413-
expect.objectContaining({
414-
name: 'sampledData',
415-
chartConfig: expect.objectContaining({
416-
select: '*',
417-
}),
418-
}),
419-
],
420-
}),
421-
metadata,
422-
);
354+
expect(results).toEqual([]);
355+
expect(renderChartConfigSpy).not.toHaveBeenCalled();
423356
});
424357
});
425358

packages/common-utils/src/metadata.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -668,29 +668,22 @@ export class Metadata {
668668
return this.cache.getOrFetch(
669669
`${chartConfig.connection}.${chartConfig.from.databaseName}.${chartConfig.from.tableName}.${keys.join(',')}.${chartConfig.dateRange.toString()}.${disableRowLimit}.values`,
670670
async () => {
671-
const selectClause = keys
672-
.map((k, i) => `groupUniqArray(${limit})(${k}) AS param${i}`)
673-
.join(', ');
671+
if (keys.length === 0) return [];
674672

675673
// When disableRowLimit is true, query directly without CTE
676674
// Otherwise, use CTE with row limits for sampling
677675
const sqlConfig = disableRowLimit
678676
? {
679677
...chartConfig,
680-
select: selectClause,
678+
select: keys
679+
.map((k, i) => `groupUniqArray(${limit})(${k}) AS param${i}`)
680+
.join(', '),
681681
}
682682
: await (async () => {
683-
// Get all columns including materialized ones
684-
const columns = await this.getColumns({
685-
databaseName: chartConfig.from.databaseName,
686-
tableName: chartConfig.from.tableName,
687-
connectionId: chartConfig.connection,
688-
});
689-
690683
// Build select expression that includes all columns by name
691684
// This ensures materialized columns are included
692685
const selectExpr =
693-
columns.map(col => `\`${col.name}\``).join(', ') || '*';
686+
keys.map((k, i) => `${k} as param${i}`).join(', ') || '*';
694687

695688
return {
696689
with: [
@@ -710,7 +703,12 @@ export class Metadata {
710703
isSubquery: true,
711704
},
712705
],
713-
select: selectClause,
706+
select: keys
707+
.map(
708+
(_, i) =>
709+
`groupUniqArray(${limit})(param${i}) AS param${i}`,
710+
)
711+
.join(', '),
714712
connection: chartConfig.connection,
715713
from: { databaseName: '', tableName: 'sampledData' },
716714
where: '',

0 commit comments

Comments
 (0)