From 21a569d9bb1677f3b92a3a80d817ff3546aed095 Mon Sep 17 00:00:00 2001 From: Lykin <137850705+tiny-craft@users.noreply.github.com> Date: Tue, 26 Dec 2023 12:04:46 +0800 Subject: [PATCH] perf: optimize the logic for bulk deletion of selected keys --- backend/services/browser_service.go | 10 +++++-- frontend/src/langs/en-us.json | 2 +- frontend/src/langs/zh-cn.json | 2 +- frontend/src/objects/redisNodeItem.js | 35 ++++-------------------- frontend/src/objects/redisServerState.js | 21 ++++++++++---- frontend/src/stores/browser.js | 29 ++++++++++++++------ frontend/src/utils/promise.js | 3 ++ 7 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 frontend/src/utils/promise.js diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index a10dd2c..bd746a2 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -1999,6 +1999,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st var deletedKeys = make([]any, 0, total) var mutex sync.Mutex del := func(ctx context.Context, cli redis.UniversalClient) error { + startTime := time.Now() for i, k := range ks { // emit progress per second param := map[string]any{ @@ -2006,12 +2007,13 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st "progress": i + 1, "processing": k, } - runtime.EventsEmit(b.ctx, processEvent, param) + if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 { + startTime = time.Now() + runtime.EventsEmit(b.ctx, processEvent, param) + } key := strutil.DecodeRedisKey(k) delErr := cli.Del(ctx, key).Err() - // do some sleep to prevent blocking the Redis server - time.Sleep(100 * time.Microsecond) if err != nil { failed.Add(1) } else { @@ -2024,6 +2026,8 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st canceled = true break } + // do some sleep to prevent blocking the Redis server + time.Sleep(100 * time.Microsecond) } return nil } diff --git a/frontend/src/langs/en-us.json b/frontend/src/langs/en-us.json index 1962b66..0a7932f 100644 --- a/frontend/src/langs/en-us.json +++ b/frontend/src/langs/en-us.json @@ -143,7 +143,7 @@ "remove_tip": "{type} \"{name}\" will be deleted", "remove_group_tip": "Group \"{name}\" and all connections in it will be deleted", "delete_key_succ": "\"{key}\" has been deleted", - "deleting_key": "Deleting key({index}/{count}): {key}", + "deleting_key": "Deleting key({index}/{count})", "delete_completed": "Deletion process has been completed, {success} successed, {fail} failed", "rename_binary_key_fail": "Rename binary key name is unsupported", "handle_succ": "Success!", diff --git a/frontend/src/langs/zh-cn.json b/frontend/src/langs/zh-cn.json index 33cd19b..b9a0dfc 100644 --- a/frontend/src/langs/zh-cn.json +++ b/frontend/src/langs/zh-cn.json @@ -143,7 +143,7 @@ "remove_tip": "{type} \"{name}\" 将会被删除", "remove_group_tip": "分组 \"{name}\"及其所有连接将会被删除", "delete_key_succ": "{key} 已被删除", - "deleting_key": "正在删除键({index}/{count}):{key}", + "deleting_key": "正在删除键({index}/{count})", "delete_completed": "已完成删除操作,成功{success}个,失败{fail}个", "rename_binary_key_fail": "不支持重命名二进制键名", "handle_succ": "操作成功", diff --git a/frontend/src/objects/redisNodeItem.js b/frontend/src/objects/redisNodeItem.js index 3297f41..2ac1946 100644 --- a/frontend/src/objects/redisNodeItem.js +++ b/frontend/src/objects/redisNodeItem.js @@ -1,4 +1,4 @@ -import { isEmpty, remove, sortBy, sortedIndexBy, sumBy } from 'lodash' +import { isEmpty, remove, size, sortBy, sortedIndexBy, sumBy } from 'lodash' import { ConnectionType } from '@/consts/connection_type.js' /** @@ -95,30 +95,6 @@ export class RedisNodeItem { } } return false - - // let count = 0 - // if (!isEmpty(this.children)) { - // if (skipSort !== true) { - // this.sortChildren() - // } - // - // for (const elem of this.children) { - // elem.tidy(skipSort) - // count += elem.keyCount - // } - // } else { - // if (this.type === ConnectionType.RedisValue) { - // count += 1 - // } else { - // // no children in db node or layer node, set count to 0 - // count = 0 - // } - // } - // if (this.keyCount !== count) { - // this.keyCount = count - // return true - // } - // return false } sortChildren() { @@ -142,15 +118,14 @@ export class RedisNodeItem { /** * * @param {{}} predicate + * @return {number} */ removeChild(predicate) { - if (this.type !== ConnectionType.RedisKey) { - return + if (this.type !== ConnectionType.RedisKey && this.type !== ConnectionType.RedisDB) { + return 0 } const removed = remove(this.children, predicate) - if (this.children.length <= 0) { - // TODO: remove from parent if no children - } + return size(removed) } getChildren() { diff --git a/frontend/src/objects/redisServerState.js b/frontend/src/objects/redisServerState.js index c90e86a..1e14f94 100644 --- a/frontend/src/objects/redisServerState.js +++ b/frontend/src/objects/redisServerState.js @@ -25,7 +25,7 @@ export class RedisServerState { * @param {string|null} typeFilter redis type filter * @param {LoadingState} loadingState all loading state in opened connections map by server and LoadingState * @param {KeyViewType} viewType view type selection for all opened connections group by 'server' - * @param {Map} nodeMap map nodes by "key#type" + * @param {Map} nodeMap map nodes by "type#key" */ constructor({ name, @@ -341,7 +341,7 @@ export class RedisServerState { * @return */ tidyNode(key, skipResort) { - const dbNode = this.getRoot() + const rootNode = this.getRoot() const keyParts = split(key, this.separator) const totalParts = size(keyParts) let node @@ -355,7 +355,7 @@ export class RedisServerState { } } if (node == null) { - node = dbNode + node = rootNode } const keyCountUpdated = node.tidy(skipResort) if (keyCountUpdated) { @@ -366,12 +366,23 @@ export class RedisServerState { if (parentNode == null) { break } - parentNode.reCalcKeyCount() + const count = parentNode.reCalcKeyCount() + if (count <= 0) { + let anceKeyNode = rootNode + // remove from ancestor node + if (i > 1) { + const anceKey = join(slice(keyParts, 0, i - 1), this.separator) + anceKeyNode = this.nodeMap.get(`${ConnectionType.RedisKey}/${anceKey}`) + } + if (anceKeyNode != null) { + anceKeyNode.removeChild({ type: ConnectionType.RedisKey, redisKey: parentKey }) + } + } } // update key count of db const dbInst = this.databases[this.db] if (dbInst != null) { - dbInst.keyCount = dbNode.reCalcKeyCount() + dbInst.keyCount = rootNode.reCalcKeyCount() } } } diff --git a/frontend/src/stores/browser.js b/frontend/src/stores/browser.js index e839fa3..ce0e656 100644 --- a/frontend/src/stores/browser.js +++ b/frontend/src/stores/browser.js @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import { endsWith, get, isEmpty, map, size } from 'lodash' +import { endsWith, get, isEmpty, map, now, size } from 'lodash' import { AddHashField, AddListItem, @@ -46,6 +46,7 @@ import { EventsEmit, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js' import { RedisNodeItem } from '@/objects/redisNodeItem.js' import { RedisServerState } from '@/objects/redisServerState.js' import { RedisDatabaseItem } from '@/objects/redisDatabaseItem.js' +import { timeout } from '@/utils/promise.js' const useBrowserStore = defineStore('browser', { /** @@ -1537,7 +1538,7 @@ const useBrowserStore = defineStore('browser', { let canceled = false const serialNo = Date.now().valueOf().toString() const eventName = 'deleting:' + serialNo - const cancelEvent = 'deleting:' + serialNo + const cancelEvent = 'delete:stop:' + serialNo try { let maxProgress = 0 EventsOn(eventName, ({ total, progress, processing }) => { @@ -1551,7 +1552,6 @@ const useBrowserStore = defineStore('browser', { index: maxProgress, count: total, }) - // this._deleteKeyNode(server, db, k, false) }) delMsgRef.onClose = () => { EventsEmit(cancelEvent) @@ -1573,12 +1573,6 @@ const useBrowserStore = defineStore('browser', { } // refresh model data const deletedCount = size(deleted) - /** @type RedisServerState **/ - const serverInst = this.servers[server] - if (serverInst != null) { - serverInst.tidyNode('', true) - serverInst.updateDBKeyCount(db, -deletedCount) - } if (canceled) { $message.info(i18nGlobal.t('dialogue.handle_cancel')) } else if (failCount <= 0) { @@ -1591,6 +1585,23 @@ const useBrowserStore = defineStore('browser', { // some fail $message.warn(i18nGlobal.t('dialogue.delete_completed', { success: deletedCount, fail: failCount })) } + // update ui + timeout(100).then(async () => { + /** @type RedisServerState **/ + const serverInst = this.servers[server] + if (serverInst != null) { + let start = now() + for (let i = 0; i < deleted.length; i++) { + serverInst.removeKeyNode(deleted[i], false) + if (now() - start > 300) { + await timeout(100) + start = now() + } + } + serverInst.tidyNode('', true) + serverInst.updateDBKeyCount(db, -deletedCount) + } + }) }, /** diff --git a/frontend/src/utils/promise.js b/frontend/src/utils/promise.js new file mode 100644 index 0000000..53e19ab --- /dev/null +++ b/frontend/src/utils/promise.js @@ -0,0 +1,3 @@ +export const timeout = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)) +}