Projects STRLCPY graphql-engine Commits d966e0a9
🤬
  • server: don't compress small responses

    PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7216
    GitOrigin-RevId: 0344da3c692c9dbb39329663be78734c2052d309
  • Loading...
  • Brandon Simmons committed with hasura-bot 2 months ago
    d966e0a9
    1 parent bb46cba5
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■
    server/src-lib/Hasura/Server/Compression.hs
    skipped 49 lines
    50 50   (BL.ByteString, EncodingType)
    51 51  compressResponse reqHeaders unCompressedResp
    52 52   -- we have option to gzip:
    53  - | acceptedEncodings == Set.fromList [identityEncoding, Just CTGZip]
    54  - ||
    55  - -- we must gzip:
    56  - acceptedEncodings == Set.fromList [Just CTGZip] =
     53 + | acceptedEncodings == Set.fromList [identityEncoding, Just CTGZip] =
     54 + if shouldSkipCompression unCompressedResp
     55 + then notCompressed
     56 + else (compressFast CTGZip unCompressedResp, Just CTGZip)
     57 + -- we MUST gzip:
     58 + | acceptedEncodings == Set.fromList [Just CTGZip] =
    57 59   (compressFast CTGZip unCompressedResp, Just CTGZip)
    58  - -- we must only return an uncompressed response:
     60 + -- we must ONLY return an uncompressed response:
    59 61   | acceptedEncodings == Set.fromList [identityEncoding] =
    60  - (unCompressedResp, identityEncoding)
     62 + notCompressed
    61 63   -- this is technically a client error, but ignore for now (maintaining
    62 64   -- current behavior); assume identity:
    63 65   | otherwise =
    64  - (unCompressedResp, identityEncoding)
     66 + notCompressed
    65 67   where
    66 68   acceptedEncodings = getAcceptedEncodings reqHeaders
     69 + notCompressed = (unCompressedResp, identityEncoding)
    67 70   
    68  --- | Compress using
     71 +-- | Compress the bytestring preferring speed over compression ratio
    69 72  compressFast :: CompressionType -> BL.ByteString -> BL.ByteString
    70 73  compressFast = \case
    71 74   CTGZip -> GZ.compressWith gzipCompressionParams
    skipped 1 lines
    73 76   gzipCompressionParams =
    74 77   -- See Note [Compression ratios]
    75 78   GZ.defaultCompressParams {GZ.compressLevel = GZ.compressionLevel 1}
     79 + 
     80 +-- | Assuming we have the option to compress or not (i.e. client accepts
     81 +-- identity AND gzip), should we skip compression?
     82 +shouldSkipCompression :: BL.ByteString -> Bool
     83 +shouldSkipCompression bs = BL.length bs < 700
     84 + 
     85 +{- NOTE [Compression Heuristics]
     86 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     87 +Compression is a significant source of CPU usage (and latency for small
     88 +requests); let's be smarter about compression when we have the option.
     89 + 
     90 +Some data from cloud (omitting healthz and version), with zlib gzip at
     91 +compression level 1:
     92 + 
     93 + ~96% of requests can accept gzip responses
     94 + 
     95 + P50(uncompressed_response_size) : 150 bytes
     96 + P75(uncompressed_response_size) : 1200 bytes
     97 + P95(uncompressed_response_size) : 39000 bytes
     98 + P99(uncompressed_response_size) : 95000 bytes
     99 + 
     100 + Responses smaller than 700 bytes (the common case)...
     101 + ...account for 4% of total response egress (currently)
     102 + ...account for 68% of responses
     103 + 
     104 + ...have a P50 compression ratio of: 1.0 (i.e. no benefit)
     105 + ... a P75 compression ratio of: 1.3
     106 + ... a P99 compression ratio of: 2.0
     107 + 
     108 + ...and FYI if we take a cutoff of...
     109 + ...2000 we get P50 ratio 0.9
     110 + ...5000 we get P50 ratio 0.8
     111 +-}
    76 112   
    77 113  -- | Which encodings can the client accept? The empty set returned here is an
    78 114  -- error condition and the server tecnically ought to return a 406.
    skipped 87 lines
  • ■ ■ ■ ■ ■ ■
    server/src-test/Hasura/Server/CompressionSpec.hs
    1 1  module Hasura.Server.CompressionSpec (spec) where
    2 2   
     3 +import Codec.Compression.GZip qualified as GZ
     4 +import Data.ByteString.Lazy qualified as BL
    3 5  import Data.Set qualified as Set
    4 6  import Hasura.Prelude
    5 7  import Hasura.Server.Compression
    skipped 42 lines
    48 50   getAcceptedEncodings [("accept-encoding", "")]
    49 51   `shouldBe` Set.fromList [identityEncoding]
    50 52   
     53 + describe "compressResponse" do
     54 + let smallVal = "xxxx"
     55 + largeVal = BL.pack $ replicate 1000 121
     56 + 
     57 + it "compresses when required" do
     58 + let (valOut0, encodingChosen0) = compressResponse [("accept-encoding", "gzip, identity;q=0")] smallVal
     59 + GZ.decompress valOut0 `shouldBe` smallVal
     60 + encodingChosen0 `shouldBe` Just CTGZip
     61 + 
     62 + let (valOut1, encodingChosen1) = compressResponse [("accept-encoding", "gzip, identity;q=0")] largeVal
     63 + GZ.decompress valOut1 `shouldBe` largeVal
     64 + encodingChosen1 `shouldBe` Just CTGZip
     65 + 
     66 + it "omits compression for small values" do
     67 + let (valOut0, encodingChosen0) = compressResponse [("accept-encoding", "gzip")] smallVal
     68 + valOut0 `shouldBe` smallVal
     69 + encodingChosen0 `shouldBe` Nothing
     70 + 
     71 + let (valOut1, encodingChosen1) = compressResponse [("accept-encoding", "gzip")] largeVal
     72 + GZ.decompress valOut1 `shouldBe` largeVal
     73 + encodingChosen1 `shouldBe` Just CTGZip
     74 + 
  • ■ ■ ■ ■ ■ ■
    server/tests-py/queries/compression/graphql_query.yaml
    skipped 15 lines
    16 16   name: Brotli
    17 17   - id: 3
    18 18   name: Nothing
     19 + - id: 4
     20 + name: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    19 21   
  • ■ ■ ■ ■
    server/tests-py/queries/compression/setup.yaml
    1 1  type: bulk
    2 2  args:
    3 3   
     4 +# Arbitrary. Pad with the 'xxxx...' value to keep it above the size threshold
     5 +# for compression
    4 6  - type: run_sql
    5 7   args:
    6 8   sql: |
    7 9   CREATE TABLE test (id serial primary key, name text);
    8  - INSERT INTO test (name) values ('Gzip'), ('Brotli'), ('Nothing');
     10 + INSERT INTO test (name) values ('Gzip'), ('Brotli'), ('Nothing'),
     11 + ('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
    9 12   
    10 13  - type: track_table
    11 14   args:
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    server/tests-py/queries/compression/v1_query.yaml
    skipped 12 lines
    13 13   name: Brotli
    14 14   - id: 3
    15 15   name: Nothing
     16 + - id: 4
     17 + name: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
     18 + 
    16 19   
  • ■ ■ ■ ■ ■
    server/tests-py/test_compression.py
    skipped 7 lines
    8 8   
    9 9  usefixtures = pytest.mark.usefixtures
    10 10   
     11 +# A very basic test that compression seems to be working
    11 12  @usefixtures('per_class_tests_db_state')
    12 13  class TestCompression:
    13 14   
    skipped 51 lines
Please wait...
Page is in error, reload to recover