perf(query): optimize large-value result diagnostics

This commit is contained in:
zipg authored and GitHub committed 2026-08-14 00:11:28 +08:00
1 parent 9c50cc789b
commit 35eab8a8b1
6 files changed
+429 -33

No files matched your search

@@ -1,5 +1,34 @@
import { describe, expect, test, vi } from "vitest";
import { backendResponseError, importAgentDriver, installJdbcPluginLocal } from "@/lib/backend/http";
import { backendResponseError, executeMulti, importAgentDriver, installJdbcPluginLocal } from "@/lib/backend/http";
import { getDebugLogText } from "@/lib/backend/debugLog";
class MemoryStorage implements Storage {
private data = new Map<string, string>();
get length() {
return this.data.size;
}
clear() {
this.data.clear();
}
getItem(key: string) {
return this.data.get(key) ?? null;
}
key(index: number) {
return [...this.data.keys()][index] ?? null;
}
removeItem(key: string) {
this.data.delete(key);
}
setItem(key: string, value: string) {
this.data.set(key, value);
}
}
import { BackendErrorException } from "@/lib/backend/errorUtils";
const envelope = {
@@ -70,3 +99,60 @@ describe("HTTP backend error parsing", () => {
vi.unstubAllGlobals();
});
});
describe("HTTP query transport diagnostics", () => {
test("records timing and payload sizes without logging query contents", async () => {
vi.stubGlobal("localStorage", new MemoryStorage());
const sql = "SELECT 'private-query-marker'";
localStorage.setItem("dbx-debug-logging-enabled", "1");
localStorage.removeItem("dbx-debug-log-entries");
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
new Response(JSON.stringify([{ columns: ["value"], rows: [["private-cell-marker"]] }]), {
status: 200,
headers: {
"content-type": "application/json",
"x-dbx-core-ms": "41",
"x-dbx-serialize-ms": "7",
},
}),
),
);
await executeMulti("private-connection-marker", "private-database-marker", sql, undefined, "trace-12345678");
const logs = getDebugLogText();
expect(logs).toContain("[DBX][query-transport:http]");
expect(logs).toContain('"backendCoreMs":"41"');
expect(logs).toContain('"backendSerializeMs":"7"');
expect(logs).toContain('"requestBytes":');
expect(logs).toContain('"responseBytes":');
expect(logs).toContain('"jsonParseMs":');
expect(logs).not.toContain("private-query-marker");
expect(logs).not.toContain("private-cell-marker");
expect(logs).not.toContain("private-connection-marker");
expect(logs).not.toContain("private-database-marker");
vi.unstubAllGlobals();
});
test("preserves structured backend errors while logging only safe failure metadata", async () => {
vi.stubGlobal("localStorage", new MemoryStorage());
localStorage.setItem("dbx-debug-logging-enabled", "1");
localStorage.removeItem("dbx-debug-log-entries");
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify(envelope), { status: 400 })));
const error = await executeMulti("connection-1", "database-1", "SELECT 'failed-query-marker'", undefined, "trace-87654321").catch((value: unknown) => value);
expect(error).toBeInstanceOf(BackendErrorException);
expect(error).toMatchObject({ backendError: expect.objectContaining({ code: envelope.code }) });
const logs = getDebugLogText();
expect(logs).toContain("[DBX][query-transport:http:error]");
expect(logs).toContain('"status":400');
expect(logs).not.toContain("failed-query-marker");
expect(logs).not.toContain(envelope.detail);
vi.unstubAllGlobals();
});
});
+61 -7
View File
@@ -203,6 +203,7 @@ import type {
NacosSearchProgress,
} from "@/types/nacos";
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
import { normalizeConnectionTestResult } from "@/lib/connection/connectionDatabaseInfo";
import type { AnnotationFile, SchemaSnapshot } from "@/docs/types";
@@ -236,6 +237,55 @@ async function post<T>(url: string, body: unknown): Promise<T> {
return res.json();
}
async function postQueryWithDiagnostics<T>(url: string, body: unknown, traceId?: string): Promise<T> {
if (!isDebugLoggingEnabled()) return post(url, body);
const startedAt = performance.now();
const serializedBody = JSON.stringify(body);
const response = await fetch(apiUrl(url), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: serializedBody,
});
const headersAt = performance.now();
const responseText = await response.text();
const bodyAt = performance.now();
if (!response.ok) {
appendDebugLog("warn", "[DBX][query-transport:http:error]", {
traceId: traceId?.slice(0, 8),
status: response.status,
requestBytes: new TextEncoder().encode(serializedBody).byteLength,
responseBytes: new TextEncoder().encode(responseText).byteLength,
responseHeadersMs: Math.round(headersAt - startedAt),
responseBodyMs: Math.round(bodyAt - headersAt),
totalMs: Math.round(bodyAt - startedAt),
backendCoreMs: response.headers.get("x-dbx-core-ms"),
backendSerializeMs: response.headers.get("x-dbx-serialize-ms"),
});
throw await backendResponseError(
new Response(responseText, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
}),
);
}
const result = JSON.parse(responseText) as T;
const parsedAt = performance.now();
appendDebugLog("info", "[DBX][query-transport:http]", {
traceId: traceId?.slice(0, 8),
requestBytes: new TextEncoder().encode(serializedBody).byteLength,
responseBytes: new TextEncoder().encode(responseText).byteLength,
responseHeadersMs: Math.round(headersAt - startedAt),
responseBodyMs: Math.round(bodyAt - headersAt),
jsonParseMs: Math.round(parsedAt - bodyAt),
totalMs: Math.round(parsedAt - startedAt),
backendCoreMs: response.headers.get("x-dbx-core-ms"),
backendSerializeMs: response.headers.get("x-dbx-serialize-ms"),
});
return result;
}
async function get<T>(url: string): Promise<T> {
const res = await fetch(apiUrl(url));
if (!res.ok) throw await backendResponseError(res);
@@ -940,14 +990,18 @@ export async function executeMulti(
executionMode?: "simple";
},
): Promise<QueryResult[]> {
return post("/api/query/execute-multi", {
connectionId,
database,
sql,
schema,
return postQueryWithDiagnostics(
"/api/query/execute-multi",
{
connectionId,
database,
sql,
schema,
executionId,
...options,
},
executionId,
...options,
});
);
}
export interface ExecuteMultiProgress {
+20 -1
View File
@@ -3,6 +3,7 @@ import { BackendErrorException, type BackendError } from "@/lib/backend/errorUti
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { normalizeRustMongoCommand, type MongoCommand } from "@/lib/mongo/mongoShellCommand";
import { ExternalSqlFileTooLargeError } from "@/lib/sql/sqlFileOpen";
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
/** Normalize Tauri rejections once at the public backend boundary. */
async function invokeBackend<T>(command: string, args?: Record<string, unknown>): Promise<T> {
@@ -1159,8 +1160,10 @@ export async function executeMulti(
executionMode?: "simple";
},
): Promise<QueryResult[]> {
const diagnosticsEnabled = isDebugLoggingEnabled();
const startedAt = diagnosticsEnabled ? performance.now() : 0;
try {
return await invoke("execute_multi", {
const results = await invoke<QueryResult[]>("execute_multi", {
connectionId,
database,
sql,
@@ -1168,7 +1171,23 @@ export async function executeMulti(
executionId,
...options,
});
if (diagnosticsEnabled) {
appendDebugLog("info", "[DBX][query-transport:tauri]", {
traceId: executionId?.slice(0, 8),
totalMs: Math.round(performance.now() - startedAt),
resultCount: results.length,
rowCounts: results.map((result) => result.rows.length),
columnCounts: results.map((result) => result.columns.length),
});
}
return results;
} catch (error) {
if (diagnosticsEnabled) {
appendDebugLog("warn", "[DBX][query-transport:tauri:error]", {
traceId: executionId?.slice(0, 8),
totalMs: Math.round(performance.now() - startedAt),
});
}
throw new BackendErrorException(error);
}
}