benchmarks: Add Accept-Encoding: gzip headers (NO REGRESSION)

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5622
GitOrigin-RevId: 431f7f382573737af9b85966a58c5b262f8cb377
This commit is contained in:
Brandon Simmons 2022-08-25 02:42:07 -04:00 committed by hasura-bot
parent 8fdd0ac8f5
commit 746cae5f4b
5 changed files with 73 additions and 7 deletions

View File

@ -83,6 +83,11 @@ be ignored.
https://well-typed.com/blog/2021/01/fragmentation-deeper-look/ https://well-typed.com/blog/2021/01/fragmentation-deeper-look/
https://well-typed.com/blog/2021/03/memory-return/ https://well-typed.com/blog/2021/03/memory-return/
- If optimizing or tuning the output/compression codepath:
- `chinook`.`*_small_result` and `simple_query_*` queries are average wrt
response body size (according to cloud data)
- ...and `chinook`.`full_introspection` is ~P95
## Adding a new benchmark and reviewing ## Adding a new benchmark and reviewing
You'll create a new directory under `benchmark_sets/`, and in general can You'll create a new directory under `benchmark_sets/`, and in general can

View File

@ -5,6 +5,11 @@ headers:
X-Hasura-Admin-Secret: my-secret X-Hasura-Admin-Secret: my-secret
X-Hasura-Role: employee X-Hasura-Role: employee
X-Hasura-User-Id: 4 X-Hasura-User-Id: 4
# On cloud about 95% of requests incoming Accept gzip (which as of writing
# means we unconditionally compress, which has significant performance
# impact), so it's important that we include it in all benchmarks unless we
# have an intentional reason not to.
Accept-Encoding: gzip
# Anchors to help us DRY below; settings here may be overridden selectively # Anchors to help us DRY below; settings here may be overridden selectively
constants: constants:
@ -36,6 +41,12 @@ queries:
############################################################################ ############################################################################
# single-table query, small result size; makes use of permissions for # single-table query, small result size; makes use of permissions for
# filtering; low RPS # filtering; low RPS
#
# NOTE: According to data from cloud this is a pretty average response (we see
# P50 compressed response body size of 123 bytes, and P75 290 bytes):
#
# uncompressed body size: ~600 bytes
# compressed body size: ~200 bytes (as of time of writing)
- name: simple_query_low_load - name: simple_query_low_load
<<: *k6_custom <<: *k6_custom
options: options:
@ -79,6 +90,10 @@ queries:
######## Small result size ######## Small result size
#
# uncompressed body size: ~650 bytes
# compressed body size: ~200 bytes
- name: complex_query_low_load_small_result - name: complex_query_low_load_small_result
<<: *k6_custom <<: *k6_custom
options: options:
@ -130,6 +145,13 @@ queries:
######## Large result size ######## Large result size
#
# NOTE: According to data from cloud, this is somewhere between
# P90 (2 kB compressed) and P95 (4 kB compressed):
#
# uncompressed body size: ~33 kB
# compressed body size: ~3 kB (as of time of writing)
- name: complex_query_low_load_large_result - name: complex_query_low_load_large_result
<<: *k6_custom <<: *k6_custom
options: options:
@ -164,6 +186,12 @@ queries:
# The standard introspection query from server/src-rsr/introspection.json # The standard introspection query from server/src-rsr/introspection.json
# We don't expect a server to be hammered with these, but they are crucial # We don't expect a server to be hammered with these, but they are crucial
# for clients (like graphiql): # for clients (like graphiql):
#
# NOTE: According to data from cloud, this is somewhere between
# P95 (4 kB compressed) and P99 (40 kB compressed):
#
# uncompressed body size: ~190 kB
# compressed body size: ~13 kB (as of time of writing)
- name: full_introspection - name: full_introspection
<<: *k6_custom <<: *k6_custom

View File

@ -1,8 +1,8 @@
# This tells graphql-bench that it's testing a hasura instance and should # This tells graphql-bench that it's testing a hasura instance and should
# collect some additional metrics: # collect some additional metrics:
extended_hasura_checks: true extended_hasura_checks: true
# headers: headers:
# X-Hasura-Admin-Secret: my-secret Accept-Encoding: gzip
# Anchors to help us DRY below; settings here may be overridden selectively # Anchors to help us DRY below; settings here may be overridden selectively
constants: constants:

