@@ -7,37 +7,36 @@ Most WebSocket servers exchange JSON messages because they're convenient to
7
7
parse and serialize in a browser. These messages contain text data and tend to
8
8
be repetitive.
9
9
10
- This makes the stream of messages highly compressible. Enabling compression
10
+ This makes the stream of messages highly compressible. Compressing messages
11
11
can reduce network traffic by more than 80%.
12
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.
13
+ websockets implements WebSocket Per-Message Deflate, a compression extension
14
+ based on the Deflate _ algorithm specified in :rfc: ` 7692 ` .
15
15
16
16
.. _Deflate : https://en.wikipedia.org/wiki/Deflate
17
17
18
- Configuring compression
19
- -----------------------
18
+ :func: `~websockets.asyncio.client.connect ` and
19
+ :func: `~websockets.asyncio.server.serve ` enable compression by default because
20
+ the reduction in network bandwidth is usually worth the additional memory and
21
+ CPU cost.
20
22
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
23
25
- If you want to disable compression, set ``compression=None ``::
24
+ Configuring compression
25
+ -----------------------
26
26
27
- import websockets
27
+ To disable compression, set `` compression=None ``::
28
28
29
- websockets. connect(..., compression=None)
29
+ connect(..., compression=None, ... )
30
30
31
- websockets. serve(..., compression=None)
31
+ serve(..., compression=None, ... )
32
32
33
- If you want to customize compression settings, you can enable the Per-Message
34
- Deflate extension explicitly with :class: `ClientPerMessageDeflateFactory ` or
33
+ To customize compression settings, enable the Per-Message Deflate extension
34
+ explicitly with :class: `ClientPerMessageDeflateFactory ` or
35
35
:class: `ServerPerMessageDeflateFactory `::
36
36
37
- import websockets
38
37
from websockets.extensions import permessage_deflate
39
38
40
- websockets. connect(
39
+ connect(
41
40
...,
42
41
extensions=[
43
42
permessage_deflate.ClientPerMessageDeflateFactory(
@@ -46,9 +45,10 @@ Deflate extension explicitly with :class:`ClientPerMessageDeflateFactory` or
46
45
compress_settings={"memLevel": 4},
47
46
),
48
47
],
48
+ ...,
49
49
)
50
50
51
- websockets. serve(
51
+ serve(
52
52
...,
53
53
extensions=[
54
54
permessage_deflate.ServerPerMessageDeflateFactory(
@@ -57,13 +57,14 @@ Deflate extension explicitly with :class:`ClientPerMessageDeflateFactory` or
57
57
compress_settings={"memLevel": 4},
58
58
),
59
59
],
60
+ ...,
60
61
)
61
62
62
63
The Window Bits and Memory Level values in these examples reduce memory usage
63
64
at the expense of compression rate.
64
65
65
- Compression settings
66
- --------------------
66
+ Compression parameters
67
+ ----------------------
67
68
68
69
When a client and a server enable the Per-Message Deflate extension, they
69
70
negotiate two parameters to guarantee compatibility between compression and
@@ -81,9 +82,9 @@ and memory usage for both sides.
81
82
This requires retaining the compression context and state between messages,
82
83
which increases the memory footprint of a connection.
83
84
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.
85
+ * **Window Bits ** controls the size of the compression context. It must be an
86
+ integer between 9 (lowest memory usage) and 15 (best compression). Setting it
87
+ to 8 is possible but rejected by some versions of zlib and not very useful .
87
88
88
89
On the server side, websockets defaults to 12. Specifically, the compression
89
90
window size (server to client) is always 12 while the decompression window
@@ -94,9 +95,8 @@ and memory usage for both sides.
94
95
has the same effect as defaulting to 15.
95
96
96
97
: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 `.
98
+ the trade-off between compression rate, memory usage, and CPU usage for
99
+ compressing. They're transparent for decompressing.
100
100
101
101
* **Memory Level ** controls the size of the compression state. It must be an
102
102
integer between 1 (lowest memory usage) and 9 (best compression).
@@ -108,96 +108,92 @@ otherwise, websockets inherits defaults of :func:`~zlib.compressobj`.
108
108
* **Compression Level ** controls the effort to optimize compression. It must
109
109
be an integer between 1 (lowest CPU usage) and 9 (best compression).
110
110
111
+ websockets relies on the default value chosen by :func: `~zlib.compressobj `,
112
+ ``Z_DEFAULT_COMPRESSION ``.
113
+
111
114
* **Strategy ** selects the compression strategy. The best choice depends on
112
115
the type of data being compressed.
113
116
117
+ websockets relies on the default value chosen by :func: `~zlib.compressobj `,
118
+ ``Z_DEFAULT_STRATEGY ``.
114
119
115
- Tuning compression
116
- ------------------
120
+ To customize these parameters, add keyword arguments for
121
+ :func: ` ~zlib.compressobj ` in `` compress_settings ``.
117
122
118
- For servers
119
- ...........
123
+ Default settings for servers
124
+ ----------------------------
120
125
121
126
By default, websockets enables compression with conservative settings that
122
127
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
128
+ Window Bits = 12 and Memory Level = 5. This strikes a good balance for small
124
129
messages that are typical of WebSocket servers.
125
130
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.
131
+ Here's an example of how compression settings affect memory usage per
132
+ connection, compressed size, and compression time for a corpus of JSON
133
+ documents.
129
134
130
135
=========== ============ ============ ================ ================
131
136
Window Bits Memory Level Memory usage Size vs. default Time vs. default
132
137
=========== ============ ============ ================ ================
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 % —
138
+ 15 8 316 KiB -10 % +10 %
139
+ 14 7 172 KiB -7 % +5 %
140
+ 13 6 100 KiB -3 % +2 %
141
+ **12 ** **5 ** **64 KiB ** **= ** **= **
142
+ 11 4 46 KiB +10 % +4 %
143
+ 10 3 37 KiB +70 % +40 %
144
+ 9 2 33 KiB +130 % +90 %
145
+ — — 14 KiB +350 % —
141
146
=========== ============ ============ ================ ================
142
147
143
148
Window Bits and Memory Level don't have to move in lockstep. However, other
144
149
combinations don't yield significantly better results than those shown above.
145
150
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
151
+ websockets defaults to Window Bits = 12 and Memory Level = 5 to stay away from
152
+ Window Bits = 10 or Memory Level = 3 where performance craters, raising doubts
153
+ on what could happen at Window Bits = 11 and Memory Level = 4 on a different
157
154
corpus.
158
155
159
156
Defaults must be safe for all applications, hence a more conservative choice.
160
157
161
- .. _compression/benchmark.py : https://github.com/python-websockets/websockets/blob/main/experiments/compression/benchmark.py
158
+ Optimizing settings
159
+ -------------------
162
160
163
- The benchmark focuses on compression because it's more expensive than
164
- decompression. Indeed, leaving aside small allocations, theoretical memory
165
- usage is:
161
+ Compressed size and compression time depend on the structure of messages
162
+ exchanged by your application. As a consequence, default settings may not be
163
+ optimal for your use case.
166
164
167
- * ``(1 << (windowBits + 2)) + (1 << (memLevel + 9)) `` for compression;
168
- * ``1 << windowBits `` for decompression.
165
+ To compare how various compression settings perform for your use case:
169
166
170
- CPU usage is also higher for compression than decompression.
167
+ 1. Create a corpus of typical messages in a directory, one message per file.
168
+ 2. Run the `compression/benchmark.py `_ script, passing the directory in
169
+ argument.
171
170
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 .
171
+ The script measures compressed size and compression time for all combinations of
172
+ Window Bits and Memory Level. It outputs two tables with absolute values and two
173
+ tables with values relative to websockets' default settings .
175
174
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.
175
+ Pick your favorite settings in these tables and configure them as shown above.
179
176
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 ``.
177
+ .. _compression/benchmark.py : https://github.com/python-websockets/websockets/blob/main/experiments/compression/benchmark.py
183
178
184
- For clients
185
- ...........
179
+ Default settings for clients
180
+ ----------------------------
186
181
187
- By default, websockets enables compression with Memory Level = 5 but leaves
182
+ By default, websockets enables compression with Memory Level = 5 but leaves
188
183
the Window Bits setting up to the server.
189
184
190
- There's two good reasons and one bad reason for not optimizing the client side
191
- like the server side:
185
+ There's two good reasons and one bad reason for not optimizing Window Bits on
186
+ the client side as on the server side:
192
187
193
188
1. If the maintainers of a server configured some optimized settings, we don't
194
189
want to override them with more restrictive settings.
195
190
196
191
2. Optimizing memory usage doesn't matter very much for clients because it's
197
192
uncommon to open thousands of client connections in a program.
198
193
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.
194
+ 3. On a more pragmatic and annoying note, some servers misbehave badly when a
195
+ client configures compression settings. `AWS API Gateway `_ is the worst
196
+ offender.
201
197
202
198
.. _AWS API Gateway : https://github.com/python-websockets/websockets/issues/1065
203
199
@@ -207,6 +203,29 @@ like the server side:
207
203
Until the ecosystem levels up, interoperability with buggy servers seems
208
204
more valuable than optimizing memory usage.
209
205
206
+ Decompression
207
+ -------------
208
+
209
+ The discussion above focuses on compression because it's more expensive than
210
+ decompression. Indeed, leaving aside small allocations, theoretical memory
211
+ usage is:
212
+
213
+ * ``(1 << (windowBits + 2)) + (1 << (memLevel + 9)) `` for compression;
214
+ * ``1 << windowBits `` for decompression.
215
+
216
+ CPU usage is also higher for compression than decompression.
217
+
218
+ While it's always possible for a server to use a smaller window size for
219
+ compressing outgoing messages, using a smaller window size for decompressing
220
+ incoming messages requires collaboration from clients.
221
+
222
+ When a client doesn't support configuring the size of its compression window,
223
+ websockets enables compression with the largest possible decompression window.
224
+ In most use cases, this is more efficient than disabling compression both ways.
225
+
226
+ If you are very sensitive to memory usage, you can reverse this behavior by
227
+ setting the ``require_client_max_window_bits `` parameter of
228
+ :class: `ServerPerMessageDeflateFactory ` to ``True ``.
210
229
211
230
Further reading
212
231
---------------
@@ -216,7 +235,7 @@ settings affect memory usage and how to optimize them.
216
235
217
236
.. _blog post by Ilya Grigorik : https://www.igvita.com/2013/11/27/configuring-and-optimizing-websocket-compression/
218
237
219
- This `experiment by Peter Thorson `_ recommends Window Bits = 11 and Memory
220
- Level = 4 for optimizing memory usage.
238
+ This `experiment by Peter Thorson `_ recommends Window Bits = 11 and Memory
239
+ Level = 4 for optimizing memory usage.
221
240
222
241
.. _experiment by Peter Thorson : https://mailarchive.ietf.org/arch/msg/hybi/F9t4uPufVEy8KBLuL36cZjCmM_Y/
0 commit comments