@@ -14,6 +14,8 @@ import (
1414 pb "github.com/bootjp/elastickv/proto"
1515 "github.com/bootjp/elastickv/store"
1616 "github.com/cockroachdb/errors"
17+ "github.com/hashicorp/raft"
18+ "github.com/redis/go-redis/v9"
1719 "github.com/tidwall/redcon"
1820 "google.golang.org/grpc"
1921 "google.golang.org/grpc/credentials/insecure"
@@ -39,6 +41,8 @@ type RedisServer struct {
3941 store store.ScanStore
4042 coordinator kv.Coordinator
4143 redisTranscoder * redisTranscoder
44+ // TODO manage membership from raft log
45+ leaderRedis map [raft.ServerAddress ]string
4246
4347 route map [string ]func (conn redcon.Conn , cmd redcon.Command )
4448}
@@ -68,12 +72,13 @@ type redisResult struct {
6872 err error
6973}
7074
71- func NewRedisServer (listen net.Listener , store store.ScanStore , coordinate * kv.Coordinate ) * RedisServer {
75+ func NewRedisServer (listen net.Listener , store store.ScanStore , coordinate * kv.Coordinate , leaderRedis map [raft. ServerAddress ] string ) * RedisServer {
7276 r := & RedisServer {
7377 listen : listen ,
7478 store : store ,
7579 coordinator : coordinate ,
7680 redisTranscoder : newRedisTranscoder (),
81+ leaderRedis : leaderRedis ,
7782 }
7883
7984 r .route = map [string ]func (conn redcon.Conn , cmd redcon.Command ){
@@ -243,42 +248,87 @@ func (r *RedisServer) exists(conn redcon.Conn, cmd redcon.Command) {
243248}
244249
245250func (r * RedisServer ) keys (conn redcon.Conn , cmd redcon.Command ) {
251+ pattern := cmd .Args [1 ]
246252
247- // If an asterisk (*) is not included, the match will be exact,
248- // so check if the key exists.
249- if ! bytes .Contains (cmd .Args [1 ], []byte ("*" )) {
250- res , err := r .store .Exists (context .Background (), cmd .Args [1 ])
253+ if r .coordinator .IsLeader () {
254+ keys , err := r .localKeys (pattern )
251255 if err != nil {
252256 conn .WriteError (err .Error ())
253257 return
254258 }
255- if res {
256- conn .WriteArray (1 )
257- conn .WriteBulk (cmd .Args [1 ])
258- return
259+ conn .WriteArray (len (keys ))
260+ for _ , k := range keys {
261+ conn .WriteBulk (k )
259262 }
260- conn .WriteArray (0 )
261263 return
262264 }
263265
266+ keys , err := r .proxyKeys (pattern )
267+ if err != nil {
268+ conn .WriteError (err .Error ())
269+ return
270+ }
271+
272+ conn .WriteArray (len (keys ))
273+ for _ , k := range keys {
274+ conn .WriteBulkString (k )
275+ }
276+ }
277+
278+ func (r * RedisServer ) localKeys (pattern []byte ) ([][]byte , error ) {
279+ // If an asterisk (*) is not included, the match will be exact,
280+ // so check if the key exists.
281+ if ! bytes .Contains (pattern , []byte ("*" )) {
282+ res , err := r .store .Exists (context .Background (), pattern )
283+ if err != nil {
284+ return nil , errors .WithStack (err )
285+ }
286+ if res {
287+ return [][]byte {bytes .Clone (pattern )}, nil
288+ }
289+ return [][]byte {}, nil
290+ }
291+
264292 var start []byte
265293 switch {
266- case bytes .Equal (cmd . Args [ 1 ] , []byte ("*" )):
294+ case bytes .Equal (pattern , []byte ("*" )):
267295 start = nil
268296 default :
269- start = bytes .ReplaceAll (cmd . Args [ 1 ] , []byte ("*" ), nil )
297+ start = bytes .ReplaceAll (pattern , []byte ("*" ), nil )
270298 }
271299
272300 keys , err := r .store .Scan (context .Background (), start , nil , math .MaxInt )
273301 if err != nil {
274- conn .WriteError (err .Error ())
275- return
302+ return nil , errors .WithStack (err )
276303 }
277304
278- conn . WriteArray ( len (keys ))
305+ out := make ([][] byte , 0 , len (keys ))
279306 for _ , kvPair := range keys {
280- conn .WriteBulk (kvPair .Key )
307+ out = append (out , kvPair .Key )
308+ }
309+ return out , nil
310+ }
311+
312+ func (r * RedisServer ) proxyKeys (pattern []byte ) ([]string , error ) {
313+ leader := r .coordinator .RaftLeader ()
314+ if leader == "" {
315+ return nil , ErrLeaderNotFound
281316 }
317+
318+ leaderAddr , ok := r .leaderRedis [leader ]
319+ if ! ok || leaderAddr == "" {
320+ return nil , errors .WithStack (errors .Newf ("leader redis address unknown for %s" , leader ))
321+ }
322+
323+ cli := redis .NewClient (& redis.Options {
324+ Addr : leaderAddr ,
325+ })
326+ defer func () {
327+ _ = cli .Close ()
328+ }()
329+
330+ keys , err := cli .Keys (context .Background (), string (pattern )).Result ()
331+ return keys , errors .WithStack (err )
282332}
283333
284334// MULTI/EXEC/DISCARD handling
@@ -577,34 +627,6 @@ func encodeList(list []string) ([]byte, error) {
577627 return b , errors .WithStack (err )
578628}
579629
580- func (r * RedisServer ) pushList (key []byte , values [][]byte ) (int , error ) {
581- current , err := r .getValue (key )
582- if err != nil && ! errors .Is (err , store .ErrKeyNotFound ) {
583- return 0 , errors .WithStack (err )
584- }
585-
586- list , err := decodeList (current )
587- if err != nil {
588- return 0 , err
589- }
590- for _ , v := range values {
591- list = append (list , string (v ))
592- }
593- enc , err := encodeList (list )
594- if err != nil {
595- return 0 , err
596- }
597-
598- req , err := r .redisTranscoder .SetToRequest (key , enc )
599- if err != nil {
600- return 0 , err
601- }
602- if _ , err := r .coordinator .Dispatch (req ); err != nil {
603- return 0 , errors .WithStack (err )
604- }
605- return len (list ), nil
606- }
607-
608630func clampRange (start , end , length int ) (int , int ) {
609631 if start < 0 {
610632 start = length + start
@@ -683,12 +705,25 @@ func (r *RedisServer) getValue(key []byte) ([]byte, error) {
683705}
684706
685707func (r * RedisServer ) rpush (conn redcon.Conn , cmd redcon.Command ) {
686- length , err := r .pushList ( cmd . Args [ 1 ], cmd . Args [ 2 :] )
708+ results , err := r .runTransaction ([]redcon. Command { cmd } )
687709 if err != nil {
688710 conn .WriteError (err .Error ())
689711 return
690712 }
691- conn .WriteInt (length )
713+ if len (results ) != 1 {
714+ conn .WriteError ("ERR internal error: rpush should have one result" )
715+ return
716+ }
717+ res := results [0 ]
718+ if res .err != nil {
719+ conn .WriteError (res .err .Error ())
720+ return
721+ }
722+ if res .typ != resultInt {
723+ conn .WriteError ("ERR internal error: rpush result should be an integer" )
724+ return
725+ }
726+ conn .WriteInt64 (res .integer )
692727}
693728
694729func (r * RedisServer ) lrange (conn redcon.Conn , cmd redcon.Command ) {
0 commit comments