Deprecated: Function get_magic_quotes_gpc() is deprecated in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 99

Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 619

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1169

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176
8000 backend/compress: add zstd compression by A1ex3 · Pull Request #8756 · rclone/rclone · GitHub
Nothing Special   »   [go: up one dir, main page]

Skip to content

Conversation

A1ex3
Copy link
@A1ex3 A1ex3 commented Aug 18, 2025

What is the purpose of this change?

Added support for reading and writing seekable Zstd-compressed archives using github.com/klauspost/compress/zstd and github.com/SaveTheRbtz/zstd-seekable-format-go/pkg (ZSTD seekable compression format implementation in Go).

Updated Go version from 1.24.0 to 1.24.4 to meet the requirements of github.com/SaveTheRbtz/zstd-seekable-format-go/pkg.

Checklist

  • I have read the contribution guidelines.
  • I have added tests for all changes in this PR if appropriate.
  • I have added documentation for the changes if appropriate.
  • All commit messages are in house style.
  • I'm done, this Pull Request is ready for review :-)

@A1ex3 A1ex3 force-pushed the feat-compress-zstd branch 2 times, most recently from b920257 to cb92bc7 Compare August 18, 2025 13:20
Added support for reading and writing zstd-compressed archives in seekable format
using "github.com/klauspost/compress/zstd" and
"github.com/SaveTheRbtz/zstd-seekable-format-go/pkg".

Bumped Go version from 1.24.0 to 1.24.4 due to requirements of
"github.com/SaveTheRbtz/zstd-seekable-format-go/pkg".
@A1ex3 A1ex3 force-pushed the feat-compress-zstd branch from cb92bc7 to f2b7e5b Compare August 19, 2025 06:33
@kapitainsky
Copy link
Contributor
kapitainsky commented Aug 20, 2025

Great idea to introduce this modern compression algorithm. My question is why you limit compression levels to just 4 options? I think it would be much more useful to support all standard levels 1 to 19. I do not see any reason why to use only 4 arbitrary selected levels. Or is it something enforced by zstd golang implementation?

For example myself I often use level 17 for online backup software as its speed on my computer nicely matches my max Internet uplink speed. I do not see why to remove option for fine tuning like this when it is available.

@A1ex3
Copy link
Author
A1ex3 commented Aug 20, 2025

The Go zstd library github.com/klauspost/compress/zstd supports only four compression levels:

// EncoderLevel predefines encoder compression levels.
// Only use the constants made available, since the actual mapping
// of these values are very likely to change and your compression could change
// unpredictably when upgrading the library.
type EncoderLevel int

const (
	speedNotSet EncoderLevel = iota

	// SpeedFastest will choose the fastest reasonable compression.
	// This is roughly equivalent to the fastest Zstandard mode.
	SpeedFastest

	// SpeedDefault is the default "pretty fast" compression option.
	// This is roughly equivalent to the default Zstandard mode (level 3).
	SpeedDefault

	// SpeedBetterCompression will yield better compression than the default.
	// Currently it is about zstd level 7-8 with ~ 2x-3x the default CPU usage.
	// By using this, notice that CPU usage may go up in the future.
	SpeedBetterCompression

	// SpeedBestCompression will choose the best available compression option.
	// This will offer the best compression no matter the CPU cost.
	SpeedBestCompression

	// speedLast should be kept as the last actual compression option.
	// The is not for external usage, but is used to keep track of the valid options.
	speedLast
)

The EncoderLevelFromZstd function allows converting a standard zstd level to one of the levels supported by the library. However, there is practically no benefit from this conversion:

// EncoderLevelFromZstd will return an encoder level that closest matches the compression
// ratio of a specific zstd compression level.
// Many input values will provide the same compression level.
func EncoderLevelFromZstd(level int) EncoderLevel {
	switch {
	case level < 3:
		return SpeedFastest
	case level >= 3 && level < 6:
		return SpeedDefault
	case level >= 6 && level < 10:
		return SpeedBetterCompression
	default:
		return SpeedBestCompression
	}
}

@kapitainsky
Copy link
Contributor
kapitainsky commented Aug 20, 2025

The Go zstd library github.com/klauspost/compress/zstd supports only four compression levels:

Thank you for clarification. More granular levels would be nice but definitely are not critical. zstd with only 4 levels will make day and night difference compared to gzip anyway.

@A1ex3 A1ex3 force-pushed the feat-compress-zstd branch from 576abc0 to 3edb8cc Compare August 27, 2025 01:07
Copy link
Member
@ncw ncw left a comment

Choose a reason for hiding this comment

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

This looks like a useful feature - thank you :-)

All those switch statements

switch {
	case Gzip:
	case Zstd:
	default:
}

