perf(execution): prewarm tab session pools and report perceived wait

This commit is contained in:
zipg authored and GitHub committed 2026-09-22 14:25:39 +08:00
1 parent 78315c377a
commit f468399fbd
17 files changed
+558 -29

No files matched your search

@@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => ({
activeConnectionId: "",
getConfig: vi.fn((connectionId: string) => ({ id: connectionId, db_type: "postgres" })),
ensureConnected: vi.fn(),
warmConnection: vi.fn(),
connectionIdentifierQuote: vi.fn(() => undefined),
// 与真实 store 一致:setTableMeta 记录连接元数据代次
metadataGenerationFor: vi.fn(() => 0),
@@ -15,6 +15,7 @@ const mocks = vi.hoisted(() => ({
openDataTabsNextToActive: false,
metadataGeneration: 0,
ensureConnected: vi.fn(),
warmConnection: vi.fn(),
executeTabSql: vi.fn(),
loadTableMetadata: vi.fn(),
buildTableSelectSql: vi.fn(),
@@ -26,6 +27,7 @@ vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: () => ({
getConfig: () => ({ id: "connection-1", db_type: mocks.databaseType }),
ensureConnected: mocks.ensureConnected,
warmConnection: mocks.warmConnection,
connectionIdentifierQuote: () => undefined,
metadataGenerationFor: () => mocks.metadataGeneration,
}),
@@ -237,6 +239,19 @@ describe("useSidebarDataOpenRuntime", () => {
expect(mocks.tabs).toHaveLength(2);
});
it("prewarms the tab session pool before the first page is fetched", async () => {
await useSidebarDataOpenRuntime().openData(tableNode);
const createdTab = mocks.tabs[0]!;
// The tab executes on its own session pool; warming it up front keeps the
// connection setup off the critical path of the visible loading spinner.
expect(mocks.warmConnection).toHaveBeenCalledWith("connection-1", {
database: "app",
catalog: undefined,
clientSessionId: createdTab.id,
});
});
it("keeps adjacent placement for every tab created in always-new mode", async () => {
mocks.dataTabReuseMode = "always-new";
mocks.openDataTabsNextToActive = true;
@@ -17,6 +17,7 @@ import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
import { tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
import { canActivateExistingDataTableTab } from "@/lib/tabs/dataTabActivation";
import { beginDataTabNavigation, endDataTabNavigation, isCurrentDataTabNavigation } from "@/lib/tabs/dataTabNavigationGeneration";
import { dataTabExecutionDatabase } from "@/lib/table/dataTabExecutionDatabase";
const DATA_TAB_METADATA_TTL_MS = TABLE_METADATA_CACHE_TTL_MS;
@@ -60,6 +61,21 @@ export function useSidebarDataOpenRuntime() {
});
lastPhaseAt = now;
};
// Each data tab executes on its own session-scoped pool
// (`<connection>:<database>[:catalog:..]:session:<tabId>`), so its first Run
// pays a fresh database connection. On a remote link that setup costs
// hundreds of milliseconds of round trips and lands on the critical path of
// the page fetch the user is waiting for. Start it in the background as soon
// as the tab exists (and again once the connection is up) so it overlaps the
// table-metadata load instead of the visible loading spinner.
const warmTabSessionPool = (targetTabId: string) => {
if (!config) return;
connectionStore.warmConnection(node.connectionId, {
database: dataTabExecutionDatabase(config, node.database, node.catalog),
catalog: node.catalog,
clientSessionId: targetTabId,
});
};
openDataLog("info", "start", {
traceId,
type: node.type,
@@ -215,6 +231,7 @@ export function useSidebarDataOpenRuntime() {
})();
openDataLog("info", "tab-created", { traceId, tabId, elapsed: elapsed() });
logPhase("tab-created", { tabId });
warmTabSessionPool(tabId);
// 接管该 tab:登记本次导航代次,作废在途的 openTableTarget/openData 旧代次
// ——旧导航即使目标身份相同(同表不同 whereInput)也不得再落地
const navigationToken = beginDataTabNavigation(tabId);
@@ -312,6 +329,7 @@ export function useSidebarDataOpenRuntime() {
}
openDataLog("info", "ensure-connected:done", { traceId, elapsed: elapsed() });
logPhase("ensure-connected", { tabId });
warmTabSessionPool(tabId);
if (!config) throw new Error("Connection config not found");
const limit = tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);