[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.
This commit is contained in:
Gusted 2024-08-30 03:56:29 +02:00
parent 1004ecd56b
commit 9df10c5ac5
No known key found for this signature in database
GPG key ID: FD821B732837125F
5 changed files with 52 additions and 16 deletions

View file

@ -12,12 +12,11 @@ import (
"code.gitea.io/gitea/modules/nosql" "code.gitea.io/gitea/modules/nosql"
"code.forgejo.org/go-chi/cache" "code.forgejo.org/go-chi/cache"
"github.com/redis/go-redis/v9"
) )
// RedisCacher represents a redis cache adapter implementation. // RedisCacher represents a redis cache adapter implementation.
type RedisCacher struct { type RedisCacher struct {
c redis.UniversalClient c nosql.RedisClient
prefix string prefix string
hsetName string hsetName string
occupyMode bool occupyMode bool

View file

@ -27,8 +27,46 @@ type Manager struct {
LevelDBConnections map[string]*levelDBHolder 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 { type redisClientHolder struct {
redis.UniversalClient RedisClient
name []string name []string
count int64 count int64
} }

View file

@ -39,11 +39,11 @@ func (m *Manager) CloseRedisClient(connection string) error {
for _, name := range client.name { for _, name := range client.name {
delete(m.RedisConnections, name) delete(m.RedisConnections, name)
} }
return client.UniversalClient.Close() return client.RedisClient.Close()
} }
// GetRedisClient gets a redis client for a particular connection // 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 // 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 // wrap this in a goroutine labelled with the nosqldb context
done := make(chan struct{}) done := make(chan struct{})
@ -67,7 +67,7 @@ func (m *Manager) GetRedisClient(connection string) (client redis.UniversalClien
return client return client
} }
func (m *Manager) getRedisClient(connection string) redis.UniversalClient { func (m *Manager) getRedisClient(connection string) RedisClient {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
client, ok := m.RedisConnections[connection] client, ok := m.RedisConnections[connection]
@ -102,24 +102,24 @@ func (m *Manager) getRedisClient(connection string) redis.UniversalClient {
opts.TLSConfig = tlsConfig opts.TLSConfig = tlsConfig
fallthrough fallthrough
case "redis+sentinel": case "redis+sentinel":
client.UniversalClient = redis.NewFailoverClient(opts.Failover()) client.RedisClient = redis.NewFailoverClient(opts.Failover())
case "redis+clusters": case "redis+clusters":
fallthrough fallthrough
case "rediss+cluster": case "rediss+cluster":
opts.TLSConfig = tlsConfig opts.TLSConfig = tlsConfig
fallthrough fallthrough
case "redis+cluster": case "redis+cluster":
client.UniversalClient = redis.NewClusterClient(opts.Cluster()) client.RedisClient = redis.NewClusterClient(opts.Cluster())
case "redis+socket": case "redis+socket":
simpleOpts := opts.Simple() simpleOpts := opts.Simple()
simpleOpts.Network = "unix" simpleOpts.Network = "unix"
simpleOpts.Addr = path.Join(uri.Host, uri.Path) simpleOpts.Addr = path.Join(uri.Host, uri.Path)
client.UniversalClient = redis.NewClient(simpleOpts) client.RedisClient = redis.NewClient(simpleOpts)
case "rediss": case "rediss":
opts.TLSConfig = tlsConfig opts.TLSConfig = tlsConfig
fallthrough fallthrough
case "redis": case "redis":
client.UniversalClient = redis.NewClient(opts.Simple()) client.RedisClient = redis.NewClient(opts.Simple())
default: default:
return nil return nil
} }

View file

@ -16,7 +16,7 @@ import (
) )
type baseRedis struct { type baseRedis struct {
client redis.UniversalClient client nosql.RedisClient
isUnique bool isUnique bool
cfg *BaseConfig cfg *BaseConfig
prefix string prefix string
@ -26,7 +26,7 @@ type baseRedis struct {
var _ baseQueue = (*baseRedis)(nil) 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 { if client == nil {
client = nosql.GetManager().GetRedisClient(cfg.ConnStr) client = nosql.GetManager().GetRedisClient(cfg.ConnStr)
} }

View file

@ -26,12 +26,11 @@ import (
"code.gitea.io/gitea/modules/nosql" "code.gitea.io/gitea/modules/nosql"
"code.forgejo.org/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/redis/go-redis/v9"
) )
// RedisStore represents a redis session store implementation. // RedisStore represents a redis session store implementation.
type RedisStore struct { type RedisStore struct {
c redis.UniversalClient c nosql.RedisClient
prefix, sid string prefix, sid string
duration time.Duration duration time.Duration
lock sync.RWMutex lock sync.RWMutex
@ -39,7 +38,7 @@ type RedisStore struct {
} }
// NewRedisStore creates and returns a redis session store. // 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{ return &RedisStore{
c: c, c: c,
prefix: prefix, prefix: prefix,
@ -106,7 +105,7 @@ func (s *RedisStore) Flush() error {
// RedisProvider represents a redis session provider implementation. // RedisProvider represents a redis session provider implementation.
type RedisProvider struct { type RedisProvider struct {
c redis.UniversalClient c nosql.RedisClient
duration time.Duration duration time.Duration
prefix string prefix string
} }