Repository navigation
feat: skip filesystem cache writes for long time ranges #3631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@hyperdx/common-utils': minor | ||
| '@hyperdx/api': minor | ||
| '@hyperdx/app': minor | ||
| --- | ||
|
|
||
| feat: New per-source setting "Skip filesystem cache writes". Charts and searches over a time range longer than the chosen threshold set `read_from_filesystem_cache_if_exists_otherwise_bypass_cache = 1`, so long scans don't evict recently cached data. Off by default. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ import { | |
| useQueriedChartConfig, | ||
| } from '@/hooks/useChartConfig'; | ||
| import { useMVOptimizationExplanation } from '@/hooks/useMVOptimizationExplanation'; | ||
| import * as sourceModule from '@/source'; | ||
|
|
||
| // Mock DEFAULT_TIME_WINDOWS_SECONDS to remove the 15m window | ||
| jest.mock('@/utils/searchWindows', () => { | ||
|
|
@@ -1672,6 +1673,45 @@ describe('useChartConfig', () => { | |
| expect(result.current.isPending).toBe(false); | ||
| }); | ||
|
|
||
| it('applies the filesystem cache setting to every chunk based on the full range', async () => { | ||
| // 3 days, split into windows that are each shorter than the 2-day threshold. | ||
| const config = createMockChartConfig({ | ||
| dateRange: [ | ||
| new Date('2025-10-01 00:00:00Z'), | ||
| new Date('2025-10-04 00:00:00Z'), | ||
| ], | ||
| granularity: '1 hour', | ||
| }); | ||
| mockClickhouseClient.queryChartConfig.mockResolvedValue( | ||
| createMockQueryResponse([]), | ||
| ); | ||
| const useSourceSpy = jest | ||
| .spyOn(sourceModule, 'useSource') | ||
| .mockReturnValue({ | ||
| data: { filesystemCacheReadOnlyMinRange: '2 day' }, | ||
| isLoading: false, | ||
| } as any); | ||
|
|
||
| const { result } = renderHook( | ||
| () => useQueriedChartConfig(config, { enableQueryChunking: true }), | ||
| { wrapper }, | ||
| ); | ||
| await waitFor(() => expect(result.current.isFetching).toBe(false)); | ||
| useSourceSpy.mockRestore(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — useSource spy is only restored if waitFor succeeds
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
|
|
||
| const calls = mockClickhouseClient.queryChartConfig.mock.calls; | ||
| expect(calls.length).toBeGreaterThan(1); | ||
| for (const [{ querySettings }] of calls) { | ||
| expect(querySettings).toEqual([ | ||
| { | ||
| setting: | ||
| 'read_from_filesystem_cache_if_exists_otherwise_bypass_cache', | ||
| value: '1', | ||
| }, | ||
| ]); | ||
| } | ||
| }); | ||
|
|
||
| it('pins the series-limit ranking to the newest window on each chunk', async () => { | ||
| const config = createMockChartConfig({ | ||
| dateRange: [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ import { | |
| import { Metadata } from '@hyperdx/common-utils/dist/core/metadata'; | ||
| import { renderChartConfig } from '@hyperdx/common-utils/dist/core/renderChartConfig'; | ||
| import { | ||
| getSourceQuerySettings, | ||
| isFirstOrderByAscending, | ||
| isTimestampExpressionInFirstOrderBy, | ||
| MAX_TABLE_ROWS, | ||
|
|
@@ -257,7 +258,8 @@ const queryFn: QueryFunction<TQueryFnData, TQueryKey, TPageParam> = async ({ | |
| const query = await renderChartConfig( | ||
| windowedConfig, | ||
| metadata, | ||
| source?.querySettings, | ||
| // The full range, not the page's time window. | ||
| getSourceQuerySettings(source, config.dateRange), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — No test checks that search pages use the full range rather than the page window The only tests cover Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| ); | ||
|
|
||
| // Create abort signal from timeout if provided | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2647,6 +2647,15 @@ export const BaseSourceSchema = z.object({ | |
| }), | ||
| disabled: z.boolean().optional(), | ||
| querySettings: QuerySettingsSchema.optional(), | ||
| /** | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — New zod field copies the existing MinAutoGranularitySchema line for line (breaks the REQUIRED DRY rule)
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| * Queries whose selected time range is longer than this read the ClickHouse | ||
| * filesystem cache without filling it, so long scans don't evict hot data. | ||
| * Unset turns it off. '' (the form's "Never" option) is treated as unset. | ||
| */ | ||
| filesystemCacheReadOnlyMinRange: z.preprocess( | ||
| v => (v === '' ? undefined : v), | ||
| SQLIntervalSchema.optional(), | ||
| ), | ||
| timestampValueExpression: RequiredTimestampColumnSchema, | ||
| }); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Mongoose field copies the existing minAutoGranularityField
minAutoGranularityFieldin the same file (source.ts:285) is already{ type: String, set: v => (v === '' ? undefined : v) }. Rename it to something generic, such asoptionalIntervalField, move it abovesourceBaseSchema, and reuse it here instead of writing the setter a second time.Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond