-
Notifications
You must be signed in to change notification settings - Fork 848
Replace Gunicorn with Granian to improve pgAdmin 4 performance #9786
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
base: master
Are you sure you want to change the base?
Changes from all commits
8f36aa8
dbad9fb
0138ee9
072b937
7dbf8cd
2d40dae
d8e6d0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -178,25 +178,31 @@ if [ -z "${PGADMIN_DISABLE_POSTFIX}" ]; then | |||||||||||||||||
| sudo /usr/sbin/postfix start | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| # Get the session timeout from the pgAdmin config. We'll use this (in seconds) | ||||||||||||||||||
| # to define the Gunicorn worker timeout | ||||||||||||||||||
| TIMEOUT=$(cd /pgadmin4 && /venv/bin/python3 -c 'import config; print(config.SESSION_EXPIRATION_TIME * 60 * 60 * 24)') | ||||||||||||||||||
|
|
||||||||||||||||||
| # NOTE: currently pgadmin can run only with 1 worker due to sessions implementation | ||||||||||||||||||
| # Using --threads to have multi-threaded single-process worker | ||||||||||||||||||
|
|
||||||||||||||||||
| if [ -n "${PGADMIN_ENABLE_SOCK}" ]; then | ||||||||||||||||||
| BIND_ADDRESS="unix:/run/pgadmin/pgadmin.sock" | ||||||||||||||||||
| BIND_ARGS="--uds /run/pgadmin/pgadmin.sock" | ||||||||||||||||||
| else | ||||||||||||||||||
| BIND_ARGS="--host ${PGADMIN_LISTEN_ADDRESS:-[::]} --port ${PGADMIN_LISTEN_PORT:-80}" | ||||||||||||||||||
| if [ -n "${PGADMIN_ENABLE_TLS}" ]; then | ||||||||||||||||||
| BIND_ADDRESS="${PGADMIN_LISTEN_ADDRESS:-[::]}:${PGADMIN_LISTEN_PORT:-443}" | ||||||||||||||||||
| else | ||||||||||||||||||
| BIND_ADDRESS="${PGADMIN_LISTEN_ADDRESS:-[::]}:${PGADMIN_LISTEN_PORT:-80}" | ||||||||||||||||||
| BIND_ARGS="--host ${PGADMIN_LISTEN_ADDRESS:-[::]} --port ${PGADMIN_LISTEN_PORT:-443}" | ||||||||||||||||||
| fi | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| if [ -n "${PGADMIN_ENABLE_TLS}" ]; then | ||||||||||||||||||
| exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" --keyfile /certs/server.key --certfile /certs/server.cert -c gunicorn_config.py run_pgadmin:app | ||||||||||||||||||
| if [ "${GUNICORN_ACCESS_LOGFILE:--}" = "-" ]; then | ||||||||||||||||||
| ACCESS_LOG_ARGS="--access-log" | ||||||||||||||||||
| elif [ -n "${GUNICORN_ACCESS_LOGFILE}" ]; then | ||||||||||||||||||
| echo "Warning: GUNICORN_ACCESS_LOGFILE file paths are not supported with Granian. Access logging disabled." >&2 | ||||||||||||||||||
| ACCESS_LOG_ARGS="--no-access-log" | ||||||||||||||||||
| else | ||||||||||||||||||
| exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --limit-request-fields "${GUNICORN_LIMIT_REQUEST_FIELDS:-100}" --limit-request-field_size "${GUNICORN_LIMIT_REQUEST_FIELD_SIZE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" -c gunicorn_config.py run_pgadmin:app | ||||||||||||||||||
| ACCESS_LOG_ARGS="--no-access-log" | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| TLS_ARGS="" | ||||||||||||||||||
| if [ -n "${PGADMIN_ENABLE_TLS}" ]; then | ||||||||||||||||||
| TLS_ARGS="--ssl-keyfile /certs/server.key --ssl-certificate /certs/server.cert" | ||||||||||||||||||
|
Comment on lines
+201
to
+203
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate and inspect the actual file
find . -name "entrypoint.sh" -path "*/docker/*" | head -5Repository: pgadmin-org/pgadmin4 Length of output: 90 🏁 Script executed: # Check if Granian is referenced elsewhere in the entrypoint
rg "granian|tls|ssl|TLS|SSL" pkg/docker/entrypoint.sh -i -A 2 -B 2 | head -80Repository: pgadmin-org/pgadmin4 Length of output: 1198 🌐 Web query:
💡 Result: In Granian 2.7.2, the CLI option:
has a default value of 🌐 Web query:
💡 Result:
How to explicitly control it:
Citations:
🏁 Script executed: # Check if there are any existing GRANIAN_* environment variables in the codebase to confirm naming convention
rg "GRANIAN_" pkg/docker/ -iRepository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Check Python and OpenSSL versions in the Dockerfile
rg "FROM|python|openssl" pkg/docker/Dockerfile -i -A 1 | head -30Repository: pgadmin-org/pgadmin4 Length of output: 125 🏁 Script executed: # Find the Dockerfile
find . -name "Dockerfile*" -type f | head -10Repository: pgadmin-org/pgadmin4 Length of output: 76 🏁 Script executed: # Check the environment variable pattern used in entrypoint.sh
rg "^\s*\$\{[A-Z_]+:-" pkg/docker/entrypoint.sh | head -10Repository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Check environment variable patterns in entrypoint.sh
rg "\$\{" pkg/docker/entrypoint.sh | head -20Repository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Examine the Dockerfile to understand the base image and Python version
head -30 ./DockerfileRepository: pgadmin-org/pgadmin4 Length of output: 860 🏁 Script executed: # Get all environment variable patterns with defaults in entrypoint.sh
grep -E '\$\{[A-Z_]+:-' pkg/docker/entrypoint.shRepository: pgadmin-org/pgadmin4 Length of output: 860 🏁 Script executed: # Look for the section with TLS and surrounding context to understand the env var pattern
sed -n '190,210p' pkg/docker/entrypoint.shRepository: pgadmin-org/pgadmin4 Length of output: 769 Don't silently raise the TLS floor to 1.3. Granian 2.7.2 defaults 🛠️ Suggested change TLS_ARGS=""
if [ -n "${PGADMIN_ENABLE_TLS}" ]; then
- TLS_ARGS="--ssl-keyfile /certs/server.key --ssl-certificate /certs/server.cert"
+ TLS_ARGS="--ssl-keyfile /certs/server.key --ssl-certificate /certs/server.cert --ssl-protocol-min ${GRANIAN_SSL_PROTOCOL_MIN:-tls1.2}"
fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| # Keep the existing environment variables for backward compatibility. | ||||||||||||||||||
| exec /venv/bin/granian --interface wsgi --workers 1 --blocking-threads "${GUNICORN_THREADS:-25}" ${ACCESS_LOG_ARGS} ${TLS_ARGS} ${BIND_ARGS} run_pgadmin:app | ||||||||||||||||||
|
Comment on lines
+206
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "entrypoint.sh" -type f | head -20Repository: pgadmin-org/pgadmin4 Length of output: 90 🏁 Script executed: cat -n pkg/docker/entrypoint.sh | sed -n '200,215p'Repository: pgadmin-org/pgadmin4 Length of output: 495 🌐 Web query:
💡 Result: In Granian 2.7.2, nothing changed about backpressure or blocking-threads specifically—the 2.7.2 patch release is about ASGI websocket handling/metrics fixes (e.g., rejection flow, Django Channels hang) and dependency bumps. [1] What “backpressure” is (and why it matters for WSGI)Granian’s backpressure is a per-worker safeguard that stops the worker accept-loop from taking more connections when “too much work” is already queued for Python. Think of it as a server-level secondary backlog (in addition to the OS listen backlog). [2] Key WSGI nuance: because it acts at the connection accept level, it limits connections, not individual HTTP requests; lots of keep-alive connections (common behind reverse proxies) can “consume” backpressure capacity. [2] What “blocking-threads” is (and how it differs)Granian’s blocking threads are the threads in a worker that interact with the Python interpreter (i.e., execute your app and contend for the GIL). [2] Granian explicitly notes:
Practical difference for WSGI
Granian generally recommends not over-tuning the threadpool and instead setting an appropriate backpressure limit for your environment/workload. [2][3] Rule of thumb for WSGI tuning
Sources: GitHub release notes for 2.7.2 [1]; Granian README / docs on workers, blocking threads, and backpressure [2]; PyPI project description mirroring the same guidance [3]. Add explicit Granian backpressure; Granian treats 🛠️ Suggested change # Keep the existing environment variables for backward compatibility.
-exec /venv/bin/granian --interface wsgi --workers 1 --blocking-threads "${GUNICORN_THREADS:-25}" ${ACCESS_LOG_ARGS} ${TLS_ARGS} ${BIND_ARGS} run_pgadmin:app
+GRANIAN_BACKPRESSURE="${GRANIAN_BACKPRESSURE:-${GUNICORN_THREADS:-25}}"
+exec /venv/bin/granian --interface wsgi --workers 1 \
+ --blocking-threads "${GUNICORN_THREADS:-25}" \
+ --backpressure "${GRANIAN_BACKPRESSURE}" \
+ ${ACCESS_LOG_ARGS} ${TLS_ARGS} ${BIND_ARGS} run_pgadmin:app📝 Committable suggestion
Suggested change
🧰 Tools🪛 Shellcheck (0.11.0)[info] 207-207: Double quote to prevent globbing and word splitting. (SC2086) [info] 207-207: Double quote to prevent globbing and word splitting. (SC2086) 🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.