Delay pgbouncer preStop shutdown to avoid dropped connections during …#69608
Delay pgbouncer preStop shutdown to avoid dropped connections during …#69608bramhanandlingala wants to merge 5 commits into
Conversation
|
|
||
| pgbouncer_default_value = { | ||
| "exec": {"command": ["/bin/sh", "-c", "killall -INT pgbouncer && sleep 120"]} | ||
| "exec": {"command": ["/bin/sh", "-c", "sleep 10 && killall -INT pgbouncer && sleep 20"]} |
There was a problem hiding this comment.
| "exec": {"command": ["/bin/sh", "-c", "sleep 10 && killall -INT pgbouncer && sleep 20"]} | |
| "exec": {"command": ["/bin/sh", "-c", "sleep 10 && killall -INT pgbouncer"]} |
I think that it doesn't do anything really (unless I'm missing something).
| "/bin/sh", | ||
| "-c", | ||
| "killall -INT pgbouncer && sleep 120" | ||
| "sleep 10 && killall -INT pgbouncer && sleep 20" |
There was a problem hiding this comment.
| "sleep 10 && killall -INT pgbouncer && sleep 20" | |
| "sleep 10 && killall -INT pgbouncer" |
| # Sleep first so the pod is marked Terminating and removed from | ||
| # Service endpoints before pgbouncer stops accepting connections, | ||
| # then gracefully drain existing queries. |
There was a problem hiding this comment.
| # Sleep first so the pod is marked Terminating and removed from | |
| # Service endpoints before pgbouncer stops accepting connections, | |
| # then gracefully drain existing queries. | |
| # Sleep first to give time to Kubernetes for removing Service endpoints | |
| # before PgBouncer stops accepting and draining existing connections. |
A bit shorter version
| # Sleep first so the pod is marked Terminating and removed from | ||
| # Service endpoints before pgbouncer stops accepting connections, | ||
| # then gracefully drain existing queries. | ||
| command: ["/bin/sh", "-c", "sleep 10 && killall -INT pgbouncer && sleep 20"] |
There was a problem hiding this comment.
| command: ["/bin/sh", "-c", "sleep 10 && killall -INT pgbouncer && sleep 20"] | |
| command: ["/bin/sh", "-c", "sleep 10 && killall -INT pgbouncer"] |
|
Hi @Miretpl Thanks for the review — updated the comment wording per your suggestion. On && sleep 20, I'd like to keep it. killall -INT pgbouncer returns as soon as the signal is sent, not once pgbouncer finishes draining — so without a delay after it, Kubernetes immediately sends SIGTERM next. Per pgbouncer's docs, SIGTERM arriving while a SIGINT-triggered shutdown is still in progress escalates to an immediate/hard shutdown, killing connections that hadn't finished draining yet — close to the exact bug this PR fixes. The sleep 20 is what gives pgbouncer time to finish gracefully before that second signal can reach it. I'd rather keep it as-is, and I'm happy to add a comment explaining this. Let me know if there's something |
|
I've dug deeper into this, and I think that the analysis is a bit off, as PgBouncer exits right after the signal, because the signal is SIGINT and PgBouncer closed all its connections to the database quickly enough that it seems instantaneous. As far as I looked at the PgBouncer codebase, since 1.23.0, there are two distinct behaviours of shutting down PgBouncer, and both are good depending on the case:
I would say that the resolution to the issue is removing the
If I'm missing something, let me know. It would be good to test it in a real environment with some reasonable amount of executions (unfortunately, I will not be able to do that within a week or two probably). @bramhanandlingala @andrew-stein-sp would you be able to look at it? |
|
@Miretpl That's an interesting theory...and simple. I'll test it on Monday and let you know. |
|
@Miretpl You're right, and thanks for digging into the actual pgbouncer changelog — I was going off older pre-1.23 signal semantics, which is backwards for the 1.23.1 we're pinned to. Makes sense now why SIGINT exits so fast. I'll wait for @andrew-stein-sp test before touching the code further — happy to rework the PR to drop the custom preStop and rely on SIGTERM + terminationGracePeriodSeconds once that's confirmed. |
Description
pgbouncer's preStop hook was signaling shutdown immediately with
killall -INT pgbouncer, which meant the pod could exit before Kubernetes finished removing it from Service routing. Since pgbouncer exits as soon as its in-flight queries drain — often in well under a second for a lightly loaded pod — new connections kept getting routed to a pod that had already shut down, causing the dropped connections during rolling restarts described in #69571.The fix adds a short sleep before signaling shutdown, so Kubernetes has time to mark the pod as Terminating and propagate that to Service endpoints while pgbouncer is still accepting connections. Only after that does it send the interrupt signal to drain and stop pgbouncer. The updated command is
sleep 10 && killall -INT pgbouncer && sleep 20, and the same string was updated consistently acrossvalues.yaml,values.schema.json, and the existing test intest_container_lifecycle.pythat asserts on it.related: #69571