Skip to content

Change zstd compression algorithm #1069

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

Closed
wants to merge 3 commits into from
Closed

Conversation

jarifibrahim
Copy link
Contributor

@jarifibrahim jarifibrahim commented Oct 7, 2019

NOTE: The following benchmarks are invalid. The default compression level in both the implementations is different and that leads to the performance difference. On same compression levels, both the implementations perform similarly in terms of compression speed but there's a difference in decompression speed.

Currently, we use https://github.com/DataDog/zstd implementation for ZSTD compression and https://github.com/valyala/gozstd seems to be faster than https://github.com/DataDog/zstd . This PR
proposes use of valyala/gozstd instead of DataDog/zstd.

benchstat zstd.txt gozstd.txt 
name                       old time/op  new time/op  delta
Compression/compress-16     213ms ± 6%   116ms ± 3%  -45.79%  (p=0.000 n=9+10)
Compression/decompress-16  48.5ms ± 3%  45.0ms ± 1%   -7.30%  (p=0.000 n=10+10)

The following script was used for benchmarking

var a []byte
func BenchmarkCompression(b *testing.B) {
	opts := Options{BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
	count := uint64(1e6)
	builder := NewTableBuilder(opts)
	// Approx 64 MB table size
	for i := uint64(0); i < count*2; i++ {
		k := fmt.Sprintf("%016x", i)
		v := fmt.Sprintf("%016d", i)
		builder.Add([]byte(k), y.ValueStruct{Value: []byte(v)})
	}
	// Build a dummy table so that we can use the same buffer for benchmarks
	buf := builder.Finish()

	var x []byte
	var err error
	// Used for decompression benchmarks
	// cbuf, err := zstd.Compress(nil, buf) 	// Use this buffer for zstd.
	// require.NoError(b, err)
	cbuf1 := gozstd.Compress(nil, buf)

	fmt.Println("len of buf", humanize.Bytes(uint64(len(buf))))
	b.ResetTimer()
	b.Run("compress", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			x = gozstd.Compress(nil, buf) // change this for zstd.
		}
		a = x
	})
	b.Run("decompress", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			x, err = gozstd.Decompress(nil, cbuf1) // change this for zstd.
			if err != nil {
				panic(err)
			}
		}
		a = x
	})
}

The following is the benchmark of time taken to build a table in badger (https://github.com/dgraph-io/badger/blob/f50343ff404d8198df6dc83755ec2eab863d5ff2/table/builder_test.go#L116)

benchstat master-builder.txt new-builder.txt                       
name        old time/op  new time/op  delta
Builder-16   1.02s ± 1%   0.73s ± 2%  -27.77%  (p=0.000 n=9+10)

This change is Reviewable

@jarifibrahim jarifibrahim requested a review from a team October 7, 2019 13:42
Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ A review job has been created and sent to the PullRequest network.


@jarifibrahim you can click here to see the review status or cancel the code review job.

@coveralls
Copy link

coveralls commented Oct 7, 2019

Coverage Status

Coverage decreased (-0.02%) to 77.53% when pulling d6d06ca on ibrahim/gozstd into f50343f on master.

@ashish-goswami
Copy link
Contributor

Benchmarks are surely better for gozstd than zstd. But we should also consider stability and support for both. Just try to find who are users of both libraries(if they are public) and using those in production.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that the goztsd can panic, and does not return a result.


Reviewed with ❤️ by PullRequest

@@ -347,7 +347,7 @@ func (b *Builder) compressData(data []byte) ([]byte, error) {
case options.Snappy:
return snappy.Encode(nil, data), nil
case options.ZSTD:
return zstd.Compress(nil, data)
return gozstd.Compress(nil, data), nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although gozstd does not return an error, it will panic instead. If you want to keep a consistent behavior, and not kill the server when something goes wrong, catch the panic in a recover.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say stick with consistency, and keep the current behavior.
Furthermore, I think it's better to keep issues isolated, and not have to depend on the error handling/panic handling behavior of the caller.

@@ -125,7 +125,7 @@ func BenchmarkBuilder(b *testing.B) {

keysCount := 1300000 // This number of entries consumes ~64MB of memory.
for i := 0; i < b.N; i++ {
opts := Options{BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering why this option wasn't required before.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, great job including the benchmarks in the comments.


Reviewed with ❤️ by PullRequest

Copy link
Contributor Author

@jarifibrahim jarifibrahim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashish-goswami gozstd is used by https://github.com/VictoriaMetrics/VictoriaMetrics

Reviewable status: 0 of 5 files reviewed, 2 unresolved discussions (waiting on @ashish-goswami, @manishrjain, and @pullrequest[bot])


table/builder.go, line 350 at r1 (raw file):

Previously, pullrequest[bot] wrote…

although gozstd does not return an error, it will panic instead. If you want to keep a consistent behavior, and not kill the server when something goes wrong, catch the panic in a recover.

I could try to recover the panic here but the finishBlock function that calls this function will crash the process if compress returns an error. So the question being, do we want to crash the process by a panic or by returning an error.

https://github.com/dgraph-io/badger/blob/f50343ff404d8198df6dc83755ec2eab863d5ff2/table/builder.go#L162-L164

I'd prefer to keep it this way.


table/builder_test.go, line 128 at r1 (raw file):

Previously, pullrequest[bot] wrote…

Just wondering why this option wasn't required before.

This option isn't really needed. I added it because I realized it was missing from the benchmark.

Copy link
Contributor

@ashish-goswami ashish-goswami left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewable status: 0 of 5 files reviewed, 2 unresolved discussions (waiting on @ashish-goswami, @manishrjain, and @pullrequest[bot])

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think panic should be recovered, and keep consistent behaviors, but it isn't a major blocker for me.


Reviewed with ❤️ by PullRequest

@jarifibrahim
Copy link
Contributor Author

Benchmarks show LZ4 is faster than zstd and so we don't need this PR anymore.

go test -v -run ^$ -bench BenchmarkTableCompression
data size 4056
goos: linux
goarch: amd64
pkg: github.com/dgraph-io/badger/table
BenchmarkTableCompression/Compression/Snappy-16 	   	   	    	  148005	      8103 ns/op
BenchmarkTableCompression/Compression/LZ4-16      	   	   	   	  	  192206	      5589 ns/op
BenchmarkTableCompression/Compression/ZSTD_-_Datadog-16         	   24697	     47433 ns/op
BenchmarkTableCompression/Decompression/Snappy-16               	  308114	      3952 ns/op
BenchmarkTableCompression/Decompression/LZ4-16                  	  379030	      2728 ns/op
BenchmarkTableCompression/Decompression/ZSTD_-_Datadog-16       	  100808	     11431 ns/op
PASS
ok  	github.com/dgraph-io/badger/table	8.708s

https://gist.github.com/jarifibrahim/50b596c233aa9d82706aa0df65a546ec used for benchmarking.

@arl
Copy link

arl commented Nov 30, 2019

I know this is closed but there's another reason to switch zstd library, a serious memory corruption bug in DataDog/zstd, that can either lead to segfaults, lower compression ratio, or nothing depending on your usage of the library.

Please see DataDog/zstd#22

@joshua-goldstein joshua-goldstein deleted the ibrahim/gozstd branch October 14, 2022 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants