Skip to content

Commit 8facf00

Browse files
committed
PYTHON-1723 Support zstd wire compression
1 parent 3e11497 commit 8facf00

File tree

9 files changed

+97
-18
lines changed

9 files changed

+97
-18
lines changed

.evergreen/config.yml

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,10 @@ axes:
10261026
display_name: zlib compression
10271027
variables:
10281028
COMPRESSORS: "zlib"
1029+
- id: zstd
1030+
display_name: zstd compression
1031+
variables:
1032+
COMPRESSORS: "zstd"
10291033
- id: python-version
10301034
display_name: "Python"
10311035
values:
@@ -1283,15 +1287,22 @@ buildvariants:
12831287
compression: "*"
12841288
- python-version: ["jython2.7"]
12851289
c-extensions: "*"
1286-
compression: "snappy"
1290+
compression: ["snappy", "zstd"]
12871291
batchtime: 10080 # 7 days
12881292
display_name: "${compression} ${c-extensions} ${python-version} (x86_64)"
12891293
# Ubuntu 16.04 images have libsnappy-dev installed
12901294
run_on: ubuntu1604-test
12911295
tasks:
1292-
- "test-latest-standalone"
1293-
- "test-4.0-standalone"
1294-
- "test-3.6-standalone"
1296+
- "test-latest-standalone"
1297+
rules:
1298+
- if:
1299+
python-version: "*"
1300+
c-extensions: "*"
1301+
compression: ["snappy", "zlib"]
1302+
then:
1303+
add_tasks:
1304+
- "test-4.0-standalone"
1305+
- "test-3.6-standalone"
12951306

12961307
- matrix_name: "tests-python-version-py37-plus-compression"
12971308
matrix_spec: {"python-version-requires-openssl-102-plus": "*", "c-extensions": "*", "compression": "*"}
@@ -1301,9 +1312,16 @@ buildvariants:
13011312
# Ubuntu 16.04 images have libsnappy-dev installed, and provides OpenSSL 1.0.2
13021313
run_on: ubuntu1604-test
13031314
tasks:
1304-
- "test-latest-standalone"
1305-
- "test-4.0-standalone"
1306-
- "test-3.6-standalone"
1315+
- "test-latest-standalone"
1316+
rules:
1317+
- if:
1318+
python-version: "*"
1319+
c-extensions: "*"
1320+
compression: ["snappy", "zlib"]
1321+
then:
1322+
add_tasks:
1323+
- "test-4.0-standalone"
1324+
- "test-3.6-standalone"
13071325

13081326
- matrix_name: "tests-python-version-green-framework-rhel62"
13091327
matrix_spec: {"python-version": "*", "green-framework": "*", auth-ssl: "*"}

.evergreen/run-tests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ elif [ $COMPRESSORS = "snappy" ]; then
5656
# 0.5.2 has issues in pypy3(.5)
5757
pip install python-snappy==0.5.1
5858
PYTHON=python
59+
elif [ $COMPRESSORS = "zstd" ]; then
60+
$PYTHON_BINARY -m virtualenv --system-site-packages --never-download zstdtest
61+
. zstdtest/bin/activate
62+
trap "deactivate; rm -rf zstdtest" EXIT HUP
63+
pip install zstandard
64+
PYTHON=python
5965
else
6066
PYTHON="$PYTHON_BINARY"
6167
fi

README.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,15 @@ Wire protocol compression with snappy requires `python-snappy
118118

119119
$ python -m pip install pymongo[snappy]
120120

121+
Wire protocol compression with zstandard requires `zstandard
122+
<https://pypi.org/project/zstandard>`_::
123+
124+
$ python -m pip install pymongo[zstd]
125+
121126
You can install all dependencies automatically with the following
122127
command::
123128

124-
$ python -m pip install pymongo[snappy,gssapi,srv,tls]
129+
$ python -m pip install pymongo[snappy,gssapi,srv,tls,zstd]
125130

126131
Other optional packages:
127132

doc/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Version 3.9 adds support for MongoDB 4.2. Highlights include:
3636
- Support for retryable reads and the ``retryReads`` URI option which is
3737
enabled by default. See the :class:`~pymongo.mongo_client.MongoClient`
3838
documentation for details.
39+
- Support zstandard for wire protocol compression.
3940

4041
Now that supported operations are retried automatically and transparently,
4142
users should consider adjusting any custom retry logic to prevent

doc/installation.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,15 @@ Wire protocol compression with snappy requires `python-snappy
7575

7676
$ python -m pip install pymongo[snappy]
7777

78+
Wire protocol compression with zstandard requires `zstandard
79+
<https://pypi.org/project/zstandard>`_::
80+
81+
$ python -m pip install pymongo[zstd]
82+
7883
You can install all dependencies automatically with the following
7984
command::
8085

81-
$ python -m pip install pymongo[snappy,gssapi,srv,tls]
86+
$ python -m pip install pymongo[snappy,gssapi,srv,tls,zstd]
8287

8388
Other optional packages:
8489

pymongo/compression_support.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,15 @@
2828
# Python built without zlib support.
2929
_HAVE_ZLIB = False
3030

31+
try:
32+
from zstandard import ZstdCompressor, ZstdDecompressor
33+
_HAVE_ZSTD = True
34+
except ImportError:
35+
_HAVE_ZSTD = False
36+
3137
from pymongo.monitoring import _SENSITIVE_COMMANDS
3238

