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

fix: remove slow brotli compression and use optimized gzip middleware #968

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 27 additions & 26 deletions router-tests/response_compression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package integration

import (
"bytes"
"compress/flate"
"compress/gzip"
"encoding/json"
"github.com/buger/jsonparser"
"io"
"net/http"
"strings"
"testing"

"github.com/andybalholm/brotli"
"github.com/stretchr/testify/require"
"github.com/wundergraph/cosmo/router-tests/testenv"
)
Expand All @@ -25,21 +25,6 @@ func decompressGzip(t *testing.T, body io.Reader) []byte {
return data
}

func decompressDeflate(t *testing.T, body io.Reader) []byte {
dr := flate.NewReader(body)
defer dr.Close()
data, err := io.ReadAll(dr)
require.NoError(t, err)
return data
}

func decompressBrotli(t *testing.T, body io.Reader) []byte {
br := brotli.NewReader(body)
data, err := io.ReadAll(br)
require.NoError(t, err)
return data
}

func decompressNone(t *testing.T, body io.Reader) []byte {
data, err := io.ReadAll(body)
require.NoError(t, err)
Expand All @@ -49,24 +34,41 @@ func decompressNone(t *testing.T, body io.Reader) []byte {
func TestResponseCompression(t *testing.T) {
t.Parallel()

employeesIdDataMinSizeGzip := `{"data":{"employees":[{"id":1}` + strings.Repeat(`,{"id":1}`, 200) + `]}}`

testCases := []struct {
name string
encoding string
decompressFunc func(t *testing.T, body io.Reader) []byte
expectEncoding bool
responseData string
}{
{"gzip", "gzip", decompressGzip, true},
{"deflate", "deflate", decompressDeflate, true},
{"brotli", "br", decompressBrotli, true},
{"identity", "identity", decompressNone, false}, // NO Encoding
{"zstd", "zstd", decompressNone, false}, // Unsuported Encoding
{"gzip with min size", "gzip", decompressGzip, true, employeesIdDataMinSizeGzip}, // Gzip Encoding with min size
{"no gzip because request is too small", "", decompressGzip, false, employeesIdData}, // No Gzip Encoding because of min size
{"identity", "identity", decompressNone, false, employeesIdData}, // NO Encoding
{"zstd", "zstd", decompressNone, false, employeesIdData}, // Unsuported Encoding
}

for _, tc := range testCases {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel() // mark the subtest as parallel
testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) {
testenv.Run(t, &testenv.Config{
Subgraphs: testenv.SubgraphsConfig{
Employees: testenv.SubgraphConfig{
Middleware: func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
data, err := io.ReadAll(r.Body)
require.NoError(t, err)
_, dt, _, _ := jsonparser.Get(data, "extensions", "persistedQuery")
require.Equal(t, jsonparser.NotExist, dt)
_, _ = w.Write([]byte(tc.responseData))
})
},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
headers := http.Header{
"Content-Type": []string{"application/json"},
}
Expand All @@ -87,13 +89,12 @@ func TestResponseCompression(t *testing.T) {

if tc.expectEncoding {
require.Equal(t, tc.encoding, res.Header.Get("Content-Encoding"))
decompressedBody := tc.decompressFunc(t, res.Body)
require.JSONEq(t, tc.responseData, string(decompressedBody))
} else {
require.Empty(t, res.Header.Get("Content-Encoding"))
}
require.Contains(t, res.Header.Get("Content-Type"), "application/json")

decompressedBody := tc.decompressFunc(t, res.Body)
require.JSONEq(t, employeesIdData, string(decompressedBody))
})
})
}
Expand Down
23 changes: 14 additions & 9 deletions router/core/graph_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"crypto/ecdsa"
"errors"
"fmt"
"io"
"github.com/klauspost/compress/gzhttp"
"github.com/klauspost/compress/gzip"
"net/http"
"net/url"
"strings"
"time"

br "github.com/andybalholm/brotli"
"github.com/cloudflare/backoff"
"github.com/dgraph-io/ristretto"
"github.com/go-chi/chi/v5"
Expand Down Expand Up @@ -200,19 +200,24 @@ func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterC
return nil, fmt.Errorf("failed to build feature flag handler: %w", err)
}

