tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

compression.rst (9057B)


      1 Compression
      2 ===========
      3 
      4 .. currentmodule:: websockets.extensions.permessage_deflate
      5 
      6 Most WebSocket servers exchange JSON messages because they're convenient to
      7 parse and serialize in a browser. These messages contain text data and tend to
      8 be repetitive.
      9 
     10 This makes the stream of messages highly compressible. Enabling compression
     11 can reduce network traffic by more than 80%.
     12 
     13 There's a standard for compressing messages. :rfc:`7692` defines WebSocket
     14 Per-Message Deflate, a compression extension based on the Deflate_ algorithm.
     15 
     16 .. _Deflate: https://en.wikipedia.org/wiki/Deflate
     17 
     18 Configuring compression
     19 -----------------------
     20 
     21 :func:`~websockets.client.connect` and :func:`~websockets.server.serve` enable
     22 compression by default because the reduction in network bandwidth is usually
     23 worth the additional memory and CPU cost.
     24 
     25 If you want to disable compression, set ``compression=None``::
     26 
     27    import websockets
     28 
     29    websockets.connect(..., compression=None)
     30 
     31    websockets.serve(..., compression=None)
     32 
     33 If you want to customize compression settings, you can enable the Per-Message
     34 Deflate extension explicitly with :class:`ClientPerMessageDeflateFactory` or
     35 :class:`ServerPerMessageDeflateFactory`::
     36 
     37    import websockets
     38    from websockets.extensions import permessage_deflate
     39 
     40    websockets.connect(
     41        ...,
     42        extensions=[
     43            permessage_deflate.ClientPerMessageDeflateFactory(
     44                server_max_window_bits=11,
     45                client_max_window_bits=11,
     46                compress_settings={"memLevel": 4},
     47            ),
     48        ],
     49    )
     50 
     51    websockets.serve(
     52        ...,
     53        extensions=[
     54            permessage_deflate.ServerPerMessageDeflateFactory(
     55                server_max_window_bits=11,
     56                client_max_window_bits=11,
     57                compress_settings={"memLevel": 4},
     58            ),
     59        ],
     60    )
     61 
     62 The Window Bits and Memory Level values in these examples reduce memory usage
     63 at the expense of compression rate.
     64 
     65 Compression settings
     66 --------------------
     67 
     68 When a client and a server enable the Per-Message Deflate extension, they
     69 negotiate two parameters to guarantee compatibility between compression and
     70 decompression. These parameters affect the trade-off between compression rate
     71 and memory usage for both sides.
     72 
     73 * **Context Takeover** means that the compression context is retained between
     74  messages. In other words, compression is applied to the stream of messages
     75  rather than to each message individually.
     76 
     77  Context takeover should remain enabled to get good performance on
     78  applications that send a stream of messages with similar structure,
     79  that is, most applications.
     80 
     81  This requires retaining the compression context and state between messages,
     82  which increases the memory footprint of a connection.
     83 
     84 * **Window Bits** controls the size of the compression context. It must be
     85  an integer between 9 (lowest memory usage) and 15 (best compression).
     86  Setting it to 8 is possible but rejected by some versions of zlib.
     87 
     88  On the server side, websockets defaults to 12. Specifically, the compression
     89  window size (server to client) is always 12 while the decompression window
     90  (client to server) size may be 12 or 15 depending on whether the client
     91  supports configuring it.
     92 
     93  On the client side, websockets lets the server pick a suitable value, which
     94  has the same effect as defaulting to 15.
     95 
     96 :mod:`zlib` offers additional parameters for tuning compression. They control
     97 the trade-off between compression rate, memory usage, and CPU usage only for
     98 compressing. They're transparent for decompressing. Unless mentioned
     99 otherwise, websockets inherits defaults of :func:`~zlib.compressobj`.
    100 
    101 * **Memory Level** controls the size of the compression state. It must be an
    102  integer between 1 (lowest memory usage) and 9 (best compression).
    103 
    104  websockets defaults to 5. This is lower than zlib's default of 8. Not only
    105  does a lower memory level reduce memory usage, but it can also increase
    106  speed thanks to memory locality.
    107 
    108 * **Compression Level** controls the effort to optimize compression. It must
    109  be an integer between 1 (lowest CPU usage) and 9 (best compression).
    110 
    111 * **Strategy** selects the compression strategy. The best choice depends on
    112  the type of data being compressed.
    113 
    114 
    115 Tuning compression
    116 ------------------
    117 
    118 For servers
    119 ...........
    120 
    121 By default, websockets enables compression with conservative settings that
    122 optimize memory usage at the cost of a slightly worse compression rate:
    123 Window Bits = 12 and Memory Level = 5. This strikes a good balance for small
    124 messages that are typical of WebSocket servers.
    125 
    126 Here's how various compression settings affect memory usage of a single
    127 connection on a 64-bit system, as well a benchmark of compressed size and
    128 compression time for a corpus of small JSON documents.
    129 
    130 =========== ============ ============ ================ ================
    131 Window Bits Memory Level Memory usage Size vs. default Time vs. default
    132 =========== ============ ============ ================ ================
    133 15          8            322 KiB      -4.0%            +15%
    134 14          7            178 KiB      -2.6%            +10%
    135 13          6            106 KiB      -1.4%            +5%
    136 **12**      **5**        **70 KiB**   **=**            **=**
    137 11          4            52 KiB       +3.7%            -5%
    138 10          3            43 KiB       +90%             +50%
    139 9           2            39 KiB       +160%            +100%
    140 —           —            19 KiB       +452%            —
    141 =========== ============ ============ ================ ================
    142 
    143 Window Bits and Memory Level don't have to move in lockstep. However, other
    144 combinations don't yield significantly better results than those shown above.
    145 
    146 Compressed size and compression time depend heavily on the kind of messages
    147 exchanged by the application so this example may not apply to your use case.
    148 
    149 You can adapt `compression/benchmark.py`_ by creating a list of typical
    150 messages and passing it to the ``_run`` function.
    151 
    152 Window Bits = 11 and Memory Level = 4 looks like the sweet spot in this table.
    153 
    154 websockets defaults to Window Bits = 12 and Memory Level = 5 to stay away from
    155 Window Bits = 10 or Memory Level = 3 where performance craters, raising doubts
    156 on what could happen at Window Bits = 11 and Memory Level = 4 on a different
    157 corpus.
    158 
    159 Defaults must be safe for all applications, hence a more conservative choice.
    160 
    161 .. _compression/benchmark.py: https://github.com/python-websockets/websockets/blob/main/experiments/compression/benchmark.py
    162 
    163 The benchmark focuses on compression because it's more expensive than
    164 decompression. Indeed, leaving aside small allocations, theoretical memory
    165 usage is:
    166 
    167 * ``(1 << (windowBits + 2)) + (1 << (memLevel + 9))`` for compression;
    168 * ``1 << windowBits`` for decompression.
    169 
    170 CPU usage is also higher for compression than decompression.
    171 
    172 While it's always possible for a server to use a smaller window size for
    173 compressing outgoing messages, using a smaller window size for decompressing
    174 incoming messages requires collaboration from clients.
    175 
    176 When a client doesn't support configuring the size of its compression window,
    177 websockets enables compression with the largest possible decompression window.
    178 In most use cases, this is more efficient than disabling compression both ways.
    179 
    180 If you are very sensitive to memory usage, you can reverse this behavior by
    181 setting the ``require_client_max_window_bits`` parameter of
    182 :class:`ServerPerMessageDeflateFactory` to ``True``.
    183 
    184 For clients
    185 ...........
    186 
    187 By default, websockets enables compression with Memory Level = 5 but leaves
    188 the Window Bits setting up to the server.
    189 
    190 There's two good reasons and one bad reason for not optimizing the client side
    191 like the server side:
    192 
    193 1. If the maintainers of a server configured some optimized settings, we don't
    194   want to override them with more restrictive settings.
    195 
    196 2. Optimizing memory usage doesn't matter very much for clients because it's
    197   uncommon to open thousands of client connections in a program.
    198 
    199 3. On a more pragmatic note, some servers misbehave badly when a client
    200   configures compression settings. `AWS API Gateway`_ is the worst offender.
    201 
    202   .. _AWS API Gateway: https://github.com/python-websockets/websockets/issues/1065
    203 
    204   Unfortunately, even though websockets is right and AWS is wrong, many users
    205   jump to the conclusion that websockets doesn't work.
    206 
    207   Until the ecosystem levels up, interoperability with buggy servers seems
    208   more valuable than optimizing memory usage.
    209 
    210 
    211 Further reading
    212 ---------------
    213 
    214 This `blog post by Ilya Grigorik`_ provides more details about how compression
    215 settings affect memory usage and how to optimize them.
    216 
    217 .. _blog post by Ilya Grigorik: https://www.igvita.com/2013/11/27/configuring-and-optimizing-websocket-compression/
    218 
    219 This `experiment by Peter Thorson`_ recommends Window Bits = 11 and Memory
    220 Level = 4 for optimizing memory usage.
    221 
    222 .. _experiment by Peter Thorson: https://mailarchive.ietf.org/arch/msg/hybi/F9t4uPufVEy8KBLuL36cZjCmM_Y/