-
Notifications
You must be signed in to change notification settings - Fork 1.1k
PYTHON-2363 Rate limit new connection creations via maxConnecting #511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@@ -303,7 +316,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE, | |||
wait_queue_multiple=None, ssl_context=None, | |||
ssl_match_hostname=True, socket_keepalive=True, | |||
event_listeners=None, appname=None, driver=None, | |||
compression_settings=None): | |||
compression_settings=None, max_connecting=MAX_CONNECTING): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't configurable by users but I think it still made sense to add max_connecting to PoolOptions.
pymongo/pool.py
Outdated
while (self._pending >= self._max_connecting and | ||
not self.sockets): | ||
if self.opts.wait_queue_timeout: | ||
# TODO: What if timeout is <= zero here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still an open question. I'll have to give it some more thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved after talking offline with Prashant. A <=0 timeout is fine, it means the operation really did timeout in the wait queue.
thread.join(10) | ||
|
||
self.assertEqual(len(docs), 50) | ||
self.assertLessEqual(len(pool.sockets), 50) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test would be more useful if it asserted that we created way less than 50 connections, like this:
self.assertLessEqual(len(pool.sockets), 20)
But it would make the test more flaky, especially with noauth/nossl where connection creation is much faster. Hmm perhaps I can try changing this to:
if ssl and auth enabled:
self.assertLessEqual(len(pool.sockets), 20)
else:
self.assertLessEqual(len(pool.sockets), 50)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Waiting on EVG test results to make sure this change doesn't make the test flakey.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice test!
@@ -58,17 +58,32 @@ to 100. If there are ``maxPoolSize`` connections to a server and all are in | |||
use, the next request to that server will wait until one of the connections | |||
becomes available. | |||
|
|||
The client instance opens one additional socket per server in your MongoDB | |||
The client instance opens two additional sockets per server in your MongoDB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is because of the 4.4+ streaming SDAM behavior which requires 2 sockets per server.
15e9c11
to
ead96be
Compare
Rebased to fix the geoSearch test failures (PYTHON-2421). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM assuming EVG passes :)
- one of the first two threads finishes creating a connection, or | ||
- an existing connection is checked back into the pool. | ||
|
||
Rate limiting concurrent connection creation reduces the likelihood of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also say that users who are concerned about any additional latency this might result in can increase their minPoolSize
to reduce the likelihood of having threads wait to create new connections?
Also, should we add a note about the impact this has at application start time when a minPoolSize
is set? Presumably pools take longer to initialize in this case though I am not sure what impace this might have on applications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not suggest increasing minPoolSize. We actually want threads to wait a bit before creating connections because it yields better throughput and latency in most cases (as evidenced by test_maxConnecting
).
Also, should we add a note about the impact this has at application start time when a minPoolSize is set? Presumably pools take longer to initialize in this case though I am not sure what impace this might have on applications.
Pools shouldn't take longer because minPoolSize connection creation is (and always was) single threaded. MongoClient only creates a single connection for a single pool at once.
if PY3: | ||
def _cond_wait(condition, deadline): | ||
timeout = deadline - _time() if deadline else None | ||
return condition.wait(timeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets add a comment here that wait()
treats negative timeouts same as timeout=0
?
thread.join(10) | ||
|
||
self.assertEqual(len(docs), 50) | ||
self.assertLessEqual(len(pool.sockets), 50) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice test!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it looks like something about how threads work on Python 2 is causing one of the spec tests to fail.
Investigating this Python 2 test failure:
|
At most 2 connections can be in the pending state per connection pool. The pending state covers all the work required to setup a new connection including TCP, TLS, and MongoDB authentication. For example, if two threads are currently creating connections, a third thread will wait for either an existing connection to be checked back into the pool or for one of the two threads to finish creating a connection. The change reduces the likelihood of connection storms and improves the driver's ability to reuse existing connections.
… the operation really did timeout in the wait queue
b95baf2
to
d151589
Compare
Rebased without any changes. |
Make test_maxConnecting less flakey (again).
I still see a few test failures:
I made a slight change to the maxConnecting timeout behavior but I'm not convinced it will solve the failures. I more so suspect that Python 2 (especially Jython) is failing frequently because threading is worse there and Condition variables are more expensive (On Python 2 they're implemented as spin locks). This creates extra contention in the pool and causes the timeouts seen in the tests. Since we're dropping Python 2 support soon and this feature isn't required in PyMongo 3.x I think we should just merge the changes as is. @prashantmital any final thoughts? |
The latest change seems to resolve most of the python 2 issues. The same test failed only once and only on Jython: https://evergreen.mongodb.com/task/mongo_python_driver_tests_python_version_rhel62_test_ssl__platform~rhel62_auth~noauth_ssl~nossl_python_version~jython2.7_coverage~coverage_test_latest_sharded_cluster_patch_86d58113e52c8331ef83fd5849019c5422d7e62b_5fbc3836d1fe072958b98951_20_11_23_22_31_19 |
At most 2 connections can be in the pending state per connection pool.
The pending state covers all the work required to setup a new connection
including TCP, TLS, and MongoDB authentication. For example, if two
threads are currently creating connections, a third thread will wait for
either an existing connection to be checked back into the pool or for
one of the two threads to finish creating a connection.
The change reduces the likelihood of connection storms and improves the
driver's ability to reuse existing connections.