brCompressor := middleware.NewCompressor(5, CustomCompressibleContentTypes...)
brCompressor.SetEncoder("br", func(w io.Writer, level int) io.Writer {
return br.NewWriterLevel(w, level)
})
wrapper, err := gzhttp.NewWrapper(
gzhttp.MinSize(1024), // 1KB
gzhttp.CompressionLevel(gzip.DefaultCompression),
gzhttp.ContentTypes(CompressibleContentTypes),
)
if err != nil {
return nil, fmt.Errorf("failed to create gzip wrapper: %w", err)
}

/**
* A group where we can selectively apply middlewares to the graphql endpoint
*/
httpRouter.Group(func(cr chi.Router) {

// We are applying it conditionally because brotli compressing the 3MB playground is very slow
cr.Use(middleware.Compress(5, CustomCompressibleContentTypes...))
cr.Use(brCompressor.Handler)
// We are applying it conditionally because compressing 3MB playground is still slow even with stdlib gzip
cr.Use(func(h http.Handler) http.Handler {
return wrapper(h)
})

// Mount the feature flag handler. It calls the base mux if no feature flag is set.
cr.Mount(r.graphqlPath, multiGraphHandler)
Expand Down
4 changes: 3 additions & 1 deletion router/core/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const (
Redact IPAnonymizationMethod = "redact"
)

var CustomCompressibleContentTypes = []string{
var CompressibleContentTypes = []string{
"text/html",
"text/css",
"text/plain",
Expand All @@ -63,6 +63,8 @@ var CustomCompressibleContentTypes = []string{
"application/rss+xml",
"image/svg+xml",
"application/graphql",
"application/graphql-response+json",
"application/graphql+json",
}

type (
Expand Down
11 changes: 5 additions & 6 deletions router/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ require (
connectrpc.com/connect v1.16.2
github.com/MicahParks/keyfunc/v2 v2.1.0
github.com/alitto/pond v1.8.3
github.com/andybalholm/brotli v1.1.0
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/buger/jsonparser v1.1.1
github.com/cespare/xxhash/v2 v2.2.0
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a
github.com/dgraph-io/ristretto v0.1.1
// References to main that includes the fix for the race with ristretto.Close()
// Link: https://github.com/dgraph-io/ristretto/pull/384
github.com/dgraph-io/ristretto v0.1.2-0.20240723054643-f5997484152c
github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.0.11
github.com/go-redis/redis_rate/v10 v10.0.1
Expand Down Expand Up @@ -65,6 +67,7 @@ require (
)

require (
github.com/klauspost/compress v1.17.9
github.com/valyala/fastjson v1.6.4
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
)
Expand All @@ -89,7 +92,6 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/jensneuse/byte-template v0.0.0-20200214152254-4f3cf06e5c68 // indirect
github.com/kingledion/go-tools v0.6.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down Expand Up @@ -126,7 +128,4 @@ require (
nhooyr.io/websocket v1.8.11 // indirect
)

// To fix race with ristretto.Close() https://github.com/dgraph-io/ristretto/pull/384
replace github.com/dgraph-io/ristretto v0.1.1 => github.com/wundergraph/ristretto v0.0.0-20240715072905-d30a6481d4bf

//replace github.com/wundergraph/graphql-go-tools/v2 => ../../graphql-go-tools/v2
6 changes: 4 additions & 2 deletions router/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/ristretto v0.1.2-0.20240723054643-f5997484152c h1:V2+MhiAoTwUNENo9PFoz2NSr0VDJEzIARLuzb30YyqA=
github.com/dgraph-io/ristretto v0.1.2-0.20240723054643-f5997484152c/go.mod h1:swkazRqnUf1N62d0Nutz7KIj2UKqsm/H8tD0nBJAXqM=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
Expand Down Expand Up @@ -112,8 +114,8 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa
github.com/kingledion/go-tools v0.6.0 h1:y8C/4mWoHgLkO45dB+Y/j0o4Y4WUB5lDTAcMPMtFpTg=
github.com/kingledion/go-tools v0.6.0/go.mod h1:qcDJQxBui/H/hterGb90GMlLs9Yi7QrwaJL8OGdbsms=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
Loading