Skip to content

Commit

Permalink
Merge branch 'master' into Issue594
Browse files Browse the repository at this point in the history
  • Loading branch information
dataflake committed May 9, 2019
2 parents 53ab99e + 654dcd8 commit a4c39a2
Showing 1 changed file with 130 additions and 59 deletions.
189 changes: 130 additions & 59 deletions docs/wsgi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,84 @@ starting point for changes. The `Python Logging Cookbook
of topics for advanced configurations.


Compatible WSGI servers
-----------------------
This section describes how to integrate specific WSGI servers into your Zope
instance. These servers were chosen because they either have a `PasteDeploy`
entry point or have one provided by shim software, which means they work with
the default Zope scripts for starting/stopping the service.


waitress (the default)
~~~~~~~~~~~~~~~~~~~~~~
Choosing WSGI server software
-----------------------------
The WSGI integration gives you a choice of WSGI server software to run your
Zope application. This section lists several options that were selected
because they either have a `PasteDeploy` entry point or have one provided by
shim software, which means they work with the default Zope scripts for
starting/stopping the service.


Things to watch out for
~~~~~~~~~~~~~~~~~~~~~~~
The ZODB uses connection pooling where a working thread grabs a connection
from the pool to serve content and then releases it when the work is done.
The default size of this connection pool is 7. The advice from ``ZServer``
days to choose a number of application threads that stays safely below that
number of ZODB connections is still valid. ``ZServer`` used 4 threads by
default, so if the WSGI server lets you configure the number of threads 4 is
still a safe choice.

Another recommendation from Zope 2 is still valid as well: If you have a choice
between less Zope instances with a higher number of threads each, or more
instances with less threads each, choose the latter. Create more separate Zope
instances and set the WSGI server threads value to e.g. 2.

.. warning::

If the WSGI server software lets you configure a number of worker processes,
like ``gunicorn`` does, do not configure more than a single worker.
Otherwise you will see issues due to concurrent ZODB access by more than
one process, which may corrupt your ZODB.


Test criteria for recommendations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A simple contrived load test was done with the following parameters:

- 100 concurrent clients accessing Zope
- 100 seconds run time
- the clients just fetch "/"
- standard Zope 4 instances, one with ZEO and one without
- Python 2.7.16 on macOS Mojave/10.14.4
- standard WSGI server configurations, the only changes are to number of
threads and/or number of workers where available.

This load test uncovered several issues:

- ``cheroot`` (tested version: 6.5.5) was magnitudes slower than all others.
Unlike the others, it did not max out CPU. It is unclear where the slowdown
originates. Others reached 500-750 requests/second. ``cheroot`` only served
12 requests/second per configured thread.
- ``gunicorn`` (tested version: 19.9.0) showed very strange behavior against
the non-ZEO Zope instance. It serves around 500 requests/second, but then
hangs and serves no requests for several seconds, before picking up again.
- ``gunicorn`` (tested version: 19.9.0) does not like the ZEO instance at all.
No matter what configuration in terms of threads or workers was chosen
``gunicorn`` just hung so badly that even CTRL-C would not kill it.
Switching to an asynchronous type of worker (tested with ``gevent``)
did not make a difference.
- ``werkzeug`` (tested version: 0.15.2) does not let you specify the number
of threads, you only tell it to use threads or not. In threaded mode it
spawns too many threads and immedialy runs up agains the ZODB connection
pool limits, so with Zope only the unthreaded mode is suitable. Even in
unthreaded mode, the service speed was inconsistent. Just like ``gunicorn``
it had intermittent hangs before recovering.
- ``bjoern`` (tested version: 3.0.0) is the clear speed winner with 740
requests/second against both the ZEO and non-ZEO Zope instance, even though
it is single-threaded.
- ``waitress`` (tested version: 1.3.0) is the all-around best choice. It's
just 10-15% slower than ``bjoern``, but both the built-in WSGI tools as well
as ``plone.recipe.zope2instance`` use it as the default and make it very
convenient to use.


Recommended WSGI servers
~~~~~~~~~~~~~~~~~~~~~~~~

waitress (the default and recommended choice)
+++++++++++++++++++++++++++++++++++++++++++++
If you create a Zope instance using the ``mkwsgiinstance`` script described
above or the ``plone.recipe.zope2instance`` buildout recipe, you will
automatically get a ``waitress``-based server. The default configurations set
Expand All @@ -103,8 +171,58 @@ list is `part of the waitress documentation
<https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html>`_.


bjoern (the fastest)
++++++++++++++++++++
The `bjoern WSGI server <https://github.com/jonashaag/bjoern>`_ can be
integrated using a shim package called `dataflake.wsgi.bjoern
<https://dataflakewsgibjoern.readthedocs.io/>`_. See the `Using this package`
section for details on how to integrate `bjoern` using Zope's own
``runwsgi`` script and how to create a suitable WSGI configuration.

