From 9df10c5ac53481b9fbfeb1f47ec7f472b379bb64 Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 30 Aug 2024 03:56:29 +0200 Subject: [PATCH] [FEAT] Only implement used API of Redis client - Currently for the `nosql` module (which simply said provides a manager for redis clients) returns the [`redis.UniversalClient`](https://pkg.go.dev/github.com/redis/go-redis/v9#UniversalClient) interface. The interfaces exposes all available commands. - In generalm, dead code elimination should be able to take care of not generating the machine code for methods that aren't being used. However in this specific case, dead code elimination either is disabled or gives up on trying because of exhaustive call stack the client by `GetRedisClient` is used. - Help the Go compiler by explicitly specifying which methods we use. This reduces the binary size by ~400KB (397312 bytes). As Go no longer generate machine code for commands that aren't being used. - There's a **CAVEAT** with this, if a developer wants to use a new method that isn't specified, they will have to know about this hack (by following the definition of existing Redis methods) and add the method definition from the Redis library to the `RedisClient` interface. --- modules/cache/cache_redis.go | 3 +-- modules/nosql/manager.go | 40 +++++++++++++++++++++++++++++++++- modules/nosql/manager_redis.go | 14 ++++++------ modules/queue/base_redis.go | 4 ++-- modules/session/redis.go | 7 +++--- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/modules/cache/cache_redis.go b/modules/cache/cache_redis.go index e54887555d..4c243b2426 100644 --- a/modules/cache/cache_redis.go +++ b/modules/cache/cache_redis.go @@ -12,12 +12,11 @@ import ( "code.gitea.io/gitea/modules/nosql" "code.forgejo.org/go-chi/cache" - "github.com/redis/go-redis/v9" ) // RedisCacher represents a redis cache adapter implementation. type RedisCacher struct { - c redis.UniversalClient + c nosql.RedisClient prefix string hsetName string occupyMode bool diff --git a/modules/nosql/manager.go b/modules/nosql/manager.go index 375c2b5d00..0ba21585fa 100644 --- a/modules/nosql/manager.go +++ b/modules/nosql/manager.go @@ -27,8 +27,46 @@ type Manager struct { LevelDBConnections map[string]*levelDBHolder } +// RedisClient is a subset of redis.UniversalClient, it exposes less methods +// to avoid generating machine code for unused methods. New method definitions +// should be copied from the definitions in the Redis library github.com/redis/go-redis. +type RedisClient interface { + // redis.GenericCmdable + Del(ctx context.Context, keys ...string) *redis.IntCmd + Exists(ctx context.Context, keys ...string) *redis.IntCmd + + // redis.ListCmdable + RPush(ctx context.Context, key string, values ...any) *redis.IntCmd + LPop(ctx context.Context, key string) *redis.StringCmd + LLen(ctx context.Context, key string) *redis.IntCmd + + // redis.StringCmdable + Decr(ctx context.Context, key string) *redis.IntCmd + Incr(ctx context.Context, key string) *redis.IntCmd + Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd + Get(ctx context.Context, key string) *redis.StringCmd + + // redis.HashCmdable + HSet(ctx context.Context, key string, values ...any) *redis.IntCmd + HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd + HKeys(ctx context.Context, key string) *redis.StringSliceCmd + + // redis.SetCmdable + SAdd(ctx context.Context, key string, members ...any) *redis.IntCmd + SRem(ctx context.Context, key string, members ...any) *redis.IntCmd + SIsMember(ctx context.Context, key string, member any) *redis.BoolCmd + + // redis.Cmdable + DBSize(ctx context.Context) *redis.IntCmd + FlushDB(ctx context.Context) *redis.StatusCmd + Ping(ctx context.Context) *redis.StatusCmd + + // redis.UniversalClient + Close() error +} + type redisClientHolder struct { - redis.UniversalClient + RedisClient name []string count int64 } diff --git a/modules/nosql/manager_redis.go b/modules/nosql/manager_redis.go index 3c5502f979..79a533bd6b 100644 --- a/modules/nosql/manager_redis.go +++ b/modules/nosql/manager_redis.go @@ -39,11 +39,11 @@ func (m *Manager) CloseRedisClient(connection string) error { for _, name := range client.name { delete(m.RedisConnections, name) } - return client.UniversalClient.Close() + return client.RedisClient.Close() } // GetRedisClient gets a redis client for a particular connection -func (m *Manager) GetRedisClient(connection string) (client redis.UniversalClient) { +func (m *Manager) GetRedisClient(connection string) (client RedisClient) { // Because we want associate any goroutines created by this call to the main nosqldb context we need to // wrap this in a goroutine labelled with the nosqldb context done := make(chan struct{}) @@ -67,7 +67,7 @@ func (m *Manager) GetRedisClient(connection string) (client redis.UniversalClien return client } -func (m *Manager) getRedisClient(connection string) redis.UniversalClient { +func (m *Manager) getRedisClient(connection string) RedisClient { m.mutex.Lock() defer m.mutex.Unlock() client, ok := m.RedisConnections[connection] @@ -102,24 +102,24 @@ func (m *Manager) getRedisClient(connection string) redis.UniversalClient { opts.TLSConfig = tlsConfig fallthrough case "redis+sentinel": - client.UniversalClient = redis.NewFailoverClient(opts.Failover()) + client.RedisClient = redis.NewFailoverClient(opts.Failover()) case "redis+clusters": fallthrough case "rediss+cluster": opts.TLSConfig = tlsConfig fallthrough case "redis+cluster": - client.UniversalClient = redis.NewClusterClient(opts.Cluster()) + client.RedisClient = redis.NewClusterClient(opts.Cluster()) case "redis+socket": simpleOpts := opts.Simple() simpleOpts.Network = "unix" simpleOpts.Addr = path.Join(uri.Host, uri.Path) - client.UniversalClient = redis.NewClient(simpleOpts) + client.RedisClient = redis.NewClient(simpleOpts) case "rediss": opts.TLSConfig = tlsConfig fallthrough case "redis": - client.UniversalClient = redis.NewClient(opts.Simple()) + client.RedisClient = redis.NewClient(opts.Simple()) default: return nil } diff --git a/modules/queue/base_redis.go b/modules/queue/base_redis.go index 14931b62cd..62df30f68f 100644 --- a/modules/queue/base_redis.go +++ b/modules/queue/base_redis.go @@ -16,7 +16,7 @@ import ( ) type baseRedis struct { - client redis.UniversalClient + client nosql.RedisClient isUnique bool cfg *BaseConfig prefix string @@ -26,7 +26,7 @@ type baseRedis struct { var _ baseQueue = (*baseRedis)(nil) -func newBaseRedisGeneric(cfg *BaseConfig, unique bool, client redis.UniversalClient) (baseQueue, error) { +func newBaseRedisGeneric(cfg *BaseConfig, unique bool, client nosql.RedisClient) (baseQueue, error) { if client == nil { client = nosql.GetManager().GetRedisClient(cfg.ConnStr) } diff --git a/modules/session/redis.go b/modules/session/redis.go index 69cf6b1a4a..230b501080 100644 --- a/modules/session/redis.go +++ b/modules/session/redis.go @@ -26,12 +26,11 @@ import ( "code.gitea.io/gitea/modules/nosql" "code.forgejo.org/go-chi/session" - "github.com/redis/go-redis/v9" ) // RedisStore represents a redis session store implementation. type RedisStore struct { - c redis.UniversalClient + c nosql.RedisClient prefix, sid string duration time.Duration lock sync.RWMutex @@ -39,7 +38,7 @@ type RedisStore struct { } // NewRedisStore creates and returns a redis session store. -func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[any]any) *RedisStore { +func NewRedisStore(c nosql.RedisClient, prefix, sid string, dur time.Duration, kv map[any]any) *RedisStore { return &RedisStore{ c: c, prefix: prefix, @@ -106,7 +105,7 @@ func (s *RedisStore) Flush() error { // RedisProvider represents a redis session provider implementation. type RedisProvider struct { - c redis.UniversalClient + c nosql.RedisClient duration time.Duration prefix string }