33-
_SUPPORTED_COMPRESSORS = set(["snappy", "zlib"])
39+
_SUPPORTED_COMPRESSORS = set(["snappy", "zlib", "zstd"])
3440
_NO_COMPRESSION = set(['ismaster'])
3541
_NO_COMPRESSION.update(_SENSITIVE_COMMANDS)
3642

@@ -57,6 +63,11 @@ def validate_compressors(dummy, value):
5763
warnings.warn(
5864
"Wire protocol compression with zlib is not available. "
5965
"The zlib module is not available.")
66+
elif compressor == "zstd" and not _HAVE_ZSTD:
67+
compressors.remove(compressor)
68+
warnings.warn(
69+
"Wire protocol compression with zstandard is not available. "
70+
"You must install the zstandard module for zstandard support.")
6071
return compressors
6172

6273

@@ -83,6 +94,8 @@ def get_compression_context(self, compressors):
8394
return SnappyContext()
8495
elif chosen == "zlib":
8596
return ZlibContext(self.zlib_compression_level)
97+
elif chosen == "zstd":
98+
return ZstdContext()
8699

87100

88101
def _zlib_no_compress(data):
@@ -113,6 +126,16 @@ def __init__(self, level):
113126
self.compress = lambda data: zlib.compress(data, level)
114127

115128

129+
class ZstdContext(object):
130+
compressor_id = 3
131+
132+
@staticmethod
133+
def compress(data):
134+
# ZstdCompressor is not thread safe.
135+
# TODO: Use a pool?
136+
return ZstdCompressor().compress(data)
137+
138+
116139
def decompress(data, compressor_id):
117140
if compressor_id == SnappyContext.compressor_id:
118141
# python-snappy doesn't support the buffer interface.
@@ -126,5 +149,9 @@ def decompress(data, compressor_id):
126149
return snappy.uncompress(bytes(data))
127150
elif compressor_id == ZlibContext.compressor_id:
128151
return zlib.decompress(data)
152+
elif compressor_id == ZstdContext.compressor_id:
153+
# ZstdDecompressor is not thread safe.
154+
# TODO: Use a pool?
155+
return ZstdDecompressor().decompress(data)
129156
else:
130157
raise ValueError("Unknown compressorId %d" % (compressor_id,))

pymongo/mongo_client.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,15 @@ def __init__(
312312
https://docs.mongodb.com/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments",
313313
- `compressors`: Comma separated list of compressors for wire
314314
protocol compression. The list is used to negotiate a compressor
315-
with the server. Currently supported options are "snappy" and
316-
"zlib". Support for snappy requires the
315+
with the server. Currently supported options are "snappy", "zlib"
316+
and "zstd". Support for snappy requires the
317317
`python-snappy <https://pypi.org/project/python-snappy/>`_ package.
318-
zlib support requires the Python standard library zlib module.
319-
By default no compression is used. Compression support must also be
320-
enabled on the server. MongoDB 3.4+ supports snappy compression.
321-
MongoDB 3.6+ supports snappy and zlib.
318+
zlib support requires the Python standard library zlib module. zstd
319+
requires the `zstandard <https://pypi.org/project/zstandard/>`_
320+
package. By default no compression is used. Compression support
321+
must also be enabled on the server. MongoDB 3.4+ supports snappy
322+
compression. MongoDB 3.6 adds support for zlib. MongoDB 4.2 adds
323+
support for zstd.
322324
- `zlibCompressionLevel`: (int) The zlib compression level to use
323325
when zlib is used as the wire protocol compressor. Supported values
324326
are -1 through 9. -1 tells the zlib library to use its default

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ def build_extension(self, ext):
317317
sources=['pymongo/_cmessagemodule.c',
318318
'bson/buffer.c'])]
319319

320-
extras_require = {'snappy': ["python-snappy"]}
320+
extras_require = {'snappy': ["python-snappy"], 'zstd': ["zstandard"]}
321321
vi = sys.version_info
322322
if vi[0] == 2:
323323
extras_require.update(

test/test_client.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from pymongo import auth, message
3737
from pymongo.common import _UUID_REPRESENTATIONS
3838
from pymongo.command_cursor import CommandCursor
39-
from pymongo.compression_support import _HAVE_SNAPPY
39+
from pymongo.compression_support import _HAVE_SNAPPY, _HAVE_ZSTD
4040
from pymongo.cursor import Cursor, CursorType
4141
from pymongo.database import Database
4242
from pymongo.errors import (AutoReconnect,
@@ -1343,6 +1343,21 @@ def compression_settings(client):
13431343
opts = compression_settings(client)
13441344
self.assertEqual(opts.compressors, ['snappy', 'zlib'])
13451345

1346+
if not _HAVE_ZSTD:
1347+
uri = "mongodb://localhost:27017/?compressors=zstd"
1348+
client = MongoClient(uri, connect=False)
1349+
opts = compression_settings(client)
1350+
self.assertEqual(opts.compressors, [])
1351+
else:
1352+
uri = "mongodb://localhost:27017/?compressors=zstd"
1353+
client = MongoClient(uri, connect=False)
1354+
opts = compression_settings(client)
1355+
self.assertEqual(opts.compressors, ['zstd'])
1356+
uri = "mongodb://localhost:27017/?compressors=zstd,zlib"
1357+
client = MongoClient(uri, connect=False)
1358+
opts = compression_settings(client)
1359+
self.assertEqual(opts.compressors, ['zstd', 'zlib'])
1360+
13461361
options = client_context.default_client_options
13471362
if "compressors" in options and "zlib" in options["compressors"]:
13481363
for level in range(-1, 10):

0 commit comments

Comments
 (0)