Skip to content

Commit b0074ee

Browse files
authored
Merge pull request #10 from yannh/support-key-filter
Add filter flag
2 parents 0094cd8 + e19c351 commit b0074ee

File tree

3 files changed

+62
-59
lines changed

3 files changed

+62
-59
lines changed

Readme.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ Warning: like similar tools, Redis-dump-go does NOT provide Point-in-Time backup
2121
## Run
2222

2323
```
24-
$ redis-dump-go -h
25-
Usage of ./redis-dump-go:
26-
-db int
24+
$ ./bin/redis-dump-go -h
25+
Usage of ./bin/redis-dump-go:
26+
-db uint
2727
only dump this database (default: all databases)
28+
-filter string
29+
key filter to use (default "*")
2830
-host string
2931
Server host (default "127.0.0.1")
3032
-n int
@@ -34,8 +36,11 @@ Usage of ./redis-dump-go:
3436
-port int
3537
Server port (default 6379)
3638
-s Silent mode (disable progress bar)
37-
$ redis-dump-go > redis-backup.resp
38-
[==================================================] 100% [5/5]
39+
-ttl
40+
Preserve Keys TTL (default true)
41+
$ ./bin/redis-dump-go > dump.resp
42+
Database 0: 9 element dumped
43+
Database 1: 1 element dumped
3944
```
4045

4146
For password-protected Redis servers, set the shell variable REDISDUMPGO_AUTH:

main.go

+29-18
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,33 @@ import (
66
"io"
77
"log"
88
"os"
9-
"strings"
109
"sync"
1110

1211
"github.com/yannh/redis-dump-go/redisdump"
1312
)
1413

