From e1553b4ba69a0dbfc2c9faafd66343ea44410379 Mon Sep 17 00:00:00 2001 From: Panagiotis Bailis Date: Thu, 3 Sep 2026 23:59:29 +0300 Subject: [PATCH 1/2] updating vector-sizing-calculator --- .../components/HeroSizeLine.tsx | 2 +- .../components/ResultsPanel.test.tsx | 88 +++++++++++++++++++ .../components/ResultsPanel.tsx | 59 ++++++------- .../formatNumbers.test.ts | 29 ++++++ .../VectorSizingCalculator/formatNumbers.ts | 16 ++++ .../VectorSizingCalculator/types.ts | 2 +- .../vector-sizing-calculator.css | 20 +++++ 7 files changed, 184 insertions(+), 32 deletions(-) create mode 100644 src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx create mode 100644 src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.test.ts diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx index 86e0a90895..d0ac0c113e 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx @@ -2,7 +2,7 @@ import { formatHeroSizeParts } from '../calculations' interface HeroSizeLineProps { bytes?: number - resourceLabel: 'Disk' | 'RAM' + resourceLabel: 'Disk' | 'Off-heap RAM' } export function HeroSizeLine({ bytes = 0, resourceLabel }: HeroSizeLineProps) { diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx new file mode 100644 index 0000000000..d86b9c6d0a --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx @@ -0,0 +1,88 @@ +import { calculate } from '../calculations' +import type { CalculatorInputs, ValidationResult } from '../types' +import { ResultsPanel } from './ResultsPanel' +import { EuiProvider } from '@elastic/eui' +import { render, screen } from '@testing-library/react' +import type { ComponentProps } from 'react' + +const inputs: CalculatorInputs = { + numVectors: 1_000_000, + numDimensions: 768, + elementType: 'float', + indexType: 'hnsw', + quantization: 'bbq', + replicas: 0, + hnswM: 16, + efConstruction: 100, + vectorsPerCluster: 384, + offHeapRamPercent: 10, +} + +const valid: ValidationResult = { valid: true } + +function renderPanel( + overrides: Partial> = {} +) { + const result = calculate(inputs) + return render( + + + + ) +} + +describe('ResultsPanel', () => { + it('labels RAM as off-heap and states disk relative to that working set', () => { + renderPanel() + + expect(screen.getAllByText(/Off-heap RAM/).length).toBeGreaterThan(0) + expect( + screen.getByText('Off-heap RAM per replica:') + ).toBeInTheDocument() + expect( + screen.getByText(/Disk is about .+ the off-heap RAM working set\./) + ).toBeInTheDocument() + expect( + screen.queryByText(/Disk : off-heap RAM/) + ).not.toBeInTheDocument() + }) + + it('scopes the estimate to self-managed and Elastic Cloud Hosted', () => { + renderPanel() + + expect( + screen.getByText( + /self-managed Elasticsearch and Elastic Cloud Hosted/ + ) + ).toBeInTheDocument() + expect( + screen.getByText(/not how you size Elastic Cloud Serverless/) + ).toBeInTheDocument() + expect(screen.getByText(/not JVM heap/)).toBeInTheDocument() + }) + + it('hides the compactness sentence and disclaimer when inputs are invalid', () => { + renderPanel({ result: null, inputsValid: false }) + + expect( + screen.queryByText(/off-heap RAM working set/) + ).not.toBeInTheDocument() + expect( + screen.queryByText(/Elastic Cloud Serverless/) + ).not.toBeInTheDocument() + expect( + screen.getByText('Off-heap RAM per replica:') + ).toBeInTheDocument() + }) +}) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx index 44a873007b..87f937802c 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx @@ -1,6 +1,7 @@ import { formatBytesString } from '../calculations' -import { formatGroupedInteger } from '../formatNumbers' +import { formatDiskToRamSentence, formatGroupedInteger } from '../formatNumbers' import type { SizingResult, ValidationResult } from '../types' +import { CalcToolTip } from './CalcToolTip' import { HeroSizeLine } from './HeroSizeLine' import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui' @@ -16,7 +17,10 @@ const KNN_MEMORY_DOC = 'https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search#_ensure_data_nodes_have_enough_memory' const SIZING_DISCLAIMER = - 'This calculator is a basic approximation of per-replica and cluster disk and RAM. Real requirements depend on data shape, indexing settings, query patterns, and how you deploy Elasticsearch.' + 'These estimates are vector-field disk and off-heap (page cache) RAM per copy, for self-managed Elasticsearch and Elastic Cloud Hosted. They are not JVM heap, not a full-node size, and not how you size Elastic Cloud Serverless. Actual needs still depend on data shape, indexing settings, and query patterns.' + +const DISK_TO_RAM_TIP = + 'This is how much disk this field uses relative to its off-heap RAM working set. A high ratio, typical of DiskBBQ, means most of the index can stay on disk. It is not a node type, not JVM heap, and not a serverless capacity number.' function clusterResourcesLabel(replicas: number): string { if (replicas === 0) { @@ -28,17 +32,6 @@ function clusterResourcesLabel(replicas: number): string { return `Cluster total (1 primary + ${formatGroupedInteger(replicas)} replicas):` } -function formatRatio(value: number): string { - return `${value.toFixed(value >= 10 ? 0 : 1)}×` -} - -function diskToRamRatioLabel(result: SizingResult): string { - if (result.diskToRamRatio <= 0) { - return '-' - } - return formatRatio(result.diskToRamRatio) -} - export function ResultsPanel({ result, inputsValid, @@ -47,6 +40,8 @@ export function ResultsPanel({ validation, }: ResultsPanelProps) { const showBody = inputsValid && result !== null && !validation.warning + const diskToRamSentence = + showBody && result ? formatDiskToRamSentence(result.diskToRamRatio) : '' return (
@@ -69,13 +64,16 @@ export function ResultsPanel({ />
) : (
- +
)} @@ -105,13 +103,7 @@ export function ResultsPanel({ size="s" className="vectorSizingCalc__detailLabel" > - RAM per replica: - - - Disk : off-heap RAM: + Off-heap RAM per replica:
@@ -145,17 +137,24 @@ export function ResultsPanel({ ? formatBytesString(result.totalRam) : '0 MiB'} - - {showBody && result - ? diskToRamRatioLabel(result) - : '-'} -
+ {diskToRamSentence && ( + + + {diskToRamSentence} + + + )} + { + it('uses no decimal places at 10× and above', () => { + expect(formatTimes(19.4)).toBe('19×') + expect(formatTimes(10)).toBe('10×') + }) + + it('keeps one decimal place below 10×', () => { + expect(formatTimes(1.54)).toBe('1.5×') + expect(formatTimes(2.3)).toBe('2.3×') + }) +}) + +describe('formatDiskToRamSentence', () => { + it('describes disk relative to the off-heap RAM working set', () => { + expect(formatDiskToRamSentence(19.4)).toBe( + 'Disk is about 19× the off-heap RAM working set.' + ) + expect(formatDiskToRamSentence(1.54)).toBe( + 'Disk is about 1.5× the off-heap RAM working set.' + ) + }) + + it('returns empty when the ratio is not meaningful', () => { + expect(formatDiskToRamSentence(0)).toBe('') + expect(formatDiskToRamSentence(-1)).toBe('') + }) +}) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts index cba1b3df9d..fc14ce57fb 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts @@ -10,6 +10,22 @@ export function formatExactBytes(bytes: number): string { return `${formatGroupedInteger(bytes)} bytes` } +/** Compact multiplier, e.g. `19×` or `1.5×`. */ +export function formatTimes(value: number): string { + return `${value.toFixed(value >= 10 ? 0 : 1)}×` +} + +/** + * Compactness of this field: disk ÷ off-heap RAM working set. + * Empty when the ratio is not meaningful. + */ +export function formatDiskToRamSentence(ratio: number): string { + if (ratio <= 0) { + return '' + } + return `Disk is about ${formatTimes(ratio)} the off-heap RAM working set.` +} + /** Normalize user input: commas = thousands, dot = decimal; accepts legacy dot-grouping. */ export function normalizeGroupedNumberInput(raw: string): string { const trimmed = raw.trim() diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/types.ts b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/types.ts index eced103b85..59a21fe556 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/types.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/types.ts @@ -55,7 +55,7 @@ export interface SizingResult { totalRam: number clusterDisk: number clusterRam: number - /** Disk ÷ off-heap RAM ratio (per replica). */ + /** Disk ÷ off-heap RAM working set (per replica). */ diskToRamRatio: number /** Index copies = 1 primary + replicas. */ totalCopies: number diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css index 6945dc08b1..715b03f6bc 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css @@ -152,6 +152,26 @@ vector-sizing-calculator:not(:defined) { line-height: 20px !important; } +.vectorSizingCalc__diskRamSentence { + margin-top: 12px; +} + +.vectorSizingCalc__diskRamSentence .vectorSizingCalc__labelRow { + align-items: flex-start; + font-size: 14px; + line-height: 20px; + font-weight: 500; + color: #1d2a3e; + white-space: normal; +} + +.vectorSizingCalc__diskRamSentence + .vectorSizingCalc__labelRow + > span:first-child { + flex: 1 1 auto; + min-width: 0; +} + .vectorSizingCalc__disclaimerFooter { margin-top: auto; padding-top: 16px; From f8efc935ac5d21abdbaa8ec3321214e1751e549a Mon Sep 17 00:00:00 2001 From: Panagiotis Bailis Date: Mon, 7 Sep 2026 11:33:31 +0300 Subject: [PATCH 2/2] minor updates to vector-sizing calculator --- .../components/HeroSizeLine.tsx | 2 +- .../components/ResultsPanel.test.tsx | 26 +++++++-------- .../components/ResultsPanel.tsx | 33 ++++++++----------- .../formatNumbers.test.ts | 6 ++-- .../VectorSizingCalculator/formatNumbers.ts | 4 +-- .../vector-sizing-calculator.css | 15 +++++---- 6 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx index d0ac0c113e..86e0a90895 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/HeroSizeLine.tsx @@ -2,7 +2,7 @@ import { formatHeroSizeParts } from '../calculations' interface HeroSizeLineProps { bytes?: number - resourceLabel: 'Disk' | 'Off-heap RAM' + resourceLabel: 'Disk' | 'RAM' } export function HeroSizeLine({ bytes = 0, resourceLabel }: HeroSizeLineProps) { diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx index d86b9c6d0a..3b5ea67786 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.test.tsx @@ -43,46 +43,42 @@ function renderPanel( } describe('ResultsPanel', () => { - it('labels RAM as off-heap and states disk relative to that working set', () => { + it('labels RAM plainly and states disk relative to RAM needed for search', () => { renderPanel() - expect(screen.getAllByText(/Off-heap RAM/).length).toBeGreaterThan(0) + expect(screen.getByText('RAM per replica:')).toBeInTheDocument() expect( - screen.getByText('Off-heap RAM per replica:') - ).toBeInTheDocument() - expect( - screen.getByText(/Disk is about .+ the off-heap RAM working set\./) + screen.getByText(/Disk is about .+ the RAM needed for search\./) ).toBeInTheDocument() + expect(screen.queryByText(/Off-heap RAM/)).not.toBeInTheDocument() expect( screen.queryByText(/Disk : off-heap RAM/) ).not.toBeInTheDocument() }) - it('scopes the estimate to self-managed and Elastic Cloud Hosted', () => { + it('calls out serverless on its own line and does not link away', () => { renderPanel() + expect( + screen.getByText('Does not apply to Elastic Cloud Serverless.') + ).toBeInTheDocument() expect( screen.getByText( /self-managed Elasticsearch and Elastic Cloud Hosted/ ) ).toBeInTheDocument() - expect( - screen.getByText(/not how you size Elastic Cloud Serverless/) - ).toBeInTheDocument() - expect(screen.getByText(/not JVM heap/)).toBeInTheDocument() + expect(screen.queryByText('Learn more')).not.toBeInTheDocument() }) it('hides the compactness sentence and disclaimer when inputs are invalid', () => { renderPanel({ result: null, inputsValid: false }) expect( - screen.queryByText(/off-heap RAM working set/) + screen.queryByText(/RAM needed for search/) ).not.toBeInTheDocument() expect( screen.queryByText(/Elastic Cloud Serverless/) ).not.toBeInTheDocument() - expect( - screen.getByText('Off-heap RAM per replica:') - ).toBeInTheDocument() + expect(screen.getByText('RAM per replica:')).toBeInTheDocument() }) }) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx index 87f937802c..0ae71e5f03 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/components/ResultsPanel.tsx @@ -3,7 +3,7 @@ import { formatDiskToRamSentence, formatGroupedInteger } from '../formatNumbers' import type { SizingResult, ValidationResult } from '../types' import { CalcToolTip } from './CalcToolTip' import { HeroSizeLine } from './HeroSizeLine' -import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui' +import { EuiHorizontalRule, EuiText } from '@elastic/eui' interface ResultsPanelProps { result: SizingResult | null @@ -13,14 +13,13 @@ interface ResultsPanelProps { validation: ValidationResult } -const KNN_MEMORY_DOC = - 'https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search#_ensure_data_nodes_have_enough_memory' +const SERVERLESS_NOTE = 'Does not apply to Elastic Cloud Serverless.' const SIZING_DISCLAIMER = - 'These estimates are vector-field disk and off-heap (page cache) RAM per copy, for self-managed Elasticsearch and Elastic Cloud Hosted. They are not JVM heap, not a full-node size, and not how you size Elastic Cloud Serverless. Actual needs still depend on data shape, indexing settings, and query patterns.' + 'These estimates are disk and RAM per copy for self-managed Elasticsearch and Elastic Cloud Hosted. This RAM is for fast search, not the Java heap, and is not a full node size. Actual needs still depend on data shape, indexing settings, and query patterns.' const DISK_TO_RAM_TIP = - 'This is how much disk this field uses relative to its off-heap RAM working set. A high ratio, typical of DiskBBQ, means most of the index can stay on disk. It is not a node type, not JVM heap, and not a serverless capacity number.' + 'This compares how much the field stores on disk with how much RAM it wants in the filesystem cache for fast search. A high number, typical of DiskBBQ, means most of the index can stay on disk. It is not a node type, not Java heap, and not a serverless capacity number.' function clusterResourcesLabel(replicas: number): string { if (replicas === 0) { @@ -64,16 +63,13 @@ export function ResultsPanel({ /> ) : (
- +
)} @@ -103,7 +99,7 @@ export function ResultsPanel({ size="s" className="vectorSizingCalc__detailLabel" > - Off-heap RAM per replica: + RAM per replica:
@@ -167,18 +163,17 @@ export function ResultsPanel({ {showBody && (
+ + {SERVERLESS_NOTE} + - {SIZING_DISCLAIMER}{' '} - - Learn more - + {SIZING_DISCLAIMER}
)} diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.test.ts b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.test.ts index 2122ae31f2..f2570788e1 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.test.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.test.ts @@ -13,12 +13,12 @@ describe('formatTimes', () => { }) describe('formatDiskToRamSentence', () => { - it('describes disk relative to the off-heap RAM working set', () => { + it('describes disk relative to the RAM needed for search', () => { expect(formatDiskToRamSentence(19.4)).toBe( - 'Disk is about 19× the off-heap RAM working set.' + 'Disk is about 19× the RAM needed for search.' ) expect(formatDiskToRamSentence(1.54)).toBe( - 'Disk is about 1.5× the off-heap RAM working set.' + 'Disk is about 1.5× the RAM needed for search.' ) }) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts index fc14ce57fb..84fcfddb71 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/formatNumbers.ts @@ -16,14 +16,14 @@ export function formatTimes(value: number): string { } /** - * Compactness of this field: disk ÷ off-heap RAM working set. + * Compactness of this field: disk ÷ RAM needed for search. * Empty when the ratio is not meaningful. */ export function formatDiskToRamSentence(ratio: number): string { if (ratio <= 0) { return '' } - return `Disk is about ${formatTimes(ratio)} the off-heap RAM working set.` + return `Disk is about ${formatTimes(ratio)} the RAM needed for search.` } /** Normalize user input: commas = thousands, dot = decimal; accepts legacy dot-grouping. */ diff --git a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css index 715b03f6bc..811cc0a0e6 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css +++ b/src/Elastic.Documentation.Site/Assets/web-components/VectorSizingCalculator/vector-sizing-calculator.css @@ -177,6 +177,15 @@ vector-sizing-calculator:not(:defined) { padding-top: 16px; } +.vectorSizingCalc__serverlessNote { + display: block; + margin-bottom: 8px; + font-size: 12px !important; + line-height: 16px !important; + font-weight: 600 !important; + color: #1d2a3e !important; +} + .vectorSizingCalc__byteSizeValue { display: inline-block; max-width: 100%; @@ -229,12 +238,6 @@ vector-sizing-calculator:not(:defined) { color: #798eaf !important; } -.vectorSizingCalc__disclaimer .euiLink { - font-size: 12px !important; - line-height: 16px !important; - font-weight: 400 !important; -} - .vectorSizingCalc__sectionTitle { font-size: 12px; line-height: 16px;