Have made the code rather untidy though.

What do you think about factoring it into two implementations, defined by an interface. You can then separate the implementations into zstd.go and gzip.go (say)

That should restore a bit of sanity to the code.

@A1ex3 A1ex3 force-pushed the feat-compress-zstd branch from c043dd6 to 43b69ba Compare August 29, 2025 21:16
@A1ex3
Copy link
Author
A1ex3 commented Aug 29, 2025

@ncw Thanks for the suggestion! I've refactored the compression handling - almost everything has been moved behind an interface, with separate implementations in gzip_handler.go, zstd_handler.go, uncompressed_handler.go and unknown_handler.go. The codebase should now be much cleaner.

@A1ex3 A1ex3 force-pushed the feat-compress-zstd branch from 43b69ba to ca6d6f7 Compare August 30, 2025 08:59
- Replaced switch-based handling with interface-driven design
- Added separate implementations: gzip_handler.go, zstd_handler.go, uncompressed_handler.go and unknown_handler.go
@A1ex3 A1ex3 force-pushed the feat-compress-zstd branch from ca6d6f7 to 1a93d65 Compare August 30, 2025 09:18
@A1ex3 A1ex3 requested a review from ncw September 1, 2025 12:24
@ncw
Copy link
Member
ncw commented Sep 1, 2025

The refactor looks great - thank you :-)

Can you add another target to https://github.com/rclone/rclone/blob/master/fstest/test_all/config.yaml

 - backend:  "compress"
   remote:   "TestCompressZstd:"
   fastlist: false

TestCompressZstd can be something like

[TestCompressZstd]
type = compress
remote = /tmp/rclone-compress-test-zstd
mode = zstd

And then check the integration tests all pass with test_all -remotes TestCompressZstd:,TestCompress:

Once we merge that will go into the integration tester and be run once per day.

Would it be possible to lose some dependencies? I think github.com/SaveTheRbtz/zstd-seekable-format-go uses go.uber.org/atomic which can be done in stdlib now I think and go.uber.org/zap - would it be possible to use the stdlib log/slog? That will integrate with rclone's logging system.

I will try to give the code another review in a day or two.

…import, fix metadata, update deps

- Add "TestCompressZstd" target in "fstest/test_all/config.yaml"
- Refactor metadata handling
- Fix metadata size reference in Zstd compression handler
- Update package import from "github.com/SaveTheRbtz/zstd-seekable-format-go/pkg@v0.8.0" to "github.com/a1ex3/zstd-seekable-format-go/pkg@v0.10.0"
- Lower minimal Go version from 1.24.4 to 1.24.0
@A1ex3
Copy link
Author
A1ex3 commented Sep 3, 2025

Can you add another target to https://github.com/rclone/rclone/blob/master/fstest/test_all/config.yaml

 - backend:  "compress"
   remote:   "TestCompressZstd:"
   fastlist: false

TestCompressZstd can be something like

[TestCompressZstd]
type = compress
remote = /tmp/rclone-compress-test-zstd
mode = zstd

And then check the integration tests all pass with test_all -remotes TestCompressZstd:,TestCompress:

@ncw Added a target in fstest/test_all/config.yaml and ran integration tests with the following configuration:

[TestCompress]
type = compress
remote = /tmp/rclone-compress-test-gzip
mode = gzip
level = -1

[TestCompressZstd]
type = compress
remote = /tmp/rclone-compress-test-zstd
mode = zstd
level = 2

Results:
image

I decided to fork github.com/SaveTheRbtz/zstd-seekable-format-go and removed logging, since it was mostly needed for debugging the library itself.
In addition:

  • replaced go.uber.org/atomic with sync/atomic
  • removed go.uber.org/* dependencies
  • lowered the minimal Go version to 1.24.0

Now rclone uses github.com/a1ex3/zstd-seekable-format-go/pkg.

Additionally, fixed discovered issues with zstd in backend/compress.

@ncw
Copy link
Member
ncw commented Oct 6, 2025

Hi @A1ex3 sorry for the delay in looking at this. Your changes are great thank you :-) We ran the integration tests one last time locally and the bisync tests are failing - which they weren't for you earlier. Can you help?

Thanks

Nick

@A1ex3
Copy link
Author
A1ex3 commented Oct 7, 2025

Hi @ncw,
I’ve just merged the latest changes from master locally into feat-compress-zstd branch and re-ran the integration tests for TestCompress and TestCompressZstd - both passed successfully.
Could you please share the test report or steps to reproduce the failing bisync tests?
Also, do they fail for a specific compression type or for all of them?

@gordan-bobic
Copy link

Is this likely to get merged imminently? I could really do with this feature.

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 this pull request may close these issues.

4 participants

0