fix(elasticsearch): clamp page size to max_result_window

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-08-26 22:07:34 +08:00
1 parent aac09d3cb7
commit ff88516235
3 files changed
+39 -7

No files matched your search

@@ -22,7 +22,7 @@ import type { DynamoDbIndexInfo, DynamoDbTableDescription } from "@/lib/backend/
import { useConnectionStore } from "@/stores/connectionStore";
import { clampSearchSplitWidth } from "@/lib/dataGrid/dataGridSearchSplit";
import { documentViewerFontStyle } from "@/lib/document/documentViewerFontStyle";
import { clampDocumentPage, documentPageRequestLimit, resetElasticsearchDocumentTotals, resolveElasticsearchDocumentTotals } from "@/lib/document/elasticsearchDocumentTotals";
import { clampDocumentPage, clampElasticsearchRequestLimit, documentPageRequestLimit, resetElasticsearchDocumentTotals, resolveElasticsearchDocumentTotals } from "@/lib/document/elasticsearchDocumentTotals";
import { canGoNextDocumentPage, isSameDocumentQueryTotalCountRequest, resolveDocumentQueryTotals, type DocumentQueryTotalCountRequest } from "@/lib/document/documentQueryTotals";
import {
arrayObjectAncestorPathForDocumentField,
@@ -246,7 +246,7 @@ const canGoNextPage = computed(() => {
});
});
const documentRequestLimit = computed(() => {
if (documentStoreProvider.value.kind !== "elasticsearch" || paginationTotal.value === undefined) return pageSize.value;
if (documentStoreProvider.value.kind !== "elasticsearch") return pageSize.value;
return documentPageRequestLimit(page.value, pageSize.value, paginationTotal.value);
});
@@ -1357,7 +1357,10 @@ async function load(options: { page?: number; append?: boolean; offset?: number;
throw new Error(t("dynamodb.pageCursorUnavailable"));
}
const skip = storeKind === "dynamodb" ? 0 : (options.offset ?? requestPage * pageSize.value);
const requestLimit = options.limit ?? (storeKind === "elasticsearch" && paginationTotal.value !== undefined ? documentPageRequestLimit(requestPage, pageSize.value, paginationTotal.value) : pageSize.value);
const requestedLimit = options.limit ?? (storeKind === "elasticsearch" ? documentPageRequestLimit(requestPage, pageSize.value, paginationTotal.value) : pageSize.value);
// Elasticsearch rejects from+size beyond index.max_result_window even when an explicit limit
// (e.g. from the per-tab page size control) bypassed the default-path clamp above.
const requestLimit = storeKind === "elasticsearch" ? clampElasticsearchRequestLimit(skip, requestedLimit) : requestedLimit;
const result = await api.documentFindDocuments(connectionId, database, collection, skip, requestLimit, filter, undefined, sort, undefined, executionId, cursor);
if (documentLoadExecutionId.value !== executionId) return;
if (connectionId !== props.connectionId || database !== props.database || collection !== props.collection || storeKind !== documentStoreProvider.value.kind) return;
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { clampDocumentPage, documentPageRequestLimit, resetElasticsearchDocumentTotals, resolveElasticsearchDocumentTotals } from "@/lib/document/elasticsearchDocumentTotals";
import { clampDocumentPage, clampElasticsearchRequestLimit, documentPageRequestLimit, resetElasticsearchDocumentTotals, resolveElasticsearchDocumentTotals } from "@/lib/document/elasticsearchDocumentTotals";
describe("Elasticsearch document totals", () => {
it("keeps a lower-bound search total separate from an exact background count", () => {
@@ -42,4 +42,18 @@ describe("Elasticsearch document totals", () => {
expect(documentPageRequestLimit(30, 333, 10_000)).toBe(10);
expect(documentPageRequestLimit(0, 100, undefined)).toBe(100);
});
it("caps the initial page request to Elasticsearch's default max_result_window even without a known total (#7231)", () => {
// Opening an index for the first time has no paginationTotal yet; a "rows per page" preference
// set above 10,000 must not be forwarded to Elasticsearch as-is, or the from+size search fails outright.
expect(documentPageRequestLimit(0, 100_000, undefined)).toBe(10_000);
expect(documentPageRequestLimit(0, 5_000, undefined)).toBe(5_000);
});
it("clamps an explicit request limit against the remaining from+size window at a given skip", () => {
expect(clampElasticsearchRequestLimit(0, 100_000)).toBe(10_000);
expect(clampElasticsearchRequestLimit(9_000, 5_000)).toBe(1_000);
expect(clampElasticsearchRequestLimit(20_000, 5_000)).toBe(0);
expect(clampElasticsearchRequestLimit(0, 500)).toBe(500);
});
});
@@ -41,9 +41,24 @@ export function clampDocumentPage(page: number, pageSize: number, paginationTota
return Math.min(normalizedPage, lastPage);
}
// Elasticsearch rejects any from+size combination above this by default (index.max_result_window).
// DBX can't know a target index's actual configured value up front, so it clamps requests to the
// out-of-the-box default rather than letting an oversized "rows per page" preference reach the cluster as-is.
export const ELASTICSEARCH_DEFAULT_MAX_RESULT_WINDOW = 10_000;
// Clamps a requested page size against the from+size window still available at a given skip offset,
// regardless of where the requested limit came from (a settings default or an explicit override).
export function clampElasticsearchRequestLimit(skip: number, limit: number): number {
const normalizedSkip = Math.max(0, Math.floor(skip));
const normalizedLimit = Math.max(1, Math.floor(limit));
return Math.min(normalizedLimit, Math.max(0, ELASTICSEARCH_DEFAULT_MAX_RESULT_WINDOW - normalizedSkip));
}
export function documentPageRequestLimit(page: number, pageSize: number, paginationTotal?: number): number {
const normalizedPageSize = Math.max(1, Math.floor(pageSize));
if (paginationTotal === undefined) return normalizedPageSize;
const remaining = paginationTotal - Math.max(0, Math.floor(page)) * normalizedPageSize;
return remaining > 0 ? Math.min(normalizedPageSize, remaining) : normalizedPageSize;
const skip = Math.max(0, Math.floor(page)) * normalizedPageSize;
const cappedPageSize = clampElasticsearchRequestLimit(skip, normalizedPageSize);
if (paginationTotal === undefined) return cappedPageSize;
const remaining = paginationTotal - skip;
return remaining > 0 ? Math.min(cappedPageSize, remaining) : cappedPageSize;
}