From 1d1fab54d8b605e69414f5e6105ab9517066012c Mon Sep 17 00:00:00 2001 From: Lykin <137850705+tiny-craft@users.noreply.github.com> Date: Fri, 5 Jan 2024 00:36:48 +0800 Subject: [PATCH] perf: support batch update ttl of keys --- backend/services/browser_service.go | 102 ++++++++++++++++-- .../content_value/ContentToolbar.vue | 11 +- .../content_value/ContentValueHash.vue | 2 +- .../content_value/ContentValueList.vue | 2 +- .../content_value/ContentValueSet.vue | 2 +- .../content_value/ContentValueStream.vue | 2 +- .../content_value/ContentValueWrapper.vue | 2 +- .../content_value/ContentValueZSet.vue | 2 +- .../src/components/dialogs/NewKeyDialog.vue | 2 +- .../src/components/dialogs/SetTtlDialog.vue | 73 ++++++------- .../src/components/sidebar/BrowserPane.vue | 19 +++- .../src/components/sidebar/BrowserTree.vue | 13 ++- frontend/src/langs/en-us.json | 16 +-- frontend/src/langs/pt-br.json | 1 - frontend/src/langs/zh-cn.json | 14 ++- frontend/src/stores/browser.js | 73 ++++++++++++- frontend/src/stores/dialog.js | 25 ++++- frontend/src/stores/tab.js | 3 +- 18 files changed, 284 insertions(+), 80 deletions(-) diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index a08a239..35f6fa4 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -1836,14 +1836,13 @@ func (b *browserService) SetKeyTTL(connName string, db int, k any, ttl int64) (r client, ctx := item.client, item.ctx key := strutil.DecodeRedisKey(k) - var expiration time.Duration if ttl < 0 { if err = client.Persist(ctx, key).Err(); err != nil { resp.Msg = err.Error() return } } else { - expiration = time.Duration(ttl) * time.Second + expiration := time.Duration(ttl) * time.Second if err = client.Expire(ctx, key, expiration).Err(); err != nil { resp.Msg = err.Error() return @@ -1854,6 +1853,95 @@ func (b *browserService) SetKeyTTL(connName string, db int, k any, ttl int64) (r return } +// BatchSetTTL batch set ttl +func (b *browserService) BatchSetTTL(server string, db int, ks []any, ttl int64, serialNo string) (resp types.JSResp) { + conf := Connection().getConnection(server) + if conf == nil { + resp.Msg = fmt.Sprintf("no connection profile named: %s", server) + return + } + var client redis.UniversalClient + var err error + var connConfig = conf.ConnectionConfig + connConfig.LastDB = db + if client, err = b.createRedisClient(connConfig); err != nil { + resp.Msg = err.Error() + return + } + ctx, cancelFunc := context.WithCancel(b.ctx) + defer client.Close() + defer cancelFunc() + + //cancelEvent := "ttling:stop:" + serialNo + //runtime.EventsOnce(ctx, cancelEvent, func(data ...any) { + // cancelFunc() + //}) + //processEvent := "ttling:" + serialNo + total := len(ks) + var failed, updated atomic.Int64 + var canceled bool + + expiration := time.Now().Add(time.Duration(ttl) * time.Second) + del := func(ctx context.Context, cli redis.UniversalClient) error { + startTime := time.Now().Add(-10 * time.Second) + for i, k := range ks { + // emit progress per second + //param := map[string]any{ + // "total": total, + // "progress": i + 1, + // "processing": k, + //} + if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 { + startTime = time.Now() + //runtime.EventsEmit(b.ctx, processEvent, param) + // do some sleep to prevent blocking the Redis server + time.Sleep(10 * time.Millisecond) + } + + key := strutil.DecodeRedisKey(k) + var expErr error + if ttl < 0 { + expErr = cli.Persist(ctx, key).Err() + } else { + expErr = cli.ExpireAt(ctx, key, expiration).Err() + } + if err != nil { + failed.Add(1) + } else { + // save deleted key + updated.Add(1) + } + if errors.Is(expErr, context.Canceled) || canceled { + canceled = true + break + } + } + return nil + } + + if cluster, ok := client.(*redis.ClusterClient); ok { + // cluster mode + err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error { + return del(ctx, cli) + }) + } else { + err = del(ctx, client) + } + + //runtime.EventsOff(ctx, cancelEvent) + resp.Success = true + resp.Data = struct { + Canceled bool `json:"canceled"` + Updated int64 `json:"updated"` + Failed int64 `json:"failed"` + }{ + Canceled: canceled, + Updated: updated.Load(), + Failed: failed.Load(), + } + return +} + // DeleteKey remove redis key func (b *browserService) DeleteKey(server string, db int, k any, async bool) (resp types.JSResp) { item, err := b.getRedisClient(server, db) @@ -2007,13 +2095,13 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st startTime := time.Now().Add(-10 * time.Second) for i, k := range ks { // emit progress per second - param := map[string]any{ - "total": total, - "progress": i + 1, - "processing": k, - } if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 { startTime = time.Now() + param := map[string]any{ + "total": total, + "progress": i + 1, + "processing": k, + } runtime.EventsEmit(b.ctx, processEvent, param) // do some sleep to prevent blocking the Redis server time.Sleep(10 * time.Millisecond) diff --git a/frontend/src/components/content_value/ContentToolbar.vue b/frontend/src/components/content_value/ContentToolbar.vue index 1ef7eda..9a041d7 100644 --- a/frontend/src/components/content_value/ContentToolbar.vue +++ b/frontend/src/components/content_value/ContentToolbar.vue @@ -72,6 +72,15 @@ const onCopyKey = () => { $message.error(e.message) }) } + +const onTTL = () => { + dialogStore.openTTLDialog({ + server: props.server, + db: props.db, + key: binaryKey.value ? props.keyCode : props.keyPath, + ttl: props.ttl, + }) +}