-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(database): Patterns set iteration
Added a set iterator to use instead of SMEMBERS. Used it instead of fetching full patterns set in one request.
- Loading branch information
kodmi
committed
Mar 11, 2020
1 parent
ee9f7b5
commit 891c0ff
Showing
10 changed files
with
284 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package redis | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/gomodule/redigo/redis" | ||
) | ||
|
||
var ErrFinished = errors.New("redis iterator reached its end") | ||
|
||
const defaultBatchSize = uint64(20) | ||
|
||
// SetIterator scans a set, returning all it's values | ||
// May return duplicate values | ||
// A value may get omitted if it were not constantly present in the collection during a full iteration | ||
type SetIterator struct { | ||
conn redis.Conn | ||
setName string | ||
dbIterator string | ||
batchSize uint64 | ||
|
||
values []string | ||
currIndex int | ||
isFinished bool | ||
} | ||
|
||
// Next returns the next iterator value | ||
// Returns empty string and ErrFinished if there are no more values | ||
// Returns empty string and error when fetching values from db failed | ||
func (i *SetIterator) Next() (string, error) { | ||
val, err := i.nextValue() | ||
if err == nil { | ||
return val, err | ||
} | ||
if i.isFinished { | ||
i.Close() | ||
return "", ErrFinished | ||
} | ||
// making sure we skip empty responses | ||
for { | ||
i.dbIterator, i.values, err = i.receiveBatch() | ||
if err != nil { | ||
return "", fmt.Errorf("scanning the %s set failed, error: %v", i.setName, err) | ||
} | ||
|
||
i.currIndex = 0 | ||
i.isFinished = i.dbIterator == "0" | ||
|
||
if len(i.values) != 0 || i.isFinished { | ||
break | ||
} | ||
} | ||
|
||
return i.nextValue() | ||
} | ||
|
||
// ReadToEnd iterates over the whole set and returns results | ||
func (i *SetIterator) ReadToEnd() ([]string, error) { | ||
setSize, err := redis.Int(i.conn.Do("SCARD", patternsListKey)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get moira patterns, error: %v", err) | ||
} | ||
values := make([]string, 0, setSize) | ||
|
||
for { | ||
val, err := i.Next() | ||
if err == ErrFinished { | ||
break | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
values = append(values, val) | ||
} | ||
|
||
return values, nil | ||
} | ||
|
||
// Close terminates the iterator's db connection | ||
// Iterator closes automatically, once it is read to the end | ||
func (i *SetIterator) Close() error { | ||
err := i.conn.Close() | ||
if err == nil || err.Error() == "redigo: closed" { | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
func (i *SetIterator) nextValue() (string, error) { | ||
if i.currIndex >= len(i.values) { | ||
return "", ErrFinished | ||
} | ||
val := i.values[i.currIndex] | ||
i.currIndex++ | ||
return val, nil | ||
} | ||
|
||
func (i *SetIterator) receiveBatch() (next string, values []string, err error) { | ||
response, err := redis.Values(i.conn.Do("SSCAN", i.setName, i.dbIterator, "COUNT", i.getBatchSize())) | ||
if err != nil { | ||
return | ||
} | ||
next, err = redis.String(response[0], err) | ||
if err != nil { | ||
return | ||
} | ||
values, err = redis.Strings(response[1], err) | ||
if err != nil { | ||
return | ||
} | ||
return | ||
} | ||
|
||
func (i *SetIterator) getBatchSize() uint64 { | ||
if i.batchSize == 0 { | ||
return defaultBatchSize | ||
} | ||
return i.batchSize | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package redis | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/gomodule/redigo/redis" | ||
"github.com/op/go-logging" | ||
. "github.com/smartystreets/goconvey/convey" | ||
) | ||
|
||
const setName = "test-set" | ||
|
||
func TestSetIterator(t *testing.T) { | ||
logger, _ := logging.GetLogger("dataBase") | ||
db := newTestDatabase(logger, config) | ||
db.flush() | ||
defer db.flush() | ||
|
||
Convey("Set iteration", t, func() { | ||
db.clearSet() | ||
|
||
Convey("Empty set", func() { | ||
iter := db.createIterator() | ||
|
||
val, err := iter.Next() | ||
|
||
So(val, ShouldBeEmpty) | ||
So(err, ShouldEqual, ErrFinished) | ||
}) | ||
|
||
Convey("Single item", func() { | ||
item := "foo" | ||
db.addToSet(item) | ||
iter := db.createIterator() | ||
|
||
val, err := iter.Next() | ||
So(err, ShouldBeNil) | ||
So(val, ShouldEqual, item) | ||
|
||
_, err = iter.Next() | ||
So(err, ShouldEqual, ErrFinished) | ||
}) | ||
|
||
Convey("Multiple items, iterate via Next", func() { | ||
db.fillSetWithAlphabet() | ||
iter := db.createIterator() | ||
values := []string{} | ||
|
||
for { | ||
val, err := iter.Next() | ||
if err != nil { | ||
So(err, ShouldEqual, ErrFinished) | ||
break | ||
} | ||
values = append(values, val) | ||
} | ||
|
||
So(values, ShouldHaveLength, 26) //alphabet | ||
}) | ||
|
||
Convey("Multiple items, read to end", func() { | ||
db.fillSetWithAlphabet() | ||
iter := db.createIterator() | ||
|
||
values, err := iter.ReadToEnd() | ||
|
||
So(err, ShouldBeNil) | ||
So(values, ShouldHaveLength, 26) //alphabet | ||
}) | ||
|
||
Convey("Multiple items, read to end with preset batch size", func() { | ||
db.fillSetWithAlphabet() | ||
iter := db.createIterator() | ||
iter.batchSize = 60 | ||
|
||
values, err := iter.ReadToEnd() | ||
|
||
So(err, ShouldBeNil) | ||
So(values, ShouldHaveLength, 26) //alphabet | ||
}) | ||
|
||
Convey("Close returns nil error, after iterator is closed", func() { | ||
iter := db.createIterator() | ||
|
||
_, err := iter.Next() | ||
So(err, ShouldEqual, ErrFinished) | ||
|
||
err = iter.Close() | ||
So(err, ShouldBeNil) | ||
}) | ||
}) | ||
} | ||
|
||
func (c *DbConnector) clearSet() { | ||
conn := c.pool.Get() | ||
values, err := redis.Strings(conn.Do("SMEMBERS", setName)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
for _, val := range values { | ||
conn.Send("SREM", setName, val) | ||
} | ||
conn.Flush() | ||
} | ||
|
||
func (c *DbConnector) createIterator() SetIterator { | ||
return SetIterator{ | ||
conn: c.pool.Get(), | ||
setName: setName, | ||
dbIterator: "0", | ||
} | ||
} | ||
|
||
func (c *DbConnector) addToSet(value string) { | ||
conn := c.pool.Get() | ||
_, err := redis.Int64(conn.Do("SADD", setName, value)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func (c *DbConnector) fillSetWithAlphabet() { | ||
for i := int32('A'); i <= int32('Z'); i++ { | ||
c.addToSet(string(rune(i))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters