From 2012bee97cd53ef48dec85ecb785cc536eaab84b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 Nov 2023 11:27:36 -0600 Subject: [PATCH] Fix increase in latency with small messages from websocket compression changes (#7797) ## What do these changes do? Changes the threshold that is required to compress in the executor for websocket messages to 5KiB https://github.com/aio-libs/aiohttp/pull/7223 changed the websocket implementation to compress messages > 1KiB in the executor. The threshold was a bit low which caused an increase in latency compressing messages as the overhead to use the executor can exceed the cost to compress tiny messages. When testing 3.9.0 with Home Assistant, we saw a 3 order of magnitude increase in executor usage which resulted in an overall increase in cpu time since all the tiny messages were being compressed in the executor. I could not find the motivation for choosing 1KiB in the original PR ## Are there changes in behavior for the user? ## Related issue number ## Checklist - [x] I think the code is well written - [ ] Unit tests for the changes exist - [ ] Documentation reflects the changes - [ ] If you provide code modification, please add yourself to `CONTRIBUTORS.txt` * The format is <Name> <Surname>. * Please keep alphabetical order, the file is sorted by names. - [ ] Add a new news fragment into the `CHANGES` folder * name it `.` for example (588.bugfix) * if you don't have an `issue_id` change it to the pr id after creating the pr * ensure type is one of the following: * `.feature`: Signifying a new feature. * `.bugfix`: Signifying a bug fix. * `.doc`: Signifying a documentation improvement. * `.removal`: Signifying a deprecation or removal of public API. * `.misc`: A ticket has been closed, but it is not of interest to users. * Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files." (cherry picked from commit 27c308b177a8421e2ce093505e71d74a2082f374) --- CHANGES/7797.bugfix | 1 + aiohttp/http_websocket.py | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 CHANGES/7797.bugfix diff --git a/CHANGES/7797.bugfix b/CHANGES/7797.bugfix new file mode 100644 index 00000000000..a573c861897 --- /dev/null +++ b/CHANGES/7797.bugfix @@ -0,0 +1 @@ +Fix increase in latency with small messages from websocket compression changes diff --git a/aiohttp/http_websocket.py b/aiohttp/http_websocket.py index 71726497cde..8e33857ae30 100644 --- a/aiohttp/http_websocket.py +++ b/aiohttp/http_websocket.py @@ -59,6 +59,15 @@ class WSCloseCode(IntEnum): ALLOWED_CLOSE_CODES: Final[Set[int]] = {int(i) for i in WSCloseCode} +# For websockets, keeping latency low is extremely important as implementations +# generally expect to be able to send and receive messages quickly. We use a +# larger chunk size than the default to reduce the number of executor calls +# since the executor is a significant source of latency and overhead when +# the chunks are small. A size of 5KiB was chosen because it is also the +# same value python-zlib-ng choose to use as the threshold to release the GIL. + +WEBSOCKET_MAX_SYNC_CHUNK_SIZE = 5 * 1024 + class WSMsgType(IntEnum): # websocket spec types @@ -626,11 +635,17 @@ async def _send_frame( if (compress or self.compress) and opcode < 8: if compress: # Do not set self._compress if compressing is for this frame - compressobj = ZLibCompressor(level=zlib.Z_BEST_SPEED, wbits=-compress) + compressobj = ZLibCompressor( + level=zlib.Z_BEST_SPEED, + wbits=-compress, + max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE, + ) else: # self.compress if not self._compressobj: self._compressobj = ZLibCompressor( - level=zlib.Z_BEST_SPEED, wbits=-self.compress + level=zlib.Z_BEST_SPEED, + wbits=-self.compress, + max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE, ) compressobj = self._compressobj