If you use ``plone.recipe.zope2instance``, the following
section will pull in the correct dependencies:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
dataflake.wsgi.bjoern
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/bjoern.ini
Problematic WSGI servers
~~~~~~~~~~~~~~~~~~~~~~~~

werkzeug
++++++++
`werkzeug <https://palletsprojects.com/p/werkzeug/>`_ is a WSGI library that
contains not just a WSGI server, but also a powerful debugger. It can
easily integrate wth Zope using a shim package called `dataflake.wsgi.werkzeug
<https://dataflakewsgiwerkzeug.readthedocs.io/>`_. See the `Using this package`
section for how to integrate `werkzeug` using Zope's own ``runwsgi`` script and
how to create a suitable WSGI configuration.

If you use ``plone.recipe.zope2instance``, the following section will pull in
the correct dependencies, after you have created a WSGI configuration file:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
dataflake.wsgi.werkzeug
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/werkzeug.ini
gunicorn
~~~~~~~~
++++++++
The `gunicorn WSGI server <https://gunicorn.org/>`_ has a built-in
`PasteDeploy` entry point and integrates easily. The following example buildout
configuration section will create a ``bin/runwsgi`` script that uses
Expand Down Expand Up @@ -165,7 +283,7 @@ path to the path of the configuration file you created yourself:
cheroot
~~~~~~~
+++++++
The `cheroot WSGI server <https://cheroot.cherrypy.org>`_ can be integrated
using a shim package called `dataflake.wsgi.cheroot
<https://dataflakewsgicheroot.readthedocs.io/>`_. See the `Using this package`
Expand All @@ -187,53 +305,6 @@ section will pull in the correct dependencies:
wsgi = ${buildout:directory}/etc/cheroot.ini
bjoern
~~~~~~
The `bjoern WSGI server <https://github.com/jonashaag/bjoern>`_ can be
integrated using a shim package called `dataflake.wsgi.bjoern
<https://dataflakewsgibjoern.readthedocs.io/>`_. See the `Using this package`
section for details on how to integrate `bjoern` using Zope's own
``runwsgi`` script and how to create a suitable WSGI configuration.

If you use ``plone.recipe.zope2instance``, the following
section will pull in the correct dependencies:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
dataflake.wsgi.bjoern
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/bjoern.ini
werkzeug
~~~~~~~~
`werkzeug <https://palletsprojects.com/p/werkzeug/>`_ is a WSGI library that
contains not just a WSGI server, but also a powerful debugger. It can
easily integrate wth Zope using a shim package called `dataflake.wsgi.werkzeug
<https://dataflakewsgiwerkzeug.readthedocs.io/>`_. See the `Using this package`
section for how to integrate `werkzeug` using Zope's own ``runwsgi`` script and
how to create a suitable WSGI configuration.

If you use ``plone.recipe.zope2instance``, the following section will pull in
the correct dependencies, after you have created a WSGI configuration file:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
dataflake.wsgi.werkzeug
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/werkzeug.ini
Debugging Zope applications under WSGI
--------------------------------------
You can debug a WSGI-based Zope application the same way you have debugged
Expand Down

0 comments on commit a4c39a2

Please sign in to comment.