Skip to content

Commit a538e3c

Browse files
authored
Switch to gunicorn's gthread worker type (#242)
gunicorn's default worker type is the `sync` worker, which is more suited to CPU/network-bandwidth bound workloads. As such this switches to the thread-based `gthread` worker for improved request throughput performance for blocking I/O workloads - given that it's common for Heroku apps to make blocking requests to external APIs/datastores etc. The threads count of 5 is somewhat arbitrary, but seemed to work well enough with rough benchmarking against a few different dyno sizes (where the process count will vary according to `WEB_CONCURRENCY`) and simulated workload types. The `preload_app` option has also been enabled, which makes gunicorn load the app before the worker processes are forked, to reduce memory usage and boot times. See: https://docs.gunicorn.org/en/stable/design.html#server-model https://docs.gunicorn.org/en/stable/settings.html#worker-class GUS-W-17236632.
1 parent 962e798 commit a538e3c

File tree

1 file changed

+34
-0
lines changed

1 file changed

+34
-0
lines changed

gunicorn.conf.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,37 @@
1414
# Bind to the IPv6 interface instead of the gunicorn default of IPv4, so the app works in IPv6-only
1515
# environments. IPv4 connections will still work so long as `IPV6_V6ONLY` hasn't been enabled.
1616
bind = [f"[::]:{_port}"]
17+
18+
# The default `sync` worker is more suited to CPU/network-bandwidth bound workloads, so we
19+
# instead use the thread based worker type for improved support of blocking I/O workloads:
20+
# https://docs.gunicorn.org/en/stable/design.html#server-model
21+
#
22+
# If you need to further improve the performance of blocking I/O workloads, you may want to
23+
# try the `gevent` worker type, though you will need to disable `preload_app`, enable DB
24+
# connecting pooling, and be aware that gevent's monkey patching can break some packages.
25+
#
26+
# Note: When changing the number of dynos/workers/threads you will want to make sure you
27+
# do not exceed the maximum number of connections to external services such as DBs:
28+
# https://devcenter.heroku.com/articles/python-concurrency-and-database-connections
29+
worker_class = "gthread"
30+
31+
# gunicorn will start this many worker processes. The Python buildpack automatically sets a
32+
# default for WEB_CONCURRENCY at dyno boot, based on the number of CPUs and available RAM:
33+
# https://devcenter.heroku.com/articles/python-concurrency
34+
workers = os.environ.get("WEB_CONCURRENCY", 1)
35+
36+
# Each `gthread` worker process will use a pool of this many threads.
37+
threads = 5
38+
39+
# Load the app before the worker processes are forked, to reduce memory usage and boot times.
40+
preload_app = True
41+
42+
# Workers silent for more than this many seconds are killed and restarted.
43+
# Note: This only affects the maximum request time when using the `sync` worker.
44+
# For all other worker types it acts only as a worker heartbeat timeout.
45+
timeout = 20
46+
47+
# After receiving a restart signal, workers have this much time to finish serving requests.
48+
# This should be set to a value less than the 30 second Heroku dyno shutdown timeout:
49+
# https://devcenter.heroku.com/articles/dyno-shutdown-behavior
50+
graceful_timeout = 20

0 commit comments

Comments
 (0)