mirror of
				https://github.com/minio/minio.git
				synced 2025-10-29 15:55:00 -04:00 
			
		
		
		
	Switch to Snappy -> S2 compression (#8189)
This commit is contained in:
		
							parent
							
								
									be313f1758
								
							
						
					
					
						commit
						ff726969aa
					
				| @ -251,18 +251,18 @@ var ( | |||||||
| 	// configuration must be present. | 	// configuration must be present. | ||||||
| 	globalAutoEncryption bool | 	globalAutoEncryption bool | ||||||
| 
 | 
 | ||||||
| 	// Is compression include extensions/content-types set. | 	// Is compression include extensions/content-types set? | ||||||
| 	globalIsEnvCompression bool | 	globalIsEnvCompression bool | ||||||
| 
 | 
 | ||||||
| 	// Is compression enabeld. | 	// Is compression enabled? | ||||||
| 	globalIsCompressionEnabled = false | 	globalIsCompressionEnabled = false | ||||||
| 
 | 
 | ||||||
| 	// Include-list for compression. | 	// Include-list for compression. | ||||||
| 	globalCompressExtensions = []string{".txt", ".log", ".csv", ".json"} | 	globalCompressExtensions = []string{".txt", ".log", ".csv", ".json", ".tar", ".xml", ".bin"} | ||||||
| 	globalCompressMimeTypes  = []string{"text/csv", "text/plain", "application/json"} | 	globalCompressMimeTypes  = []string{"text/*", "application/json", "application/xml"} | ||||||
| 
 | 
 | ||||||
| 	// Some standard object extensions which we strictly dis-allow for compression. | 	// Some standard object extensions which we strictly dis-allow for compression. | ||||||
| 	standardExcludeCompressExtensions = []string{".gz", ".bz2", ".rar", ".zip", ".7z"} | 	standardExcludeCompressExtensions = []string{".gz", ".bz2", ".rar", ".zip", ".7z", ".xz", ".mp4", ".mkv", ".mov"} | ||||||
| 
 | 
 | ||||||
| 	// Some standard content-types which we strictly dis-allow for compression. | 	// Some standard content-types which we strictly dis-allow for compression. | ||||||
| 	standardExcludeCompressContentTypes = []string{"video/*", "audio/*", "application/zip", "application/x-gzip", "application/x-zip-compressed", " application/x-compress", "application/x-spoon"} | 	standardExcludeCompressContentTypes = []string{"video/*", "audio/*", "application/zip", "application/x-gzip", "application/x-zip-compressed", " application/x-compress", "application/x-spoon"} | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ | |||||||
| package cmd | package cmd | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"archive/zip" |  | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| @ -31,6 +30,7 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/klauspost/compress/zip" | ||||||
| 	"github.com/minio/minio/cmd/crypto" | 	"github.com/minio/minio/cmd/crypto" | ||||||
| 	"github.com/minio/minio/cmd/logger" | 	"github.com/minio/minio/cmd/logger" | ||||||
| 	"github.com/minio/minio/pkg/event" | 	"github.com/minio/minio/pkg/event" | ||||||
|  | |||||||
| @ -33,7 +33,8 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| 	"unicode/utf8" | 	"unicode/utf8" | ||||||
| 
 | 
 | ||||||
| 	snappy "github.com/golang/snappy" | 	"github.com/klauspost/compress/s2" | ||||||
|  | 	"github.com/klauspost/readahead" | ||||||
| 	"github.com/minio/minio-go/v6/pkg/s3utils" | 	"github.com/minio/minio-go/v6/pkg/s3utils" | ||||||
| 	"github.com/minio/minio/cmd/crypto" | 	"github.com/minio/minio/cmd/crypto" | ||||||
| 	xhttp "github.com/minio/minio/cmd/http" | 	xhttp "github.com/minio/minio/cmd/http" | ||||||
| @ -56,6 +57,12 @@ const ( | |||||||
| 	minioMetaTmpBucket = minioMetaBucket + "/tmp" | 	minioMetaTmpBucket = minioMetaBucket + "/tmp" | ||||||
| 	// DNS separator (period), used for bucket name validation. | 	// DNS separator (period), used for bucket name validation. | ||||||
| 	dnsDelimiter = "." | 	dnsDelimiter = "." | ||||||
|  | 	// On compressed files bigger than this; | ||||||
|  | 	compReadAheadSize = 100 << 20 | ||||||
|  | 	// Read this many buffers ahead. | ||||||
|  | 	compReadAheadBuffers = 5 | ||||||
|  | 	// Size of each buffer. | ||||||
|  | 	compReadAheadBufSize = 1 << 20 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // isMinioBucket returns true if given bucket is a MinIO internal | // isMinioBucket returns true if given bucket is a MinIO internal | ||||||
| @ -337,6 +344,22 @@ func (o ObjectInfo) IsCompressed() bool { | |||||||
| 	return ok | 	return ok | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // IsCompressedOK returns whether the object is compressed and can be decompressed. | ||||||
|  | func (o ObjectInfo) IsCompressedOK() (bool, error) { | ||||||
|  | 	scheme, ok := o.UserDefined[ReservedMetadataPrefix+"compression"] | ||||||
|  | 	if !ok { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	if crypto.IsEncrypted(o.UserDefined) { | ||||||
|  | 		return true, fmt.Errorf("compression %q and encryption enabled on same object", scheme) | ||||||
|  | 	} | ||||||
|  | 	switch scheme { | ||||||
|  | 	case compressionAlgorithmV1, compressionAlgorithmV2: | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 	return true, fmt.Errorf("unknown compression scheme: %s", scheme) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // GetActualSize - read the decompressed size from the meta json. | // GetActualSize - read the decompressed size from the meta json. | ||||||
| func (o ObjectInfo) GetActualSize() int64 { | func (o ObjectInfo) GetActualSize() int64 { | ||||||
| 	metadata := o.UserDefined | 	metadata := o.UserDefined | ||||||
| @ -364,13 +387,20 @@ func isCompressible(header http.Header, object string) bool { | |||||||
| func excludeForCompression(header http.Header, object string) bool { | func excludeForCompression(header http.Header, object string) bool { | ||||||
| 	objStr := object | 	objStr := object | ||||||
| 	contentType := header.Get(xhttp.ContentType) | 	contentType := header.Get(xhttp.ContentType) | ||||||
| 	if globalIsCompressionEnabled { | 	if !globalIsCompressionEnabled { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// We strictly disable compression for standard extensions/content-types (`compressed`). | 	// We strictly disable compression for standard extensions/content-types (`compressed`). | ||||||
| 	if hasStringSuffixInSlice(objStr, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, contentType) { | 	if hasStringSuffixInSlice(objStr, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, contentType) { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	// Filter compression includes. | 	// Filter compression includes. | ||||||
| 		if len(globalCompressExtensions) > 0 || len(globalCompressMimeTypes) > 0 { | 	if len(globalCompressExtensions) == 0 || len(globalCompressMimeTypes) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	extensions := globalCompressExtensions | 	extensions := globalCompressExtensions | ||||||
| 	mimeTypes := globalCompressMimeTypes | 	mimeTypes := globalCompressMimeTypes | ||||||
| 	if hasStringSuffixInSlice(objStr, extensions) || hasPattern(mimeTypes, contentType) { | 	if hasStringSuffixInSlice(objStr, extensions) || hasPattern(mimeTypes, contentType) { | ||||||
| @ -378,15 +408,13 @@ func excludeForCompression(header http.Header, object string) bool { | |||||||
| 	} | 	} | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // Utility which returns if a string is present in the list. | // Utility which returns if a string is present in the list. | ||||||
|  | // Comparison is case insensitive. | ||||||
| func hasStringSuffixInSlice(str string, list []string) bool { | func hasStringSuffixInSlice(str string, list []string) bool { | ||||||
|  | 	str = strings.ToLower(str) | ||||||
| 	for _, v := range list { | 	for _, v := range list { | ||||||
| 		if strings.HasSuffix(str, v) { | 		if strings.HasSuffix(str, strings.ToLower(v)) { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -413,7 +441,7 @@ func getPartFile(entries []string, partNumber int, etag string) string { | |||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Returs the compressed offset which should be skipped. | // Returns the compressed offset which should be skipped. | ||||||
| func getCompressedOffsets(objectInfo ObjectInfo, offset int64) (int64, int64) { | func getCompressedOffsets(objectInfo ObjectInfo, offset int64) (int64, int64) { | ||||||
| 	var compressedOffset int64 | 	var compressedOffset int64 | ||||||
| 	var skipLength int64 | 	var skipLength int64 | ||||||
| @ -494,7 +522,10 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, pcfn CheckCopyPrecondi | |||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	isEncrypted := crypto.IsEncrypted(oi.UserDefined) | 	isEncrypted := crypto.IsEncrypted(oi.UserDefined) | ||||||
| 	isCompressed := oi.IsCompressed() | 	isCompressed, err := oi.IsCompressedOK() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, 0, 0, err | ||||||
|  | 	} | ||||||
| 	var skipLen int64 | 	var skipLen int64 | ||||||
| 	// Calculate range to read (different for | 	// Calculate range to read (different for | ||||||
| 	// e.g. encrypted/compressed objects) | 	// e.g. encrypted/compressed objects) | ||||||
| @ -602,10 +633,23 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, pcfn CheckCopyPrecondi | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			// Decompression reader. | 			// Decompression reader. | ||||||
| 			snappyReader := snappy.NewReader(inputReader) | 			s2Reader := s2.NewReader(inputReader) | ||||||
| 			// Apply the skipLen and limit on the | 			// Apply the skipLen and limit on the decompressed stream. | ||||||
| 			// decompressed stream | 			err = s2Reader.Skip(decOff) | ||||||
| 			decReader := io.LimitReader(ioutil.NewSkipReader(snappyReader, decOff), decLength) | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			decReader := io.LimitReader(s2Reader, decLength) | ||||||
|  | 			if decLength > compReadAheadSize { | ||||||
|  | 				rah, err := readahead.NewReaderSize(decReader, compReadAheadBuffers, compReadAheadBufSize) | ||||||
|  | 				if err == nil { | ||||||
|  | 					decReader = rah | ||||||
|  | 					cFns = append(cFns, func() { | ||||||
|  | 						rah.Close() | ||||||
|  | 					}) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 			oi.Size = decLength | 			oi.Size = decLength | ||||||
| 
 | 
 | ||||||
| 			// Assemble the GetObjectReader | 			// Assemble the GetObjectReader | ||||||
| @ -760,55 +804,29 @@ func CleanMinioInternalMetadataKeys(metadata map[string]string) map[string]strin | |||||||
| 	return newMeta | 	return newMeta | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // snappyCompressReader compresses data as it reads | // newS2CompressReader will read data from r, compress it and return the compressed data as a Reader. | ||||||
| // from the underlying io.Reader. | // Use Close to ensure resources are released on incomplete streams. | ||||||
| type snappyCompressReader struct { | func newS2CompressReader(r io.Reader) io.ReadCloser { | ||||||
| 	r      io.Reader | 	pr, pw := io.Pipe() | ||||||
| 	w      *snappy.Writer | 	comp := s2.NewWriter(pw) | ||||||
| 	closed bool | 	// Copy input to compressor | ||||||
| 	buf    bytes.Buffer | 	go func() { | ||||||
| } | 		_, err := io.Copy(comp, r) | ||||||
| 
 |  | ||||||
| func newSnappyCompressReader(r io.Reader) *snappyCompressReader { |  | ||||||
| 	cr := &snappyCompressReader{r: r} |  | ||||||
| 	cr.w = snappy.NewBufferedWriter(&cr.buf) |  | ||||||
| 	return cr |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cr *snappyCompressReader) Read(p []byte) (int, error) { |  | ||||||
| 	if cr.closed { |  | ||||||
| 		// if snappy writer is closed r has been completely read, |  | ||||||
| 		// return any remaining data in buf. |  | ||||||
| 		return cr.buf.Read(p) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// read from original using p as buffer |  | ||||||
| 	nr, readErr := cr.r.Read(p) |  | ||||||
| 
 |  | ||||||
| 	// write read bytes to snappy writer |  | ||||||
| 	nw, err := cr.w.Write(p[:nr]) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 		return 0, err | 			comp.Close() | ||||||
|  | 			pw.CloseWithError(err) | ||||||
|  | 			return | ||||||
| 		} | 		} | ||||||
| 	if nw != nr { | 		// Close the stream. | ||||||
| 		return 0, io.ErrShortWrite | 		err = comp.Close() | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// if last of data from reader, close snappy writer to flush |  | ||||||
| 	if readErr == io.EOF { |  | ||||||
| 		err := cr.w.Close() |  | ||||||
| 		cr.closed = true |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return 0, err | 			pw.CloseWithError(err) | ||||||
|  | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 		// Everything ok, do regular close. | ||||||
| 
 | 		pw.Close() | ||||||
| 	// read compressed bytes out of buf | 	}() | ||||||
| 	n, err := cr.buf.Read(p) | 	return pr | ||||||
| 	if readErr != io.EOF && (err == nil || err == io.EOF) { |  | ||||||
| 		err = readErr |  | ||||||
| 	} |  | ||||||
| 	return n, err |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Returns error if the cancelCh has been closed (indicating that S3 client has disconnected) | // Returns error if the cancelCh has been closed (indicating that S3 client has disconnected) | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * MinIO Cloud Storage, (C) 2016 MinIO, Inc. |  * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc. | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @ -21,9 +21,11 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/golang/snappy" | 	"github.com/klauspost/compress/s2" | ||||||
|  | 	"github.com/minio/minio/cmd/crypto" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Tests validate bucket name. | // Tests validate bucket name. | ||||||
| @ -298,10 +300,11 @@ func TestIsCompressed(t *testing.T) { | |||||||
| 	testCases := []struct { | 	testCases := []struct { | ||||||
| 		objInfo ObjectInfo | 		objInfo ObjectInfo | ||||||
| 		result  bool | 		result  bool | ||||||
|  | 		err     bool | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			objInfo: ObjectInfo{ | 			objInfo: ObjectInfo{ | ||||||
| 				UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", | 				UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV1, | ||||||
| 					"content-type": "application/octet-stream", | 					"content-type": "application/octet-stream", | ||||||
| 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
| 			}, | 			}, | ||||||
| @ -309,7 +312,35 @@ func TestIsCompressed(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			objInfo: ObjectInfo{ | 			objInfo: ObjectInfo{ | ||||||
| 				UserDefined: map[string]string{"X-Minio-Internal-XYZ": "golang/snappy/LZ77", | 				UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2, | ||||||
|  | 					"content-type": "application/octet-stream", | ||||||
|  | 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
|  | 			}, | ||||||
|  | 			result: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			objInfo: ObjectInfo{ | ||||||
|  | 				UserDefined: map[string]string{"X-Minio-Internal-compression": "unknown/compression/type", | ||||||
|  | 					"content-type": "application/octet-stream", | ||||||
|  | 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
|  | 			}, | ||||||
|  | 			result: true, | ||||||
|  | 			err:    true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			objInfo: ObjectInfo{ | ||||||
|  | 				UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2, | ||||||
|  | 					"content-type": "application/octet-stream", | ||||||
|  | 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2", | ||||||
|  | 					crypto.SSEIV:   "yes", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			result: true, | ||||||
|  | 			err:    true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			objInfo: ObjectInfo{ | ||||||
|  | 				UserDefined: map[string]string{"X-Minio-Internal-XYZ": "klauspost/compress/s2", | ||||||
| 					"content-type": "application/octet-stream", | 					"content-type": "application/octet-stream", | ||||||
| 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
| 			}, | 			}, | ||||||
| @ -324,11 +355,21 @@ func TestIsCompressed(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	for i, test := range testCases { | 	for i, test := range testCases { | ||||||
|  | 		t.Run(strconv.Itoa(i), func(t *testing.T) { | ||||||
| 			got := test.objInfo.IsCompressed() | 			got := test.objInfo.IsCompressed() | ||||||
| 			if got != test.result { | 			if got != test.result { | ||||||
| 			t.Errorf("Test %d - expected %v but received %v", | 				t.Errorf("IsCompressed: Expected %v but received %v", | ||||||
| 				i+1, test.result, got) | 					test.result, got) | ||||||
| 			} | 			} | ||||||
|  | 			got, gErr := test.objInfo.IsCompressedOK() | ||||||
|  | 			if got != test.result { | ||||||
|  | 				t.Errorf("IsCompressedOK: Expected %v but received %v", | ||||||
|  | 					test.result, got) | ||||||
|  | 			} | ||||||
|  | 			if gErr != nil != test.err { | ||||||
|  | 				t.Errorf("IsCompressedOK: want error: %t, got error: %v", test.err, gErr) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -367,6 +408,13 @@ func TestExcludeForCompression(t *testing.T) { | |||||||
| 			}, | 			}, | ||||||
| 			result: false, | 			result: false, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			object: "object", | ||||||
|  | 			header: http.Header{ | ||||||
|  | 				"Content-Type": []string{"text/something"}, | ||||||
|  | 			}, | ||||||
|  | 			result: false, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	for i, test := range testCases { | 	for i, test := range testCases { | ||||||
| 		globalIsCompressionEnabled = true | 		globalIsCompressionEnabled = true | ||||||
| @ -422,7 +470,7 @@ func TestGetActualSize(t *testing.T) { | |||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			objInfo: ObjectInfo{ | 			objInfo: ObjectInfo{ | ||||||
| 				UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", | 				UserDefined: map[string]string{"X-Minio-Internal-compression": "klauspost/compress/s2", | ||||||
| 					"X-Minio-Internal-actual-size": "100000001", | 					"X-Minio-Internal-actual-size": "100000001", | ||||||
| 					"content-type":                 "application/octet-stream", | 					"content-type":                 "application/octet-stream", | ||||||
| 					"etag":                         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | 					"etag":                         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
| @ -441,7 +489,7 @@ func TestGetActualSize(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			objInfo: ObjectInfo{ | 			objInfo: ObjectInfo{ | ||||||
| 				UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", | 				UserDefined: map[string]string{"X-Minio-Internal-compression": "klauspost/compress/s2", | ||||||
| 					"X-Minio-Internal-actual-size": "841", | 					"X-Minio-Internal-actual-size": "841", | ||||||
| 					"content-type":                 "application/octet-stream", | 					"content-type":                 "application/octet-stream", | ||||||
| 					"etag":                         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | 					"etag":                         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
| @ -451,7 +499,7 @@ func TestGetActualSize(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			objInfo: ObjectInfo{ | 			objInfo: ObjectInfo{ | ||||||
| 				UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", | 				UserDefined: map[string]string{"X-Minio-Internal-compression": "klauspost/compress/s2", | ||||||
| 					"content-type": "application/octet-stream", | 					"content-type": "application/octet-stream", | ||||||
| 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | 					"etag":         "b3ff3ef3789147152fbfbc50efba4bfd-2"}, | ||||||
| 				Parts: []ObjectPartInfo{}, | 				Parts: []ObjectPartInfo{}, | ||||||
| @ -540,7 +588,7 @@ func TestGetCompressedOffsets(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestSnappyCompressReader(t *testing.T) { | func TestS2CompressReader(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		name string | ||||||
| 		data []byte | 		data []byte | ||||||
| @ -554,7 +602,8 @@ func TestSnappyCompressReader(t *testing.T) { | |||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			buf := make([]byte, 100) // make small buffer to ensure multiple reads are required for large case | 			buf := make([]byte, 100) // make small buffer to ensure multiple reads are required for large case | ||||||
| 
 | 
 | ||||||
| 			r := newSnappyCompressReader(bytes.NewReader(tt.data)) | 			r := newS2CompressReader(bytes.NewReader(tt.data)) | ||||||
|  | 			defer r.Close() | ||||||
| 
 | 
 | ||||||
| 			var rdrBuf bytes.Buffer | 			var rdrBuf bytes.Buffer | ||||||
| 			_, err := io.CopyBuffer(&rdrBuf, r, buf) | 			_, err := io.CopyBuffer(&rdrBuf, r, buf) | ||||||
| @ -563,7 +612,7 @@ func TestSnappyCompressReader(t *testing.T) { | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			var stdBuf bytes.Buffer | 			var stdBuf bytes.Buffer | ||||||
| 			w := snappy.NewBufferedWriter(&stdBuf) | 			w := s2.NewWriter(&stdBuf) | ||||||
| 			_, err = io.CopyBuffer(w, bytes.NewReader(tt.data), buf) | 			_, err = io.CopyBuffer(w, bytes.NewReader(tt.data), buf) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| @ -582,7 +631,7 @@ func TestSnappyCompressReader(t *testing.T) { | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			var decBuf bytes.Buffer | 			var decBuf bytes.Buffer | ||||||
| 			decRdr := snappy.NewReader(&rdrBuf) | 			decRdr := s2.NewReader(&rdrBuf) | ||||||
| 			_, err = io.Copy(&decBuf, decRdr) | 			_, err = io.Copy(&decBuf, decRdr) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
|  | |||||||
| @ -61,6 +61,7 @@ var supportedHeadGetReqParams = map[string]string{ | |||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	compressionAlgorithmV1 = "golang/snappy/LZ77" | 	compressionAlgorithmV1 = "golang/snappy/LZ77" | ||||||
|  | 	compressionAlgorithmV2 = "klauspost/compress/s2" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // setHeadGetRespHeaders - set any requested parameters as response headers. | // setHeadGetRespHeaders - set any requested parameters as response headers. | ||||||
| @ -800,13 +801,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | |||||||
| 	if isCompressed { | 	if isCompressed { | ||||||
| 		compressMetadata = make(map[string]string, 2) | 		compressMetadata = make(map[string]string, 2) | ||||||
| 		// Preserving the compression metadata. | 		// Preserving the compression metadata. | ||||||
| 		compressMetadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 		compressMetadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2 | ||||||
| 		compressMetadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10) | 		compressMetadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10) | ||||||
| 		// Remove all source encrypted related metadata to | 		// Remove all source encrypted related metadata to | ||||||
| 		// avoid copying them in target object. | 		// avoid copying them in target object. | ||||||
| 		crypto.RemoveInternalEntries(srcInfo.UserDefined) | 		crypto.RemoveInternalEntries(srcInfo.UserDefined) | ||||||
| 
 | 
 | ||||||
| 		reader = newSnappyCompressReader(gr) | 		s2c := newS2CompressReader(gr) | ||||||
|  | 		defer s2c.Close() | ||||||
|  | 		reader = s2c | ||||||
| 		length = -1 | 		length = -1 | ||||||
| 	} else { | 	} else { | ||||||
| 		// Remove the metadata for remote calls. | 		// Remove the metadata for remote calls. | ||||||
| @ -1175,7 +1178,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req | |||||||
| 
 | 
 | ||||||
| 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { | 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { | ||||||
| 		// Storing the compression metadata. | 		// Storing the compression metadata. | ||||||
| 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2 | ||||||
| 		metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10) | 		metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10) | ||||||
| 
 | 
 | ||||||
| 		actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) | 		actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) | ||||||
| @ -1185,7 +1188,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Set compression metrics. | 		// Set compression metrics. | ||||||
| 		reader = newSnappyCompressReader(actualReader) | 		s2c := newS2CompressReader(actualReader) | ||||||
|  | 		defer s2c.Close() | ||||||
|  | 		reader = s2c | ||||||
| 		size = -1   // Since compressed size is un-predictable. | 		size = -1   // Since compressed size is un-predictable. | ||||||
| 		md5hex = "" // Do not try to verify the content. | 		md5hex = "" // Do not try to verify the content. | ||||||
| 		sha256hex = "" | 		sha256hex = "" | ||||||
| @ -1389,7 +1394,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r | |||||||
| 
 | 
 | ||||||
| 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) { | 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) { | ||||||
| 		// Storing the compression metadata. | 		// Storing the compression metadata. | ||||||
| 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2 | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	opts, err = putOpts(ctx, r, bucket, object, metadata) | 	opts, err = putOpts(ctx, r, bucket, object, metadata) | ||||||
| @ -1632,7 +1637,9 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | |||||||
| 	isCompressed := compressPart | 	isCompressed := compressPart | ||||||
| 	// Compress only if the compression is enabled during initial multipart. | 	// Compress only if the compression is enabled during initial multipart. | ||||||
| 	if isCompressed { | 	if isCompressed { | ||||||
| 		reader = newSnappyCompressReader(gr) | 		s2c := newS2CompressReader(gr) | ||||||
|  | 		defer s2c.Close() | ||||||
|  | 		reader = s2c | ||||||
| 		length = -1 | 		length = -1 | ||||||
| 	} else { | 	} else { | ||||||
| 		reader = gr | 		reader = gr | ||||||
| @ -1872,7 +1879,9 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Set compression metrics. | 		// Set compression metrics. | ||||||
| 		reader = newSnappyCompressReader(actualReader) | 		s2c := newS2CompressReader(actualReader) | ||||||
|  | 		defer s2c.Close() | ||||||
|  | 		reader = s2c | ||||||
| 		size = -1   // Since compressed size is un-predictable. | 		size = -1   // Since compressed size is un-predictable. | ||||||
| 		md5hex = "" // Do not try to verify the content. | 		md5hex = "" // Do not try to verify the content. | ||||||
| 		sha256hex = "" | 		sha256hex = "" | ||||||
|  | |||||||
| @ -129,6 +129,10 @@ func setupTestReadDirGeneric(t *testing.T) (testResults []result) { | |||||||
| 
 | 
 | ||||||
| // Test to read non-empty directory with symlinks. | // Test to read non-empty directory with symlinks. | ||||||
| func setupTestReadDirSymlink(t *testing.T) (testResults []result) { | func setupTestReadDirSymlink(t *testing.T) (testResults []result) { | ||||||
|  | 	if runtime.GOOS != "Windows" { | ||||||
|  | 		t.Log("symlinks not available on windows") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 	dir := mustSetupDir(t) | 	dir := mustSetupDir(t) | ||||||
| 	entries := []string{} | 	entries := []string{} | ||||||
| 	for i := 0; i < 10; i++ { | 	for i := 0; i < 10; i++ { | ||||||
|  | |||||||
| @ -20,16 +20,14 @@ import ( | |||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
|  | 	"encoding/gob" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 
 |  | ||||||
| 	"encoding/gob" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 
 |  | ||||||
| 	"fmt" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/minio/minio/cmd/http" | 	"github.com/minio/minio/cmd/http" | ||||||
|  | |||||||
| @ -358,10 +358,12 @@ func (s *storageRESTServer) ReadFileStreamHandler(w http.ResponseWriter, r *http | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	defer rc.Close() | 	defer rc.Close() | ||||||
|  | 
 | ||||||
| 	w.Header().Set(xhttp.ContentLength, strconv.Itoa(length)) | 	w.Header().Set(xhttp.ContentLength, strconv.Itoa(length)) | ||||||
| 
 | 
 | ||||||
| 	io.Copy(w, rc) | 	io.Copy(w, rc) | ||||||
| 	w.(http.Flusher).Flush() | 	w.(http.Flusher).Flush() | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // readMetadata func provides the function types for reading leaf metadata. | // readMetadata func provides the function types for reading leaf metadata. | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ | |||||||
| package cmd | package cmd | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"archive/zip" |  | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @ -29,13 +28,12 @@ import ( | |||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" |  | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	humanize "github.com/dustin/go-humanize" | 	"github.com/dustin/go-humanize" | ||||||
| 	snappy "github.com/golang/snappy" |  | ||||||
| 	"github.com/gorilla/mux" | 	"github.com/gorilla/mux" | ||||||
| 	"github.com/gorilla/rpc/v2/json2" | 	"github.com/gorilla/rpc/v2/json2" | ||||||
|  | 	"github.com/klauspost/compress/zip" | ||||||
| 	miniogopolicy "github.com/minio/minio-go/v6/pkg/policy" | 	miniogopolicy "github.com/minio/minio-go/v6/pkg/policy" | ||||||
| 	"github.com/minio/minio-go/v6/pkg/s3utils" | 	"github.com/minio/minio-go/v6/pkg/s3utils" | ||||||
| 	"github.com/minio/minio-go/v6/pkg/set" | 	"github.com/minio/minio-go/v6/pkg/set" | ||||||
| @ -995,7 +993,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { | |||||||
| 	} | 	} | ||||||
| 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { | 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { | ||||||
| 		// Storing the compression metadata. | 		// Storing the compression metadata. | ||||||
| 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2 | ||||||
| 		metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10) | 		metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10) | ||||||
| 
 | 
 | ||||||
| 		actualReader, err := hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat) | 		actualReader, err := hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat) | ||||||
| @ -1006,7 +1004,9 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { | |||||||
| 
 | 
 | ||||||
| 		// Set compression metrics. | 		// Set compression metrics. | ||||||
| 		size = -1 // Since compressed size is un-predictable. | 		size = -1 // Since compressed size is un-predictable. | ||||||
| 		reader = newSnappyCompressReader(actualReader) | 		s2c := newS2CompressReader(actualReader) | ||||||
|  | 		defer s2c.Close() | ||||||
|  | 		reader = s2c | ||||||
| 		hashReader, err = hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat) | 		hashReader, err = hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			writeWebErrorResponse(w, err) | 			writeWebErrorResponse(w, err) | ||||||
| @ -1234,7 +1234,6 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { | |||||||
| 	ctx := newContext(r, w, "WebDownloadZip") | 	ctx := newContext(r, w, "WebDownloadZip") | ||||||
| 	defer logger.AuditLog(w, r, "WebDownloadZip", mustGetClaimsFromToken(r)) | 	defer logger.AuditLog(w, r, "WebDownloadZip", mustGetClaimsFromToken(r)) | ||||||
| 
 | 
 | ||||||
| 	var wg sync.WaitGroup |  | ||||||
| 	objectAPI := web.ObjectAPI() | 	objectAPI := web.ObjectAPI() | ||||||
| 	if objectAPI == nil { | 	if objectAPI == nil { | ||||||
| 		writeWebErrorResponse(w, errServerNotInitialized) | 		writeWebErrorResponse(w, errServerNotInitialized) | ||||||
| @ -1306,7 +1305,6 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { | |||||||
| 	archive := zip.NewWriter(w) | 	archive := zip.NewWriter(w) | ||||||
| 	defer archive.Close() | 	defer archive.Close() | ||||||
| 
 | 
 | ||||||
| 	var length int64 |  | ||||||
| 	for _, object := range args.Objects { | 	for _, object := range args.Objects { | ||||||
| 		// Writes compressed object file to the response. | 		// Writes compressed object file to the response. | ||||||
| 		zipit := func(objectName string) error { | 		zipit := func(objectName string) error { | ||||||
| @ -1318,58 +1316,28 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { | |||||||
| 			defer gr.Close() | 			defer gr.Close() | ||||||
| 
 | 
 | ||||||
| 			info := gr.ObjInfo | 			info := gr.ObjInfo | ||||||
| 
 |  | ||||||
| 			var actualSize int64 |  | ||||||
| 			if info.IsCompressed() { | 			if info.IsCompressed() { | ||||||
| 				// Read the decompressed size from the meta.json. | 				// For reporting, set the file size to the uncompressed size. | ||||||
| 				actualSize = info.GetActualSize() | 				info.Size = info.GetActualSize() | ||||||
| 				// Set the info.Size to the actualSize. |  | ||||||
| 				info.Size = actualSize |  | ||||||
| 			} | 			} | ||||||
| 			header := &zip.FileHeader{ | 			header := &zip.FileHeader{ | ||||||
| 				Name:   strings.TrimPrefix(objectName, args.Prefix), | 				Name:   strings.TrimPrefix(objectName, args.Prefix), | ||||||
| 				Method: zip.Deflate, | 				Method: zip.Deflate, | ||||||
| 				UncompressedSize64: uint64(length), |  | ||||||
| 				UncompressedSize:   uint32(length), |  | ||||||
| 			} | 			} | ||||||
| 			zipWriter, err := archive.CreateHeader(header) | 			if hasStringSuffixInSlice(info.Name, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, info.ContentType) { | ||||||
|  | 				// We strictly disable compression for standard extensions/content-types. | ||||||
|  | 				header.Method = zip.Store | ||||||
|  | 			} | ||||||
|  | 			writer, err := archive.CreateHeader(header) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				writeWebErrorResponse(w, errUnexpected) | 				writeWebErrorResponse(w, errUnexpected) | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 			var writer io.Writer |  | ||||||
| 
 |  | ||||||
| 			if info.IsCompressed() { |  | ||||||
| 				// Open a pipe for compression |  | ||||||
| 				// Where compressWriter is actually passed to the getObject |  | ||||||
| 				decompressReader, compressWriter := io.Pipe() |  | ||||||
| 				snappyReader := snappy.NewReader(decompressReader) |  | ||||||
| 
 |  | ||||||
| 				// The limit is set to the actual size. |  | ||||||
| 				responseWriter := ioutil.LimitedWriter(zipWriter, 0, actualSize) |  | ||||||
| 				wg.Add(1) //For closures. |  | ||||||
| 				go func() { |  | ||||||
| 					defer wg.Done() |  | ||||||
| 					// Finally, writes to the client. |  | ||||||
| 					_, perr := io.Copy(responseWriter, snappyReader) |  | ||||||
| 
 |  | ||||||
| 					// Close the compressWriter if the data is read already. |  | ||||||
| 					// Closing the pipe, releases the writer passed to the getObject. |  | ||||||
| 					compressWriter.CloseWithError(perr) |  | ||||||
| 				}() |  | ||||||
| 				writer = compressWriter |  | ||||||
| 			} else { |  | ||||||
| 				writer = zipWriter |  | ||||||
| 			} |  | ||||||
| 			httpWriter := ioutil.WriteOnClose(writer) | 			httpWriter := ioutil.WriteOnClose(writer) | ||||||
| 
 | 
 | ||||||
| 			// Write object content to response body | 			// Write object content to response body | ||||||
| 			if _, err = io.Copy(httpWriter, gr); err != nil { | 			if _, err = io.Copy(httpWriter, gr); err != nil { | ||||||
| 				httpWriter.Close() | 				httpWriter.Close() | ||||||
| 				if info.IsCompressed() { |  | ||||||
| 					// Wait for decompression go-routine to retire. |  | ||||||
| 					wg.Wait() |  | ||||||
| 				} |  | ||||||
| 				if !httpWriter.HasWritten() { // write error response only if no data or headers has been written to client yet | 				if !httpWriter.HasWritten() { // write error response only if no data or headers has been written to client yet | ||||||
| 					writeWebErrorResponse(w, err) | 					writeWebErrorResponse(w, err) | ||||||
| 				} | 				} | ||||||
| @ -1382,10 +1350,6 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { | |||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if info.IsCompressed() { |  | ||||||
| 				// Wait for decompression go-routine to retire. |  | ||||||
| 				wg.Wait() |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			// Notify object accessed via a GET request. | 			// Notify object accessed via a GET request. | ||||||
| 			sendEvent(eventArgs{ | 			sendEvent(eventArgs{ | ||||||
|  | |||||||
| @ -1,6 +1,10 @@ | |||||||
| # Compression Guide [](https://slack.min.io) | # Compression Guide [](https://slack.min.io) | ||||||
| 
 | 
 | ||||||
| MinIO server allows streaming compression to ensure efficient disk space usage. Compression happens inflight, i.e objects are compressed before being written to disk(s). MinIO uses [`golang/snappy`](https://github.com/golang/snappy) streaming compression due to its stability and performance. | MinIO server allows streaming compression to ensure efficient disk space usage. Compression happens inflight, i.e objects are compressed before being written to disk(s). MinIO uses [`klauspost/compress/s2`](https://github.com/klauspost/compress/tree/master/s2) streaming compression due to its stability and performance. | ||||||
|  | 
 | ||||||
|  | This algorithm is specifically optimized for machine generated content. Write throughput is typically at least 300MB/s per CPU core. Decompression speed is typically at least 1GB/s. | ||||||
|  | This means that in cases where raw IO is below these numbers compression will not only reduce disk usage but also help increase system throughput. | ||||||
|  | Typically enabling compression on spinning disk systems will increase speed when the content can be compressed. | ||||||
| 
 | 
 | ||||||
| ## Get Started | ## Get Started | ||||||
| 
 | 
 | ||||||
| @ -15,13 +19,26 @@ Compression can be enabled by updating the `compress` config settings for MinIO | |||||||
| ```json | ```json | ||||||
| "compress": { | "compress": { | ||||||
|         "enabled": true, |         "enabled": true, | ||||||
|         "extensions": [".txt",".log",".csv", ".json"], |         "extensions": [".txt",".log",".csv", ".json", ".tar"], | ||||||
|         "mime-types": ["text/csv","text/plain","application/json"] |         "mime-types": ["text/*","application/json","application/xml"] | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Since text, log, csv, json files are highly compressible, These extensions/mime-types are included by default for compression. | Since text, log, csv, json files are highly compressible, These extensions/mime-types are included by default for compression. | ||||||
| 
 | 
 | ||||||
|  | Having compression enabled and no extensions or mime types will attempt to compress anything that isn't explicitly known to be already compressed content.  | ||||||
|  | Settings for enabling compression on all content, except for types listed below: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | "compress": { | ||||||
|  |         "enabled": true, | ||||||
|  |         "extensions": [], | ||||||
|  |         "mime-types": [] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Incompressible content will be skipped with quite low CPU usage and storage overhead, typically at several GB/s. | ||||||
|  | 
 | ||||||
| To update the configuration, use `mc admin config get` command to get the current configuration file for the minio cluster in json format, and save it locally. | To update the configuration, use `mc admin config get` command to get the current configuration file for the minio cluster in json format, and save it locally. | ||||||
| 
 | 
 | ||||||
| ```sh | ```sh | ||||||
| @ -69,6 +86,8 @@ export MINIO_COMPRESS_MIMETYPES="application/pdf" | |||||||
|       | `application/x-compress` | |       | `application/x-compress` | | ||||||
|       | `application/x-xz` | |       | `application/x-xz` | | ||||||
| 
 | 
 | ||||||
|  | All files with these extensions and mime types are excluded from compression, even if compression is enabled for all types. | ||||||
|  | 
 | ||||||
| - MinIO does not support encryption with compression because compression and encryption together potentially enables room for side channel attacks like [`CRIME and BREACH`](https://blog.minio.io/c-e-compression-encryption-cb6b7f04a369) | - MinIO does not support encryption with compression because compression and encryption together potentially enables room for side channel attacks like [`CRIME and BREACH`](https://blog.minio.io/c-e-compression-encryption-cb6b7f04a369) | ||||||
| 
 | 
 | ||||||
| - MinIO does not support compression for Gateway (Azure/GCS/NAS) implementations. | - MinIO does not support compression for Gateway (Azure/GCS/NAS) implementations. | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @ -21,7 +21,6 @@ require ( | |||||||
| 	github.com/fatih/color v1.7.0 | 	github.com/fatih/color v1.7.0 | ||||||
| 	github.com/fatih/structs v1.1.0 | 	github.com/fatih/structs v1.1.0 | ||||||
| 	github.com/go-sql-driver/mysql v1.4.1 | 	github.com/go-sql-driver/mysql v1.4.1 | ||||||
| 	github.com/golang/snappy v0.0.1 |  | ||||||
| 	github.com/gomodule/redigo v2.0.0+incompatible | 	github.com/gomodule/redigo v2.0.0+incompatible | ||||||
| 	github.com/gorilla/handlers v1.4.0 | 	github.com/gorilla/handlers v1.4.0 | ||||||
| 	github.com/gorilla/mux v1.7.0 | 	github.com/gorilla/mux v1.7.0 | ||||||
| @ -29,7 +28,7 @@ require ( | |||||||
| 	github.com/hashicorp/vault v1.1.0 | 	github.com/hashicorp/vault v1.1.0 | ||||||
| 	github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf | 	github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf | ||||||
| 	github.com/json-iterator/go v1.1.7 | 	github.com/json-iterator/go v1.1.7 | ||||||
| 	github.com/klauspost/compress v1.5.0 | 	github.com/klauspost/compress v1.8.3 | ||||||
| 	github.com/klauspost/pgzip v1.2.1 | 	github.com/klauspost/pgzip v1.2.1 | ||||||
| 	github.com/klauspost/readahead v1.3.0 | 	github.com/klauspost/readahead v1.3.0 | ||||||
| 	github.com/klauspost/reedsolomon v1.9.1 | 	github.com/klauspost/reedsolomon v1.9.1 | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @ -340,6 +340,8 @@ github.com/klauspost/compress v1.3.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 | |||||||
| github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | ||||||
| github.com/klauspost/compress v1.5.0 h1:iDac0ZKbmSA4PRrRuXXjZL8C7UoJan8oBYxXkMzEQrI= | github.com/klauspost/compress v1.5.0 h1:iDac0ZKbmSA4PRrRuXXjZL8C7UoJan8oBYxXkMzEQrI= | ||||||
| github.com/klauspost/compress v1.5.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | github.com/klauspost/compress v1.5.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | ||||||
|  | github.com/klauspost/compress v1.8.3 h1:CkLseiEYMM/fRb0RIg9mXB+Iwgmle+U9KGFu+JCO4Ec= | ||||||
|  | github.com/klauspost/compress v1.8.3/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | ||||||
| github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | ||||||
| github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | ||||||
| github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= | github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user