View File

@ -1,8 +1,8 @@
# This tells graphql-bench that it's testing a hasura instance and should # This tells graphql-bench that it's testing a hasura instance and should
# collect some additional metrics: # collect some additional metrics:
extended_hasura_checks: true extended_hasura_checks: true
# headers: headers:
# X-Hasura-Admin-Secret: my-secret Accept-Encoding: gzip
# Anchors to help us DRY below; settings here may be overridden selectively # Anchors to help us DRY below; settings here may be overridden selectively
constants: constants:

View File

@ -19,21 +19,26 @@ data CompressionType
compressionTypeToTxt :: CompressionType -> Text compressionTypeToTxt :: CompressionType -> Text
compressionTypeToTxt CTGZip = "gzip" compressionTypeToTxt CTGZip = "gzip"
-- | Maybe compress the response body
compressResponse :: compressResponse ::
NH.RequestHeaders -> NH.RequestHeaders ->
BL.ByteString -> BL.ByteString ->
(BL.ByteString, Maybe NH.Header, Maybe CompressionType) (BL.ByteString, Maybe NH.Header, Maybe CompressionType)
compressResponse reqHeaders unCompressedResp = compressResponse reqHeaders unCompressedResp =
let compressionTypeM = getRequestedCompression reqHeaders let compressionTypeM = getAcceptedCompression reqHeaders
appendCompressionType (res, headerM) = (res, headerM, compressionTypeM) appendCompressionType (res, headerM) = (res, headerM, compressionTypeM)
gzipCompressionParams = gzipCompressionParams =
-- See Note [Compression ratios]
GZ.defaultCompressParams {GZ.compressLevel = GZ.compressionLevel 1} GZ.defaultCompressParams {GZ.compressLevel = GZ.compressionLevel 1}
in appendCompressionType $ case compressionTypeM of in appendCompressionType $ case compressionTypeM of
Just CTGZip -> (GZ.compressWith gzipCompressionParams unCompressedResp, Just gzipHeader) Just CTGZip -> (GZ.compressWith gzipCompressionParams unCompressedResp, Just gzipHeader)
Nothing -> (unCompressedResp, Nothing) Nothing -> (unCompressedResp, Nothing)
getRequestedCompression :: NH.RequestHeaders -> Maybe CompressionType -- | Which, if any, compressed encodings can the client accept?
getRequestedCompression reqHeaders --
-- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
getAcceptedCompression :: NH.RequestHeaders -> Maybe CompressionType
getAcceptedCompression reqHeaders
| "gzip" `elem` acceptEncodingVals = Just CTGZip | "gzip" `elem` acceptEncodingVals = Just CTGZip
| otherwise = Nothing | otherwise = Nothing
where where
@ -41,3 +46,31 @@ getRequestedCompression reqHeaders
concatMap (splitHeaderVal . snd) $ concatMap (splitHeaderVal . snd) $
filter (\h -> fst h == NH.hAcceptEncoding) reqHeaders filter (\h -> fst h == NH.hAcceptEncoding) reqHeaders
splitHeaderVal bs = map T.strip $ T.splitOn "," $ bsToTxt bs splitHeaderVal bs = map T.strip $ T.splitOn "," $ bsToTxt bs
{-
Note [Compression ratios]
~~~~~~~~~~~~~~~~~~~~~~~~~
I did some measurements of compression ratios at `gzip -1` (libc) of some
randomly generated json, real json datasets, and output from our benchmarked
chinook queries:
2552/6131 = 0.41
4666/8718 = 0.53
13921/27131 = 0.51
5895/8879 = 0.66 <----- completely random strings
8634/28261 = 0.30
70422/372466 = 0.18
200/600 = 0.33 <----| from chinook graphql benchmarks
3000/33000 = 0.09 <----|
13000/190000 = 0.07 <----'
Given these numbers I would suggest using a rule-of-thumb expected compression
ratio between 2:1 and 10:1, depending on what being conservative means in the
context.
I didn't test higher compression levels much, but `gzip -4` for the most part
resulted in less than 10% smaller output on random json, and ~30% on our highly
compressible benchmark output.
-}