I've been doing some testing of https connection pooling from cloudant python requests as part of gunicorn request handling:
# -*- coding: utf-8 -
from requests.adapters import HTTPAdapter
import cloudant
import logging
import json
# log when new connections are started by urllib3
logging.basicConfig()
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
def app(environ, start_response):
httpAdapter = HTTPAdapter(pool_connections=10, pool_maxsize=100)
account = cloudant.Account('education', async=False)
account._session.adapters['https://'] = httpAdapter
db = account.database('foundbite')
db_response = db.get( '_all_docs' )
data = str.encode(json.dumps(db_response.json()))
status = str(db_response.status_code)
response_headers = [
('Content-type', 'application/json'),
('Content-Length', str(len(data))),
]
start_response(status, response_headers)
return iter([data])
If I make 5 requests, you can see that 5 new connections are started:
INFO: requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): education.cloudant.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
INFO: requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): education.cloudant.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
INFO: requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): education.cloudant.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
INFO: requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): education.cloudant.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
INFO: requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): education.cloudant.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
One option is to move the cloudant Account object instantiation outside of the request handler so that it can be shared between requests:
# -*- coding: utf-8 -
from requests.adapters import HTTPAdapter
import cloudant
import logging
import json
# log when new connections are started by urllib3
logging.basicConfig()
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
httpAdapter = HTTPAdapter(pool_connections=10, pool_maxsize=100)
account = cloudant.Account('education', async=False)
account._session.adapters['https://'] = httpAdapter
def app(environ, start_response):
db = account.database('foundbite')
db_response = db.get( '_all_docs' )
data = str.encode(json.dumps(db_response.json()))
status = str(db_response.status_code)
response_headers = [
('Content-type', 'application/json'),
('Content-Length', str(len(data))),
]
start_response(status, response_headers)
return iter([data])
This time, only one https connection is created and that is used for all 5 requests:
INFO: requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): education.cloudant.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
DEBUG:requests.packages.urllib3.connectionpool:"GET /foundbite/_all_docs HTTP/1.1" 200 None
Question: This second approach will reduce the number of expensive https connections, however is this approach safe, i.e. is it threadsafe?