Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow SPOP command due to re-sorting #335

Closed
dzvancuks opened this issue Jun 22, 2023 · 2 comments · Fixed by #336
Closed

Slow SPOP command due to re-sorting #335

dzvancuks opened this issue Jun 22, 2023 · 2 comments · Fixed by #336

Comments

@dzvancuks
Copy link

dzvancuks commented Jun 22, 2023

I'm using github.com/alicebob/miniredis/v2 v2.21.0 for testing redis in UT. And I need to check code that contains SPOP call that retrieves multiple members. It seems that there is an issue with tests are running very slow, but on live Redis there are no problems. As it turns out issue is in slow SPOP handling in MiniRedis.

To reproduce issue run the following test:

import (
	"github.com/alicebob/miniredis/v2"
	"github.com/go-redis/redis/v8"
	"github.com/stretchr/testify/assert"
)
func Test_MR(t *testing.T) {
	assert := assert.New(t)
	mr := miniredis.RunT(t)
	client := redis.NewClient(&redis.Options{
		Addr: mr.Addr(),
	})

	key := "test"
	items := 10000 // play around with this variable

	arr := []interface{}{}
	for i := 0; i < items; i++ {
		arr = append(arr, i)
	}

	client.SAdd(context.TODO(), key, arr)
	_, err := client.SPopN(context.TODO(), key, int64(items)).Result()
	assert.NoError(err)
}

Then execute test with tracing go test -timeout 30s -run ^Test_MR$ <package name where this test is located> -trace trace.out.
After that run trace analysis tool go tool trace trace.out

In traces you will see that MiniRedis is the one that takes almost all CPU resources. It tries to sort slice on every item removal:

sort.pdqsort:114
--
sort.pdqsort:125
sort.pdqsort:125
sort.Sort:48
sort.Strings:164
github.com/alicebob/miniredis/v2.(*RedisDB).setMembers:297
github.com/alicebob/miniredis/v2.(*Miniredis).cmdSpop.func1:403
github.com/alicebob/miniredis/v2.withTx:92
github.com/alicebob/miniredis/v2.(*Miniredis).cmdSpop:384
...

This are the following code lines:

members := db.setMembers(opts.key)
and

miniredis/db.go

Line 297 in fb75409

sort.Strings(members)

My proposal is to use golang's map for set storage. Or continue to use slices and optimize multiple random set member removal.

@alicebob
Copy link
Owner

Hi,

thanks for your issue.

For the spop (which takes a random one), it doesn't make sense to sort the list, so we could try just skipping the sort in that case (other cases sorting is nice to keep things predictable). I'll give it a try in a few days, or feel free to open a PR. I can't promise that'll fix everything, and there's also a limit how complicated I want to make miniredis in general.

@alicebob alicebob mentioned this issue Jun 24, 2023
@alicebob
Copy link
Owner

The linked PR makes the test go from 8 seconds to 0.017 seconds. The set is already a map internally, btw. It uses a list to pick a random element.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants