<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>RST on Last DBA</title><link>https://lastdba.com/en/tags/rst/</link><description>Recent content in RST on Last DBA</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 liuzhilong62</copyright><lastBuildDate>Wed, 10 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://lastdba.com/en/tags/rst/index.xml" rel="self" type="application/rss+xml"/><item><title>A Brief Analysis of Connection Pools and TCP Probing</title><link>https://lastdba.com/en/2026/06/10/a-brief-analysis-of-connection-pools-and-tcp-probing/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://lastdba.com/en/2026/06/10/a-brief-analysis-of-connection-pools-and-tcp-probing/</guid><description>&lt;p&gt;It&amp;rsquo;s important for DBAs to understand some connection pool and TCP probing/keepalive knowledge — it helps with troubleshooting disconnection errors, SQL execution errors, and HA failover scenarios.&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP Keepalive and PostgreSQL Parameters
 &lt;div id="tcp-keepalive-and-postgresql-parameters" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp-keepalive-and-postgresql-parameters" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Applications (including business clients, database servers, psql) and the operating system can all set socket options. If not explicitly set, the Linux kernel parameter defaults are used.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Linux Parameter&lt;/th&gt;
 &lt;th&gt;Linux Default&lt;/th&gt;
 &lt;th&gt;Socket Option&lt;/th&gt;
 &lt;th&gt;PG Server Parameter&lt;/th&gt;
 &lt;th&gt;libpq Parameter (PG Client)&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;SO_KEEPALIVE (default 1)&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1(default),on&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_time&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;7200s&lt;/td&gt;
 &lt;td&gt;TCP_KEEPIDLE&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_idle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_idle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_intvl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;75s&lt;/td&gt;
 &lt;td&gt;TCP_KEEPINTVL&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_probes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_retries2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;15&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;TCP_USER_TIMEOUT&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_user_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_user_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;client_connection_check_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both PG server and libpq use the OS socket defaults by default.&lt;/p&gt;</description><content:encoded>&lt;p&gt;It&amp;rsquo;s important for DBAs to understand some connection pool and TCP probing/keepalive knowledge — it helps with troubleshooting disconnection errors, SQL execution errors, and HA failover scenarios.&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP Keepalive and PostgreSQL Parameters
 &lt;div id="tcp-keepalive-and-postgresql-parameters" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp-keepalive-and-postgresql-parameters" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Applications (including business clients, database servers, psql) and the operating system can all set socket options. If not explicitly set, the Linux kernel parameter defaults are used.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Linux Parameter&lt;/th&gt;
 &lt;th&gt;Linux Default&lt;/th&gt;
 &lt;th&gt;Socket Option&lt;/th&gt;
 &lt;th&gt;PG Server Parameter&lt;/th&gt;
 &lt;th&gt;libpq Parameter (PG Client)&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;SO_KEEPALIVE (default 1)&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1(default),on&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_time&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;7200s&lt;/td&gt;
 &lt;td&gt;TCP_KEEPIDLE&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_idle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_idle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_intvl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;75s&lt;/td&gt;
 &lt;td&gt;TCP_KEEPINTVL&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_probes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_retries2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;15&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;TCP_USER_TIMEOUT&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_user_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_user_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;client_connection_check_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both PG server and libpq use the OS socket defaults by default.&lt;/p&gt;
&lt;p&gt;What the defaults mean: after a connection has been idle for 2 hours, the TCP kernel actively sends a keepalive probe, and after 75s × 9 = 11.25 minutes, the connection is terminated.&lt;/p&gt;
&lt;p&gt;The default &lt;code&gt;net.ipv4.tcp_keepalive_time=7200s&lt;/code&gt; is far too large — it&amp;rsquo;s almost meaningless. What&amp;rsquo;s the point of doing keepalive only after the intermediate network layer (firewalls, etc.) has already killed the connection?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;client_connection_check_interval&lt;/code&gt; is an application-layer mechanism introduced in PG 14 — the PG server performs a non-blocking &lt;code&gt;recv()&lt;/code&gt; on the client socket every N milliseconds, and if it returns an error (connection broken), it proactively cleans up. This doesn&amp;rsquo;t require any Linux kernel parameter configuration.&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP FIN and RST Packets
 &lt;div id="tcp-fin-and-rst-packets" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp-fin-and-rst-packets" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Reference: &lt;a href="https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/" target="_blank" rel="noreferrer"&gt;https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;TCP 6 control bits:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Flag&lt;/th&gt;
 &lt;th&gt;Name&lt;/th&gt;
 &lt;th&gt;Purpose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SYN&lt;/td&gt;
 &lt;td&gt;Synchronize&lt;/td&gt;
 &lt;td&gt;Initiates a connection (used in the handshake).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;Acknowledge&lt;/td&gt;
 &lt;td&gt;Confirms receipt of a packet (includes an ACK number for sequence tracking).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;FIN&lt;/td&gt;
 &lt;td&gt;Finish&lt;/td&gt;
 &lt;td&gt;Signals intent to close a connection gracefully.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RST&lt;/td&gt;
 &lt;td&gt;Reset&lt;/td&gt;
 &lt;td&gt;Abruptly terminates a connection (no graceful closure).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PSH&lt;/td&gt;
 &lt;td&gt;Push&lt;/td&gt;
 &lt;td&gt;Forces immediate delivery of data (bypasses buffering).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;URG&lt;/td&gt;
 &lt;td&gt;Urgent&lt;/td&gt;
 &lt;td&gt;Marks data as &amp;ldquo;urgent&amp;rdquo; (rarely used today).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;FIN and RST can be sent in both normal and abnormal situations. Key takeaways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Process exit or program abort sends a FIN packet — this includes &lt;code&gt;kill -9&lt;/code&gt; (verified: killing a PG process with &lt;code&gt;kill -9&lt;/code&gt; sends FIN; see &amp;ldquo;Tests&amp;rdquo; section)&lt;/li&gt;
&lt;li&gt;Network unreachability such as port not listening produces an RST packet&lt;/li&gt;
&lt;li&gt;TCP keepalive timeout also produces an RST packet, because the probe detected network unreachability&lt;/li&gt;
&lt;li&gt;Firewalls may also send RESET&lt;/li&gt;
&lt;li&gt;RST packets are related to the application-layer &lt;code&gt;connection reset by peer&lt;/code&gt; error&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below is a detailed explanation of the 6 TCP control bits and FIN/RST:&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP Disconnection Tests
 &lt;div id="tcp-disconnection-tests" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp-disconnection-tests" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Test: Does killing a session trigger an active disconnect?
 &lt;div id="test-does-killing-a-session-trigger-an-active-disconnect" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-does-killing-a-session-trigger-an-active-disconnect" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ORACLE: whether using the built-in &lt;code&gt;alter system&lt;/code&gt; to kill a session or &lt;code&gt;kill -9&lt;/code&gt; to kill a session, the client receives a FIN packet from the server.&lt;/li&gt;
&lt;li&gt;PG: using the built-in &lt;code&gt;pg_terminate_backend()&lt;/code&gt; to kill a session, the client receives a FIN packet from the server.&lt;/li&gt;
&lt;li&gt;Redis: shutting down the database or &lt;code&gt;kill -9&lt;/code&gt; on the redis-server process, the client receives a FIN packet from the server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Test conclusion: Even when a process terminates abnormally, the TCP kernel can send a FIN packet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Additionally, in this round of testing, redis-cli did not appear to handle the FIN packet correctly — it sent RST on its own:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;Seq&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Time&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Direction&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Flags&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;1&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:42:43.131958&lt;/td&gt;
 &lt;td style="text-align: left"&gt;S→C&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server sends ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;2&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:42:49.264831&lt;/td&gt;
 &lt;td style="text-align: left"&gt;S→C&lt;/td&gt;
 &lt;td style="text-align: left"&gt;[F.] FIN+ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server actively requests close&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;3&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:42:49.304905&lt;/td&gt;
 &lt;td style="text-align: left"&gt;C→S&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client ACKs FIN (ack=9=8+1)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;4~15&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:43:04 ~ 17:44:19&lt;/td&gt;
 &lt;td style="text-align: left"&gt;C→S&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client keeps ACKing (holding connection?)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;16&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:44:19.323962&lt;/td&gt;
 &lt;td style="text-align: left"&gt;S→C&lt;/td&gt;
 &lt;td style="text-align: left"&gt;[R] RST&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server sends RST&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;Test: What packet does the client receive when PG process terminates, normal shutdown, or forced shutdown?
 &lt;div id="test-what-packet-does-the-client-receive-when-pg-process-terminates-normal-shutdown-or-forced-shutdown" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-what-packet-does-the-client-receive-when-pg-process-terminates-normal-shutdown-or-forced-shutdown" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Test environment: Rocky 10.1 + PG 18.2, tcpdump capturing TCP packets on the lo interface.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Scenario&lt;/th&gt;
 &lt;th&gt;Server sends&lt;/th&gt;
 &lt;th&gt;Four-way handshake&lt;/th&gt;
 &lt;th&gt;Client error&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pg_terminate_backend(PID)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK&lt;/td&gt;
 &lt;td&gt;✅ Complete&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;FATAL: terminating connection due to administrator command&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pg_ctl stop -m fast&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK&lt;/td&gt;
 &lt;td&gt;✅ Complete&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;FATAL: terminating connection due to administrator command&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;kill -9 postmaster&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK&lt;/td&gt;
 &lt;td&gt;✅ Complete&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;server closed the connection unexpectedly&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Conclusion: kill -9 also sends FIN, not RST.&lt;/strong&gt; When a process is SIGKILLed, the Linux TCP kernel closes the socket on behalf of the process, sending FIN to complete the four-way handshake. In all three scenarios, the client receives a normal FIN close — no scenario produces RST.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Test: How to produce an RST packet
 &lt;div id="test-how-to-produce-an-rst-packet" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-how-to-produce-an-rst-packet" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Port not listening (PG already shut down)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:01:48.492004 IP 127.0.0.1.52092 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 2570941791
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:01:48.492012 IP 127.0.0.1.ircu-2 &amp;gt; 127.0.0.1.52092: Flags [R.], seq 0, ack 2570941792, win 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Client SYN → kernel returns &lt;code&gt;[R.]&lt;/code&gt; RST+ACK, &lt;code&gt;win 0&lt;/code&gt;. psql reports &lt;code&gt;Connection refused&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iptables REJECT &amp;ndash;reject-with tcp-reset&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:02:37.768515 IP 127.0.0.1.36436 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 382980016
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:02:37.768522 IP 127.0.0.1.ircu-2 &amp;gt; 127.0.0.1.36436: Flags [R.], seq 0, ack 382980017, win 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Exactly the same as port not listening: &lt;code&gt;[R.]&lt;/code&gt; RST+ACK. psql likewise reports &lt;code&gt;Connection refused&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iptables DROP (simulating firewall silent drop)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:00:07.050040 IP 127.0.0.1.33166 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 985608804
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:00:08.095618 IP 127.0.0.1.33166 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 985608804 ← retransmit after 1s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:00:09.119647 IP 127.0.0.1.33166 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 985608804 ← retransmit after 2s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;No response from the server. The client retransmits SYN 3 times (at 1s, 2s, 4s intervals) then times out. &lt;strong&gt;Unlike REJECT, DROP produces no RST — the client can only detect it via timeout.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary of RST-producing scenarios&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Scenario&lt;/th&gt;
 &lt;th&gt;Layer&lt;/th&gt;
 &lt;th&gt;Packet Type&lt;/th&gt;
 &lt;th&gt;Triggered By&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Port not listening&lt;/td&gt;
 &lt;td&gt;TCP kernel&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[R.]&lt;/code&gt; RST+ACK&lt;/td&gt;
 &lt;td&gt;OS kernel&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Firewall REJECT&lt;/td&gt;
 &lt;td&gt;iptables&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[R.]&lt;/code&gt; RST+ACK&lt;/td&gt;
 &lt;td&gt;Firewall&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TCP keepalive timeout&lt;/td&gt;
 &lt;td&gt;TCP kernel&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[R]&lt;/code&gt; RST&lt;/td&gt;
 &lt;td&gt;OS kernel (after keepalive probe fails)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Process termination (kill -9)&lt;/td&gt;
 &lt;td&gt;TCP kernel&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK (NOT RST!)&lt;/td&gt;
 &lt;td&gt;OS kernel closes socket on behalf of process&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Firewall DROP&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Core distinction: FIN comes from process exit (kernel gracefully closes on behalf of the process, even for kill -9); RST comes from network unreachability.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;Test: Does taking an IP offline trigger an active disconnect?
 &lt;div id="test-does-taking-an-ip-offline-trigger-an-active-disconnect" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-does-taking-an-ip-offline-trigger-an-active-disconnect" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;redis-cli test, taking the Redis server&amp;rsquo;s listening IP offline.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#term1:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r -h 30.181.15.96 -p &lt;span style="color:#ae81ff"&gt;17742&lt;/span&gt; -a 1qaz@WSX
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tcpdump host 30.181.48.7 and port &lt;span style="color:#ae81ff"&gt;54854&lt;/span&gt; -n -vv 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#term2:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tcpdump host 30.181.48.7 and port &lt;span style="color:#ae81ff"&gt;54854&lt;/span&gt; -n -vv &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this test, taking the IP offline did not produce any FIN or RST packets. Only the keepalive mechanism itself initiated an RST. The sequence:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;Seq&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Time&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Direction&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Flags&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;1&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:43.004897&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client sends ACK (15s interval)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;2&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:43.004960&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server→Client&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server responds ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;3&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.043896&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client Keep-Alive (15s interval)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;4&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.043953&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server→Client&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server responds ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;5&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.063214&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server→Client&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Server duplicate ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;6&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.063234&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client responds ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;7&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:13.051905&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client Keep-Alive (15s interval)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;8&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:18.059901&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client Keep-Alive (5s interval)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;9&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:23.067901&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client Keep-Alive (5s interval)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;10&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:28.075899&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client→Server&lt;/td&gt;
 &lt;td style="text-align: left"&gt;[R.] RST+ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Client actively disconnects (5s interval)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;redis-cli has no keepalive configuration, but the &lt;a href="https://raw.githubusercontent.com/redis/redis/refs/heads/4.0/src/redis-cli.c" target="_blank" rel="noreferrer"&gt;redis-cli source code&lt;/a&gt; hardcodes:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define REDIS_CLI_KEEPALIVE_INTERVAL 15 &lt;/span&gt;&lt;span style="color:#75715e"&gt;/* seconds */&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;redis-cli&amp;rsquo;s keepalive is hardcoded at 15 seconds in the code, hence the 15-second keepalive packets visible in the capture.&lt;/p&gt;
&lt;p&gt;During the capture, the server IP was taken offline but no disconnection notification was received. Eventually, the client&amp;rsquo;s keepalive probe detected the socket anomaly, and the client actively sent RST.&lt;/p&gt;
&lt;p&gt;(The Redis server side can also initiate keepalive, but it wasn&amp;rsquo;t triggered this time.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test conclusion: Directly taking an IP offline — the kernel may not perform any FIN/RST action at all.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;Test: Does normal data communication interfere with the tcp_keepalive cycle?
 &lt;div id="test-does-normal-data-communication-interfere-with-the-tcp_keepalive-cycle" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-does-normal-data-communication-interfere-with-the-tcp_keepalive-cycle" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Conclusion: Yes, it does. Data communication not only sends PSH packets to the peer but also includes ACK packets.&lt;/p&gt;
&lt;p&gt;The following test uses redis-cli, where redis-cli&amp;rsquo;s keepalive = 15s and redis-server&amp;rsquo;s keepalive = 2h:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Client Trigger&lt;/th&gt;
 &lt;th&gt;TCP Timestamp&lt;/th&gt;
 &lt;th&gt;Client Sends&lt;/th&gt;
 &lt;th&gt;Server Sends&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;tcp_keepalive&lt;/td&gt;
 &lt;td&gt;17:16:05.558570-17:16:15.048701&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PING&lt;/td&gt;
 &lt;td&gt;17:16:15.048312-17:16:15.048701&lt;/td&gt;
 &lt;td&gt;PSH&lt;/td&gt;
 &lt;td&gt;PSH&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;tcp_keepalive&lt;/td&gt;
 &lt;td&gt;17:16:15.048433-17:16:30.071278&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;tcp_keepalive&lt;/td&gt;
 &lt;td&gt;17:16:30.070906-17:16:30.071278&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;Test: Does idle_in_transaction and long-running SQL trigger keepalive?
 &lt;div id="test-does-idle_in_transaction-and-long-running-sql-trigger-keepalive" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-does-idle_in_transaction-and-long-running-sql-trigger-keepalive" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Test environment: Rocky 10.1 + PG 18.2, client libpq configured with &lt;code&gt;keepalives_idle=5 keepalives_interval=3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;idle_in_transaction:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:11.611 Last data ACK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:16.927 Client → Server [.] ACK ← after 5.3s, first keepalive probe
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:16.927 Server → Client [.] ACK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:21.983 Client → Server [.] ACK ← after 5s, second probe
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:21.983 Server → Client [.] ACK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:27.039 Client → Server [.] ACK ← after 5s, third probe
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:27.039 Server → Client [.] ACK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Conclusion: idle_in_transaction &lt;strong&gt;does send keepalive&lt;/strong&gt;. Every 5 seconds, a pair of probe+response — no other TCP packets whatsoever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long-running SQL (server &lt;code&gt;tcp_keepalives_idle=10&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:43.148 Last ACK (after client sends SELECT pg_sleep(30))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ← 10 seconds of zero TCP packets ← SQL is running, but no data returned
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:53.279 Server → Client [.] ACK ← after 10.1s, server sends keepalive probe
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:53.279 Client → Server [.] ACK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Conclusion: &lt;strong&gt;SQL running ≠ TCP has packets.&lt;/strong&gt; During &lt;code&gt;pg_sleep(30)&lt;/code&gt;, there&amp;rsquo;s zero TCP communication — keepalive still fires. It only cares whether there&amp;rsquo;s data exchange at the TCP layer, not what the database is doing.&lt;/p&gt;
&lt;p&gt;If a report query runs for 5 minutes without returning intermediate results, from the perspective of firewalls/NAT/load balancers, this TCP connection is a 5-minute dead connection — without keepalive configured, it will be killed.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Connection Probing
 &lt;div id="connection-probing" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#connection-probing" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The problem of dead connections on the client side can only be solved by the client — the server is already unreachable, so you can&amp;rsquo;t expect it to notify you.&lt;/p&gt;
&lt;p&gt;Two key concepts of connection pools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;socket.close()&lt;/code&gt; ≠ connection pool &lt;code&gt;close()&lt;/code&gt;&lt;/strong&gt;: The former is a TCP four-way handshake completely disconnecting; the latter is returning the connection to the pool. The connection remains ESTABLISHED, with its state changing to idle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goal of probing&lt;/strong&gt;: To promptly detect &amp;ldquo;zombie connections&amp;rdquo; — sockets that are already broken but the connection pool still considers alive.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two common socket error states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESTABLISHED but actually unusable&lt;/strong&gt;: The connection pool hasn&amp;rsquo;t detected that the socket has failed; errors only appear when the application layer tries to use it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIME_WAIT&lt;/strong&gt;: The socket is known to be unusable but not released in time; a large number of TIME_WAIT connections can exhaust ports.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Broadly speaking, probing mechanisms are divided into two types by network layer:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Type&lt;/th&gt;
 &lt;th&gt;Action&lt;/th&gt;
 &lt;th&gt;Trigger Method&lt;/th&gt;
 &lt;th&gt;Content Sent&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Layer 4 probing&lt;/td&gt;
 &lt;td&gt;Kernel-level TCP packets&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive&lt;/code&gt; series parameters; connection pool&amp;rsquo;s own keepalive&lt;/td&gt;
 &lt;td&gt;ACK packets (empty probe to check if peer is alive)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Layer 7 probing&lt;/td&gt;
 &lt;td&gt;Application-layer database commands&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt; / &lt;code&gt;testOnReturn&lt;/code&gt; / &lt;code&gt;testWhileIdle&lt;/code&gt; / &lt;code&gt;PING&lt;/code&gt; / configure test-query&lt;/td&gt;
 &lt;td&gt;Depends on driver, e.g., &lt;code&gt;SELECT 1&lt;/code&gt;, &lt;code&gt;PING&lt;/code&gt;; &lt;code&gt;SELECT NOT pg_is_in_recovery()&lt;/code&gt; / &lt;code&gt;SELECT @@READ_ONLY&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;Layer 4 Probing
 &lt;div id="layer-4-probing" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#layer-4-probing" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Linux&amp;rsquo;s &lt;code&gt;tcp_keepalive&lt;/code&gt; is the foundation of Layer 4 probing:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.tcp_keepalive_time &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7200&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Start probing after 2 hours of idle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.tcp_keepalive_intvl &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;75&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Probe interval 75 seconds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.tcp_keepalive_probes &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt; &lt;span style="color:#75715e"&gt;# After 9 failed probes, disconnect&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The problem with the defaults: 7200 seconds (2 hours) before probing begins — by then the firewall has long since killed the connection, making the probe pointless. Production environments typically need to tune this down to the minute level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If there&amp;rsquo;s a proxy in the path (Nginx, HAProxy, etc.), TCP keepalive only reaches the proxy, not the backend database.&lt;/strong&gt; The proxy-to-database segment needs the proxy&amp;rsquo;s own keepalive configuration; otherwise, if the proxy dies, the connection pool won&amp;rsquo;t notice.&lt;/p&gt;
&lt;p&gt;In actual communication, when there&amp;rsquo;s data exchange, PSH/ACK packets themselves serve as a form of &amp;ldquo;keepalive.&amp;rdquo; Keepalive only triggers when the connection is completely idle — if there&amp;rsquo;s continuous data send/receive, the keepalive timer gets reset and no ACK probe packets are sent.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Layer 7 Probing
 &lt;div id="layer-7-probing" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#layer-7-probing" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Layer 7 probing is when the application actively sends database commands to verify the connection. Representative parameters for various connection pools (not exhaustive):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Connection Pool&lt;/th&gt;
 &lt;th&gt;Parameter&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;JDBC Generic&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt;, &lt;code&gt;testOnReturn&lt;/code&gt;, &lt;code&gt;testWhileIdle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Validate on borrow/return/idle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;HikariCP&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;connectionTestQuery&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Validation SQL, commonly &lt;code&gt;SELECT 1&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Jedis&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Validate on borrow&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Lettuce&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pingBeforeActivateConnection&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;PING before activation&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Redisson&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pingConnectionInterval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Periodic PING interval&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Apache Commons Pool2&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt;, etc.&lt;/td&gt;
 &lt;td&gt;Generic object pool validation&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both &lt;code&gt;close()&lt;/code&gt; and &lt;code&gt;returnObject()&lt;/code&gt; return the connection to the pool, not truly close the TCP connection. After being returned, the connection is in idle state, but the socket remains ESTABLISHED. Apache Commons Pool2 maintains these connections through a standardized object pool management mechanism.&lt;/p&gt;
&lt;p&gt;Regarding the performance impact of &lt;code&gt;testOnBorrow&lt;/code&gt;: issuing &lt;code&gt;SELECT 1&lt;/code&gt; every time a connection is borrowed adds overhead under high concurrency. Typically, &lt;code&gt;testWhileIdle&lt;/code&gt; + a reasonable check interval is used to balance this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choosing between Layer 4 and Layer 7:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Layer 4: Direct database connection, no proxy in the path — just tune TCP keepalive to a small value.&lt;/li&gt;
&lt;li&gt;Layer 7: Proxy in the path, need to confirm the database can truly execute SQL (not just TCP reachable), and can ensure the entire path is clear.&lt;/li&gt;
&lt;li&gt;Layer 7 + role awareness: When primary/replica distinction is needed, simple SQL like &lt;code&gt;SELECT 1&lt;/code&gt; can&amp;rsquo;t identify the database role — custom SQL must be configured. For example, Redis &lt;code&gt;PING&lt;/code&gt; can&amp;rsquo;t tell you the replica&amp;rsquo;s status.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Single Domain vs Dual Domain
 &lt;div id="single-domain-vs-dual-domain" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#single-domain-vs-dual-domain" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;When the driver is configured with primary/replica addresses (JDBC&amp;rsquo;s &lt;code&gt;read-write&lt;/code&gt; + &lt;code&gt;read-only&lt;/code&gt;, or Lettuce&amp;rsquo;s Master/Replica), it can automatically identify primary/replica and route accordingly.&lt;/p&gt;
&lt;p&gt;Problems with a single domain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can&amp;rsquo;t detect primary/replica switchover&lt;/li&gt;
&lt;li&gt;Constrained by JVM/OS DNS caching (&lt;code&gt;networkaddress.cache.ttl&lt;/code&gt;) — after switchover, connections may keep going to the old IP for a long time&lt;/li&gt;
&lt;li&gt;Layer 7 probing with &lt;code&gt;SELECT NOT pg_is_in_recovery()&lt;/code&gt; can detect primary/replica changes, but it&amp;rsquo;s less flexible than dual domains&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Summary
 &lt;div id="summary" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#summary" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;FIN and RST occurrence scenarios:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FIN is sent by the kernel on behalf of the exiting process (including kill -9), completing a graceful four-way handshake close&lt;/li&gt;
&lt;li&gt;RST is produced when the network is unreachable: port not listening, keepalive timeout, firewall REJECT, etc.&lt;/li&gt;
&lt;li&gt;Directly taking an IP offline produces no FIN/RST — it can only be detected by keepalive probing&lt;/li&gt;
&lt;li&gt;Firewall DROP silently discards packets — no RST, client can only detect via timeout&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Layer 4 and Layer 7 probing mechanisms:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Layer 4 (TCP keepalive): Defaults to probing after 2 hours — must be tuned down for production. Only reaches the proxy, not the backend.&lt;/li&gt;
&lt;li&gt;Layer 7 (application-layer PING/SQL): Can confirm the database can truly execute commands, but has performance overhead under high concurrency.&lt;/li&gt;
&lt;li&gt;Proxy present / primary-replica distinction needed → Layer 7 is required&lt;/li&gt;
&lt;li&gt;Direct database connection → Layer 4 tuned to a small value suffices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keepalive behavior for idle_in_transaction and long-running SQL:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Both trigger keepalive — the trigger condition is no data exchange at the TCP layer, not the database state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL running ≠ TCP has packets&lt;/strong&gt;: Long report queries that don&amp;rsquo;t return intermediate results are equivalent to dead connections at the TCP layer&lt;/li&gt;
&lt;li&gt;Without keepalive configured, firewalls may kill the connection while the SQL is still running&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Some notes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;socket.close()&lt;/code&gt; ≠ connection pool return: the former disconnects TCP; the latter merely marks the connection as idle&lt;/li&gt;
&lt;li&gt;The goal of connection pool probing is to discover those zombie connections whose sockets are already broken but the pool still thinks are alive&lt;/li&gt;
&lt;li&gt;&lt;code&gt;testOnBorrow&lt;/code&gt; queries the database on every borrow — overhead under high concurrency; &lt;code&gt;testWhileIdle&lt;/code&gt; + a reasonable interval is more practical&lt;/li&gt;
&lt;li&gt;When there&amp;rsquo;s a proxy in the path, each segment needs independent keepalive configuration — if one segment breaks, the other side won&amp;rsquo;t notice&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 class="relative group"&gt;ref
 &lt;div id="ref" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ref" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/redis/redis/refs/heads/4.0/src/redis-cli.c" target="_blank" rel="noreferrer"&gt;https://raw.githubusercontent.com/redis/redis/refs/heads/4.0/src/redis-cli.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redisson.pro/docs/configuration/" target="_blank" rel="noreferrer"&gt;https://redisson.pro/docs/configuration/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.paic.com.cn/#/post/57844638" target="_blank" rel="noreferrer"&gt;https://docs.paic.com.cn/#/post/57844638&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.huaweicloud.com/intl/en-us/dcs_faq/dcs-faq-211230001.html" target="_blank" rel="noreferrer"&gt;https://support.huaweicloud.com/intl/en-us/dcs_faq/dcs-faq-211230001.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://howtodoinjava.com/spring-data/spring-boot-redis-with-lettuce-jedis/" target="_blank" rel="noreferrer"&gt;https://howtodoinjava.com/spring-data/spring-boot-redis-with-lettuce-jedis/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redis/lettuce/wiki/Connection-Pooling" target="_blank" rel="noreferrer"&gt;https://github.com/redis/lettuce/wiki/Connection-Pooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.github.io/lettuce/advanced-usage/client-options/" target="_blank" rel="noreferrer"&gt;https://redis.github.io/lettuce/advanced-usage/client-options/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.github.io/lettuce/advanced-usage/connection-pooling/" target="_blank" rel="noreferrer"&gt;https://redis.github.io/lettuce/advanced-usage/connection-pooling/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.csdn.net/u014495560/article/details/103576786" target="_blank" rel="noreferrer"&gt;https://blog.csdn.net/u014495560/article/details/103576786&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.man7.org/linux/man-pages/man7/socket.7.html" target="_blank" rel="noreferrer"&gt;https://www.man7.org/linux/man-pages/man7/socket.7.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.man7.org/linux/man-pages/man7/tcp.7.html" target="_blank" rel="noreferrer"&gt;https://www.man7.org/linux/man-pages/man7/tcp.7.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/runtime-config-connection.html" target="_blank" rel="noreferrer"&gt;https://www.postgresql.org/docs/18/runtime-config-connection.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/libpq-connect.html" target="_blank" rel="noreferrer"&gt;https://www.postgresql.org/docs/18/libpq-connect.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/" target="_blank" rel="noreferrer"&gt;https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/cd/E13189_01/kodo/docs324/ref_guide_dbsetup.html" target="_blank" rel="noreferrer"&gt;https://docs.oracle.com/cd/E13189_01/kodo/docs324/ref_guide_dbsetup.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>