perf(redis): reduce large key search payload and render cost (#1672)
This commit is contained in:
1 parent
f29d393c37
commit
90fd77cb9e
8 files changed
+494
-30
No files matched your search
@@ -22,7 +22,7 @@ import type { RedisKeyInfo, RedisScanResult, RedisValue, HistoryEntry } from "@/
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { buildRedisKeyTree, collectExpandedGroupIds, collectRedisGroupKeyRaws, flattenVisibleRedisKeyTree, mergeKeysIntoRedisKeyTree, type RedisKeyTreeNode } from "@/lib/redisKeyTree";
|
||||
import { buildRedisKeyTree, collectExpandedGroupIds, collectRedisGroupKeyRaws, flattenVisibleRedisKeyTree, mergeKeysIntoRedisKeyTree, redisKeyToFlatTreeRow, type RedisKeyTreeNode } from "@/lib/redisKeyTree";
|
||||
import { classifyRedisCommandSafety } from "@/lib/redisCommandSafety";
|
||||
import { isRedisMutatingCommand } from "@/lib/redisCommandTable";
|
||||
import { isRedisClearScreenCommand, nextRedisCommandDb, redisKeyTextToRaw } from "@/lib/redisCommandSession";
|
||||
@@ -108,6 +108,7 @@ const valueQuery = computed(() => searchPattern.value.trim());
|
||||
const isValueSearchMode = computed(() => searchMode.value === "value" || searchMode.value === "all");
|
||||
const effectivePattern = computed(() => (searchMode.value === "key" ? redisKeySearchPattern(searchPattern.value, fuzzyKeySearch.value) : "*"));
|
||||
const isSearchMode = computed(() => (searchMode.value === "key" ? effectivePattern.value !== "*" : valueQuery.value !== ""));
|
||||
const useFlatKeySearchRows = computed(() => searchMode.value === "key" && isSearchMode.value);
|
||||
const searchPlaceholder = computed(() => {
|
||||
if (searchMode.value === "key") return fuzzyKeySearch.value ? t("redis.fuzzyPattern") : t("redis.pattern");
|
||||
return searchMode.value === "all" ? t("redis.allSearchPlaceholder") : t("redis.valueSearchPlaceholder");
|
||||
@@ -150,12 +151,13 @@ const createKeyTypeOptions = computed<{ value: RedisCreateKeyType; label: string
|
||||
{ value: "stream", label: "Stream" },
|
||||
{ value: "json", label: "JSON" },
|
||||
]);
|
||||
const visibleRows = computed(() =>
|
||||
flattenVisibleRedisKeyTree(treeKeys.value, expandedGroupIds.value).map((row) => ({
|
||||
const visibleRows = computed(() => {
|
||||
const rows = useFlatKeySearchRows.value ? flatKeys.value.map((key) => redisKeyToFlatTreeRow(key, props.db)) : flattenVisibleRedisKeyTree(treeKeys.value, expandedGroupIds.value);
|
||||
return rows.map((row) => ({
|
||||
...row,
|
||||
id: row.node.id,
|
||||
})),
|
||||
);
|
||||
}));
|
||||
});
|
||||
let commandHistoryId = 0;
|
||||
|
||||
function countLeaves(node: RedisKeyTreeNode): number {
|
||||
@@ -236,7 +238,10 @@ function appendScanResult(result: RedisScanResult, options: { updateTree?: boole
|
||||
}
|
||||
|
||||
if (options.updateTree ?? true) {
|
||||
if (treeKeys.value.length === 0) {
|
||||
if (useFlatKeySearchRows.value) {
|
||||
treeKeys.value = [];
|
||||
expandedGroupIds.value = new Set();
|
||||
} else if (treeKeys.value.length === 0) {
|
||||
rebuildTree(isSearchMode.value);
|
||||
} else {
|
||||
mergeTree(newKeys);
|
||||
@@ -345,7 +350,7 @@ async function fetchAll() {
|
||||
}
|
||||
} finally {
|
||||
if (requestId === searchRequestId) {
|
||||
if (changed) rebuildTree(isSearchMode.value);
|
||||
if (changed && !useFlatKeySearchRows.value) rebuildTree(isSearchMode.value);
|
||||
isFetchingAll.value = false;
|
||||
}
|
||||
}
|
||||
@@ -1012,7 +1017,7 @@ defineExpose({ focusSearch });
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center justify-end gap-1">
|
||||
<Badge v-if="row.node.kind === 'leaf' && row.node.keyType !== 'unknown'" variant="outline" class="text-xs px-1.5 py-0" :class="typeColor(row.node.keyType)">{{ row.node.keyType }}</Badge>
|
||||
<Badge v-if="row.node.kind === 'leaf' && row.node.keyType" variant="outline" class="text-xs px-1.5 py-0" :class="typeColor(row.node.keyType)">{{ row.node.keyType }}</Badge>
|
||||
<Button v-if="row.node.kind === 'group'" variant="ghost" size="icon" class="h-5 w-5 shrink-0 text-destructive opacity-0 group-hover:opacity-100" :title="t('redis.deleteGroup')" @click="requestGroupDelete(row.node, $event)">
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
@@ -182,12 +182,13 @@ const isBinaryStringValue = computed(() => data.value?.key_type === "string" &&
|
||||
const hasMore = computed(() => scanCursor.value != null && scanCursor.value > 0);
|
||||
const metadataSizeLabel = computed(() => {
|
||||
const metadata = props.metadata;
|
||||
if (!metadata || metadata.size <= 0) return "";
|
||||
const size = metadata?.size ?? 0;
|
||||
if (!metadata || size <= 0) return "";
|
||||
if (metadata.key_type === "string") {
|
||||
if (metadata.size >= 1024) return `${(metadata.size / 1024).toFixed(1)} KB`;
|
||||
return `${metadata.size} B`;
|
||||
if (size >= 1024) return `${(size / 1024).toFixed(1)} KB`;
|
||||
return `${size} B`;
|
||||
}
|
||||
return String(metadata.size);
|
||||
return String(size);
|
||||
});
|
||||
|
||||
function collectionCountLabel(kind: "items" | "fields" | "members", loaded: number, total?: number | null) {
|
||||
|
||||
@@ -37,6 +37,25 @@ function buildLeafId(db: number, keyRaw: string): string {
|
||||
return `leaf:${db}:${keyRaw}`;
|
||||
}
|
||||
|
||||
export function redisKeyToFlatTreeRow(key: RedisKeyInfo, db: number): RedisKeyTreeRow {
|
||||
return {
|
||||
node: {
|
||||
kind: "leaf",
|
||||
id: buildLeafId(db, key.key_raw),
|
||||
label: key.key_display,
|
||||
fullKeyDisplay: key.key_display,
|
||||
keyRaw: key.key_raw,
|
||||
db,
|
||||
keyType: key.key_type ?? "",
|
||||
ttl: key.ttl ?? -2,
|
||||
size: key.size ?? 0,
|
||||
valuePreview: key.value_preview ?? "",
|
||||
pathSegments: [key.key_display],
|
||||
},
|
||||
depth: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function compareRedisTreeNodes(a: RedisKeyTreeNode, b: RedisKeyTreeNode): number {
|
||||
if (a.kind !== b.kind) return a.kind === "group" ? -1 : 1;
|
||||
return a.label.localeCompare(b.label);
|
||||
@@ -74,10 +93,10 @@ function insertKeyIntoTree(root: RedisKeyTreeNode[], groupMap: Map<string, Redis
|
||||
fullKeyDisplay: key.key_display,
|
||||
keyRaw: key.key_raw,
|
||||
db,
|
||||
keyType: key.key_type,
|
||||
ttl: key.ttl,
|
||||
size: key.size,
|
||||
valuePreview: key.value_preview,
|
||||
keyType: key.key_type ?? "",
|
||||
ttl: key.ttl ?? -2,
|
||||
size: key.size ?? 0,
|
||||
valuePreview: key.value_preview ?? "",
|
||||
pathSegments,
|
||||
});
|
||||
return;
|
||||
@@ -110,10 +129,10 @@ function insertKeyIntoTree(root: RedisKeyTreeNode[], groupMap: Map<string, Redis
|
||||
fullKeyDisplay: key.key_display,
|
||||
keyRaw: key.key_raw,
|
||||
db,
|
||||
keyType: key.key_type,
|
||||
ttl: key.ttl,
|
||||
size: key.size,
|
||||
valuePreview: key.value_preview,
|
||||
keyType: key.key_type ?? "",
|
||||
ttl: key.ttl ?? -2,
|
||||
size: key.size ?? 0,
|
||||
valuePreview: key.value_preview ?? "",
|
||||
pathSegments,
|
||||
});
|
||||
}
|
||||
@@ -192,11 +211,21 @@ export function collectRedisGroupKeyRaws(group: RedisKeyTreeGroupNode): string[]
|
||||
|
||||
export function flattenVisibleRedisKeyTree(nodes: RedisKeyTreeNode[], expandedGroupIds: ReadonlySet<string>, depth = 0): RedisKeyTreeRow[] {
|
||||
const rows: RedisKeyTreeRow[] = [];
|
||||
const stack: RedisKeyTreeRow[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
rows.push({ node, depth });
|
||||
if (node.kind === "group" && expandedGroupIds.has(node.id)) {
|
||||
rows.push(...flattenVisibleRedisKeyTree(node.children, expandedGroupIds, depth + 1));
|
||||
for (let index = nodes.length - 1; index >= 0; index--) {
|
||||
stack.push({ node: nodes[index], depth });
|
||||
}
|
||||
|
||||
while (stack.length > 0) {
|
||||
const row = stack.pop()!;
|
||||
rows.push(row);
|
||||
|
||||
if (row.node.kind !== "group" || !expandedGroupIds.has(row.node.id)) continue;
|
||||
|
||||
const childDepth = row.depth + 1;
|
||||
for (let index = row.node.children.length - 1; index >= 0; index--) {
|
||||
stack.push({ node: row.node.children[index], depth: childDepth });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1113,10 +1113,10 @@ export async function getAppVersion(): Promise<string> {
|
||||
export interface RedisKeyInfo {
|
||||
key_display: string;
|
||||
key_raw: string;
|
||||
key_type: string;
|
||||
ttl: number;
|
||||
size: number;
|
||||
value_preview: string;
|
||||
key_type?: string;
|
||||
ttl?: number;
|
||||
size?: number;
|
||||
value_preview?: string;
|
||||
}
|
||||
|
||||
export interface RedisDatabaseInfo {
|
||||
|
||||
Reference in new issue
Block a user