Optimize table data open latency
This commit is contained in:
1 parent
bd77670770
commit
33e0cfa831
9 files changed
+455
-97
No files matched your search
@@ -221,6 +221,10 @@ const props = defineProps<{
|
||||
}) => Promise<void>;
|
||||
}>();
|
||||
|
||||
const dataGridTraceId = uuid().slice(0, 8);
|
||||
const dataGridCreatedAt = performance.now();
|
||||
const dataGridElapsed = () => `${Math.round(performance.now() - dataGridCreatedAt)}ms`;
|
||||
|
||||
const emit = defineEmits<{
|
||||
reload: [sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number];
|
||||
paginate: [offset: number, limit: number, whereInput?: string, orderBy?: string];
|
||||
@@ -228,6 +232,48 @@ const emit = defineEmits<{
|
||||
"update:whereInput": [value: string];
|
||||
}>();
|
||||
|
||||
console.info("[DBX][DataGrid:setup]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
rowCount: props.result.rows.length,
|
||||
columnCount: props.result.columns.length,
|
||||
backendMs: props.result.execution_time_ms,
|
||||
loading: props.loading,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.result,
|
||||
(result) => {
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][DataGrid:result:prop]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
rowCount: result.rows.length,
|
||||
columnCount: result.columns.length,
|
||||
backendMs: result.execution_time_ms,
|
||||
loading: props.loading,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
nextTick(() => {
|
||||
console.info("[DBX][DataGrid:result:nextTick]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
loading: props.loading,
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
console.info("[DBX][DataGrid:result:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
loading: props.loading,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const hasData = computed(() => props.result.columns.length > 0);
|
||||
|
||||
const columnTypeMap = computed(() => {
|
||||
@@ -1971,15 +2017,36 @@ const displayItems = computed<RowItem[]>(() => {
|
||||
|
||||
watch(
|
||||
() => displayItems.value.length,
|
||||
() => {
|
||||
(length) => {
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][DataGrid:display-items:ready]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
displayItemCount: length,
|
||||
sourceRowCount: props.result.rows.length,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
nextTick(() => {
|
||||
const scrollerEl = gridRef.value?.querySelector<HTMLElement>(".data-grid-scroller");
|
||||
if (scrollerEl) {
|
||||
updateGridScrollbarGutter(scrollerEl);
|
||||
updateGridHorizontalViewport(scrollerEl);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
const renderedRows = gridRef.value?.querySelectorAll(".vue-recycle-scroller__item-view").length;
|
||||
console.info("[DBX][DataGrid:display-items:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
displayItemCount: length,
|
||||
renderedRows,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
loading: props.loading,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
interface SearchMatch {
|
||||
@@ -4158,12 +4225,27 @@ watch(
|
||||
() => props.loading,
|
||||
(isLoading) => {
|
||||
clearInterval(_loadingTimer);
|
||||
console.info(isLoading ? "[DBX][DataGrid:loading:start]" : "[DBX][DataGrid:loading:stop]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
if (isLoading) {
|
||||
_loadingStart = Date.now();
|
||||
loadingElapsed.value = 0;
|
||||
_loadingTimer = setInterval(() => {
|
||||
loadingElapsed.value = Date.now() - _loadingStart;
|
||||
}, 100);
|
||||
} else {
|
||||
nextTick(() => {
|
||||
requestAnimationFrame(() => {
|
||||
console.info("[DBX][DataGrid:loading:stop:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -153,8 +153,8 @@ const showPinnedDataTabsMenu = computed(
|
||||
const dataTabMenuItems = computed(() =>
|
||||
dataTabs.value.map((tab) => ({
|
||||
value: tab.id,
|
||||
label: tabDisplayTitle(tab),
|
||||
title: tabDisplayTitle(tab),
|
||||
label: tabDisplayTitle(tab, t),
|
||||
title: tabDisplayTitle(tab, t),
|
||||
icon: Table2,
|
||||
iconClass: "text-emerald-600 dark:text-emerald-400",
|
||||
})),
|
||||
@@ -248,7 +248,7 @@ const dataTabsMenuContainerClass = computed(() =>
|
||||
<PencilRuler v-else-if="tab.mode === 'structure'" class="h-3.5 w-3.5" />
|
||||
<Code2 v-else class="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span class="min-w-0 truncate flex-1">{{ tabDisplayTitle(tab) }}</span>
|
||||
<span class="min-w-0 truncate flex-1">{{ tabDisplayTitle(tab, t) }}</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
@@ -270,7 +270,7 @@ const dataTabsMenuContainerClass = computed(() =>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" class="text-xs grid grid-cols-[auto_1fr] gap-x-2">
|
||||
<template v-for="line in tabTooltipLines(tab)" :key="line.label">
|
||||
<template v-for="line in tabTooltipLines(tab, t)" :key="line.label">
|
||||
<span class="text-muted-foreground">{{ line.label }}</span>
|
||||
<span>{{ line.value }}</span>
|
||||
</template>
|
||||
|
||||
@@ -22,7 +22,13 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
||||
import QueryEditor from "@/components/editor/QueryEditor.vue";
|
||||
import ColumnInfoPanel from "@/components/editor/ColumnInfoPanel.vue";
|
||||
import type { ColumnInfo } from "@/components/editor/ColumnInfoPanel.vue";
|
||||
const DataGrid = defineAsyncComponent(() => import("@/components/grid/DataGrid.vue"));
|
||||
const DataGrid = defineAsyncComponent(async () => {
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][DataGrid:load:start]");
|
||||
const component = await import("@/components/grid/DataGrid.vue");
|
||||
console.info("[DBX][DataGrid:load:done]", { elapsed: `${Math.round(performance.now() - startedAt)}ms` });
|
||||
return component;
|
||||
});
|
||||
const RedisKeyBrowser = defineAsyncComponent(() => import("@/components/redis/RedisKeyBrowser.vue"));
|
||||
const MongoDocBrowser = defineAsyncComponent(() => import("@/components/mongo/MongoDocBrowser.vue"));
|
||||
const ObjectBrowser = defineAsyncComponent(() => import("@/components/objects/ObjectBrowser.vue"));
|
||||
@@ -171,6 +177,35 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeTab.result,
|
||||
(result) => {
|
||||
if (!result) return;
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][ContentArea:result:observed]", {
|
||||
tabId: props.activeTab.id,
|
||||
rowCount: result.rows.length,
|
||||
columnCount: result.columns.length,
|
||||
backendMs: result.execution_time_ms,
|
||||
isExecuting: props.activeTab.isExecuting,
|
||||
});
|
||||
nextTick(() => {
|
||||
console.info("[DBX][ContentArea:result:nextTick]", {
|
||||
tabId: props.activeTab.id,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
isExecuting: props.activeTab.isExecuting,
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
console.info("[DBX][ContentArea:result:first-frame]", {
|
||||
tabId: props.activeTab.id,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
isExecuting: props.activeTab.isExecuting,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeTab.isExecuting,
|
||||
(isExecuting, wasExecuting) => {
|
||||
@@ -499,7 +534,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
|
||||
class="inline-flex items-center rounded border border-border bg-muted/30 px-2 py-0.5 text-muted-foreground truncate"
|
||||
>
|
||||
<template v-if="activeTab.tableMeta?.schema">{{ activeTab.tableMeta.schema }}@</template
|
||||
>{{ databaseDisplayNameForTab(activeTab.connectionId, activeTab.database) }}
|
||||
>{{ databaseDisplayNameForTab(activeTab.connectionId, activeTab.database, t) }}
|
||||
</span>
|
||||
<span v-if="activeTab.tableMeta" class="ml-auto text-muted-foreground">
|
||||
{{ activeTab.tableMeta.columns.length }} {{ t("tree.columns") }}
|
||||
|
||||
@@ -65,7 +65,7 @@ import {
|
||||
setActiveTableReferencePayload,
|
||||
type QueryEditorTableReferencePayload,
|
||||
} from "@/lib/queryEditorTableDrop";
|
||||
import { editablePrimaryKeys, usesSyntheticRowIdKey } from "@/lib/tableEditing";
|
||||
import { editablePrimaryKeys } from "@/lib/tableEditing";
|
||||
import {
|
||||
supportsDatabaseCreation,
|
||||
supportsDatabaseSearch,
|
||||
@@ -523,49 +523,57 @@ async function openData() {
|
||||
if (!config) throw new Error("Connection config not found");
|
||||
|
||||
const querySchema = node.schema || node.database;
|
||||
console.info("[DBX][openData:get-columns:start]", {
|
||||
traceId,
|
||||
database: node.database,
|
||||
schema: querySchema,
|
||||
table: node.label,
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
const columns = await api.getColumns(node.connectionId, node.database, querySchema, node.label);
|
||||
console.info("[DBX][openData:get-columns:done]", {
|
||||
traceId,
|
||||
columnCount: columns.length,
|
||||
primaryKeys: columns.filter((column) => column.is_primary_key).map((column) => column.name),
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
const pks = editablePrimaryKeys(config.db_type, columns);
|
||||
const limit = settingsStore.editorSettings.pageSize;
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: config.db_type,
|
||||
schema: node.schema,
|
||||
tableName: node.label,
|
||||
columns: columns.map((column) => column.name),
|
||||
primaryKeys: pks,
|
||||
columns: [],
|
||||
primaryKeys: [],
|
||||
limit,
|
||||
includeRowId: usesSyntheticRowIdKey(config.db_type, pks),
|
||||
includeRowId: false,
|
||||
});
|
||||
console.info("[DBX][openData:sql-built]", {
|
||||
traceId,
|
||||
primaryKeys: pks,
|
||||
includeRowId: usesSyntheticRowIdKey(config.db_type, pks),
|
||||
primaryKeys: [],
|
||||
includeRowId: false,
|
||||
sql,
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
queryStore.updateSql(tabId, sql);
|
||||
queryStore.setTableMeta(tabId, {
|
||||
schema: node.schema,
|
||||
tableName: node.label,
|
||||
columns,
|
||||
primaryKeys: pks,
|
||||
});
|
||||
|
||||
const loadTableMeta = async () => {
|
||||
try {
|
||||
console.info("[DBX][openData:get-columns:start]", {
|
||||
traceId,
|
||||
database: node.database,
|
||||
schema: querySchema,
|
||||
table: node.label,
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
const columns = await api.getColumns(node.connectionId, node.database, querySchema, node.label);
|
||||
console.info("[DBX][openData:get-columns:done]", {
|
||||
traceId,
|
||||
columnCount: columns.length,
|
||||
primaryKeys: columns.filter((column) => column.is_primary_key).map((column) => column.name),
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
const pks = editablePrimaryKeys(config.db_type, columns);
|
||||
queryStore.setTableMeta(tabId, {
|
||||
schema: node.schema,
|
||||
tableName: node.label,
|
||||
columns,
|
||||
primaryKeys: pks,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("[DBX][openData:get-columns:error]", { traceId, elapsed: elapsed(), error });
|
||||
}
|
||||
};
|
||||
|
||||
console.info("[DBX][openData:execute:start]", { traceId, tabId, elapsed: elapsed() });
|
||||
await queryStore.executeTabSql(tabId, sql);
|
||||
console.info("[DBX][openData:execute:done]", { traceId, tabId, elapsed: elapsed() });
|
||||
void loadTableMeta();
|
||||
} catch (e: any) {
|
||||
console.error("[DBX][openData:error]", { traceId, elapsed: elapsed(), error: e });
|
||||
queryStore.setErrorResult(tabId, e);
|
||||
|
||||
Reference in new issue
Block a user