15-
func drawProgressBar(to io.Writer, currentPosition, nElements, widgetSize int) {
16-
if nElements == 0 {
17-
return
14+
type progressLogger struct {
15+
stats map[uint8]int
16+
}
17+
18+
func newProgressLogger() *progressLogger {
19+
return &progressLogger{
20+
stats: map[uint8]int{},
1821
}
19-
percent := currentPosition * 100 / nElements
20-
nBars := widgetSize * percent / 100
22+
}
2123

22-
bars := strings.Repeat("=", nBars)
23-
spaces := strings.Repeat(" ", widgetSize-nBars)
24-
fmt.Fprintf(to, "\r[%s%s] %3d%% [%d/%d]", bars, spaces, int(percent), currentPosition, nElements)
24+
func (p *progressLogger) drawProgress(to io.Writer, db uint8, nDumped int) {
25+
if _, ok := p.stats[db]; !ok && len(p.stats) > 0 {
26+
// We switched database, write to a new line
27+
fmt.Fprintf(to, "\n")
28+
}
2529

26-
if currentPosition == nElements {
27-
fmt.Fprint(to, "\n")
30+
p.stats[db] = nDumped
31+
if nDumped == 0 {
32+
return
2833
}
34+
35+
fmt.Fprintf(to, "\rDatabase %d: %d element dumped", db, nDumped)
2936
}
3037

3138
func isFlagPassed(name string) bool {
@@ -44,7 +51,8 @@ func realMain() int {
4451
// TODO: Number of workers & TTL as parameters
4552
host := flag.String("host", "127.0.0.1", "Server host")
4653
port := flag.Int("port", 6379, "Server port")
47-
db := flag.Int("db", 0, "only dump this database (default: all databases)")
54+
db := flag.Uint("db", 0, "only dump this database (default: all databases)")
55+
filter := flag.String("filter", "*", "key filter to use")
4856
nWorkers := flag.Int("n", 10, "Parallel workers")
4957
withTTL := flag.Bool("ttl", true, "Preserve Keys TTL")
5058
output := flag.String("output", "resp", "Output type - can be resp or commands")
@@ -76,27 +84,30 @@ func realMain() int {
7684
defer func() {
7785
close(progressNotifs)
7886
wg.Wait()
87+
if !(*silent) {
88+
fmt.Fprint(os.Stderr, "\n")
89+
}
7990
}()
8091

92+
pl := newProgressLogger()
8193
go func() {
8294
for n := range progressNotifs {
8395
if !(*silent) {
84-
drawProgressBar(os.Stderr, n.Done, n.Total, 50)
96+
pl.drawProgress(os.Stderr, n.Db, n.Done)
8597
}
8698
}
8799
wg.Done()
88100
}()
89101

90102
logger := log.New(os.Stdout, "", 0)
91103
if db == nil {
92-
if err = redisdump.DumpServer(*host, *port, redisPassword, *nWorkers, *withTTL, logger, serializer, progressNotifs); err != nil {
93-
fmt.Println(err)
104+
if err = redisdump.DumpServer(*host, *port, redisPassword, *filter, *nWorkers, *withTTL, logger, serializer, progressNotifs); err != nil {
105+
fmt.Fprintf(os.Stderr, "%s", err)
94106
return 1
95107
}
96108
} else {
97-
url := redisdump.RedisURL(*host, fmt.Sprint(*port), fmt.Sprint(*db), redisPassword)
98-
if err = redisdump.DumpDB(url, *nWorkers, *withTTL, logger, serializer, progressNotifs); err != nil {
99-
fmt.Println(err)
109+
if err = redisdump.DumpDB(*host, *port, redisPassword, uint8(*db), *filter, *nWorkers, *withTTL, logger, serializer, progressNotifs); err != nil {
110+
fmt.Fprintf(os.Stderr, "%s", err)
100111
return 1
101112
}
102113
}

redisdump/redisdump.go

+23-36
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,6 @@ import (
1212
radix "github.com/mediocregopher/radix/v3"
1313
)
1414

15-
func min(a, b int) int {
16-
if a <= b {
17-
return a
18-
}
19-
return b
20-
}
21-
2215
func ttlToRedisCmd(k string, val int64) []string {
2316
return []string{"EXPIREAT", k, fmt.Sprint(time.Now().Unix() + val)}
2417
}
@@ -160,7 +153,8 @@ func dumpKeysWorker(client radix.Client, keyBatches <-chan []string, withTTL boo
160153
// and can be used to provide a progress visualisation such as a progress bar.
161154
// Done is the number of items dumped, Total is the total number of items to dump.
162155
type ProgressNotification struct {
163-
Done, Total int
156+
Db uint8
157+
Done int
164158
}
165159

166160
func parseKeyspaceInfo(keyspaceInfo string) ([]uint8, error) {
@@ -205,14 +199,9 @@ func getDBIndexes(redisURL string) ([]uint8, error) {
205199
return parseKeyspaceInfo(keyspaceInfo)
206200
}
207201

208-
func scanKeys(client radix.Client, keyBatches chan<- []string, progressNotifications chan<- ProgressNotification) error {
202+
func scanKeys(client radix.Client, db uint8, filter string, keyBatches chan<- []string, progressNotifications chan<- ProgressNotification) error {
209203
keyBatchSize := 100
210-
s := radix.NewScanner(client, radix.ScanOpts{Command: "SCAN", Count: keyBatchSize})
211-
212-
var dbSize int
213-
if err := client.Do(radix.Cmd(&dbSize, "DBSIZE")); err != nil {
214-
return err
215-
}
204+
s := radix.NewScanner(client, radix.ScanOpts{Command: "SCAN", Pattern: filter, Count: keyBatchSize})
216205

217206
nProcessed := 0
218207
var key string
@@ -223,19 +212,31 @@ func scanKeys(client radix.Client, keyBatches chan<- []string, progressNotificat
223212
nProcessed += len(keyBatch)
224213
keyBatches <- keyBatch
225214
keyBatch = nil
226-
progressNotifications <- ProgressNotification{nProcessed, dbSize}
215+
progressNotifications <- ProgressNotification{Db: db, Done: nProcessed}
227216
}
228217
}
229218

230219
keyBatches <- keyBatch
231220
nProcessed += len(keyBatch)
232-
progressNotifications <- ProgressNotification{nProcessed, dbSize}
221+
progressNotifications <- ProgressNotification{Db: db, Done: nProcessed}
233222

234223
return s.Close()
235224
}
236225

226+
// RedisURL builds a connect URL given a Host, port, db & password
227+
func RedisURL(redisHost string, redisPort string, redisDB string, redisPassword string) string {
228+
switch {
229+
case redisDB == "":
230+
return "redis://:" + redisPassword + "@" + redisHost + ":" + fmt.Sprint(redisPort)
231+
case redisDB != "":
232+
return "redis://:" + redisPassword + "@" + redisHost + ":" + fmt.Sprint(redisPort) + "/" + redisDB
233+
}
234+
235+
return ""
236+
}
237+
237238
// DumpDB dumps all keys from a single Redis DB
238-
func DumpDB(redisURL string, nWorkers int, withTTL bool, logger *log.Logger, serializer func([]string) string, progress chan<- ProgressNotification) error {
239+
func DumpDB(redisHost string, redisPort int, redisPassword string, db uint8, filter string, nWorkers int, withTTL bool, logger *log.Logger, serializer func([]string) string, progress chan<- ProgressNotification) error {
239240
var err error
240241

241242
errors := make(chan error)
@@ -247,15 +248,13 @@ func DumpDB(redisURL string, nWorkers int, withTTL bool, logger *log.Logger, ser
247248
}
248249
}()
249250

251+
redisURL := RedisURL(redisHost, fmt.Sprint(redisPort), fmt.Sprint(db), redisPassword)
250252
client, err := radix.NewPool("tcp", redisURL, nWorkers)
251253
if err != nil {
252254
return err
253255
}
254256
defer client.Close()
255257

256-
splitURL := strings.Split(redisURL, "/")
257-
db := splitURL[len(splitURL)-1]
258-
259258
if err = client.Do(radix.Cmd(nil, "SELECT", fmt.Sprint(db))); err != nil {
260259
return err
261260
}
@@ -267,7 +266,7 @@ func DumpDB(redisURL string, nWorkers int, withTTL bool, logger *log.Logger, ser
267266
go dumpKeysWorker(client, keyBatches, withTTL, logger, serializer, errors, done)
268267
}
269268

270-
scanKeys(client, keyBatches, progress)
269+
scanKeys(client, db, filter, keyBatches, progress)
271270
close(keyBatches)
272271

273272
for i := 0; i < nWorkers; i++ {
@@ -277,30 +276,18 @@ func DumpDB(redisURL string, nWorkers int, withTTL bool, logger *log.Logger, ser
277276
return nil
278277
}
279278

280-
func RedisURL(redisHost string, redisPort string, redisDB string, redisPassword string) string {
281-
switch {
282-
case redisDB == "":
283-
return "redis://:" + redisPassword + "@" + redisHost + ":" + fmt.Sprint(redisPort)
284-
case redisDB != "":
285-
return "redis://:" + redisPassword + "@" + redisHost + ":" + fmt.Sprint(redisPort) + "/" + redisDB
286-
}
287-
288-
return ""
289-
}
290-
291279
// DumpServer dumps all Keys from the redis server given by redisURL,
292280
// to the Logger logger. Progress notification informations
293281
// are regularly sent to the channel progressNotifications
294-
func DumpServer(redisHost string, redisPort int, redisPassword string, nWorkers int, withTTL bool, logger *log.Logger, serializer func([]string) string, progress chan<- ProgressNotification) error {
282+
func DumpServer(redisHost string, redisPort int, redisPassword string, filter string, nWorkers int, withTTL bool, logger *log.Logger, serializer func([]string) string, progress chan<- ProgressNotification) error {
295283
url := RedisURL(redisHost, fmt.Sprint(redisPort), "", redisPassword)
296284
dbs, err := getDBIndexes(url)
297285
if err != nil {
298286
return err
299287
}
300288

301289
for _, db := range dbs {
302-
url = RedisURL(redisHost, fmt.Sprint(redisPort), fmt.Sprint(db), redisPassword)
303-
if err = DumpDB(url, nWorkers, withTTL, logger, serializer, progress); err != nil {
290+
if err = DumpDB(redisHost, redisPort, redisPassword, db, filter, nWorkers, withTTL, logger, serializer, progress); err != nil {
304291
return err
305292
}
306293
}

0 commit comments

Comments
 (0)