Skip to content

TCP Keepalive bei Oracle Servern

Zu einer TCP Verbindung zwischen zwei Endpunkten gehört auch, sich darüber einig zu sein ob eine Verbindung noch besteht oder nicht. Wartet eine Seite auf weitere Daten und ist die andere Seite inzwischen verschwunden, so sieht die wartende Seite nichts von dem TCP Verbindungsabbruch.

Dies geschieht nur, wenn die TCP Verbindung unsauber abgebrochen wurde. Ursachen einer solchen unsauberen Verbindungsbeendigung könnte Stromausfall, Hardwarecrash, Kernelpanic oder das unerwartete entfernen der IP-Addresse (wie z.B. bei Failover-Clustern) sein. Ebenso kann ein Netzwerkausfall dazu führen dass die Beendigung einer Verbindung nur auf einer Seite bekannt wird. Manche Firewalls die eine Verbindung nur eine bestimmte Zeit zulassen können hier auch zu Problemen führen.

Übrigens, ein Beenden der Anwendung (auch mit kill -9) alleine führt nicht zu so einer Situation. Denn das Betriebsystem schliesst alle Sockets eines beendeten Prozesses sauber.

Welche Methoden stehen für ein Endpunkt zur Verfügung einseitig bestehende Verbindungen mit Lese-Operationen zu erkennen?

  1. sie implementiert einen Lese-Timeout. Dies hat den Vorteil dass auch hängende Gegenstellen erkannt werden. Problem dabei ist aber die Dauer des Timouts. Ist dieser zu kurz gewählt so werden Verbindungen vorzeitig beendet wenn die Gegenstelle langsam antwortet. Um dies zu verhindern muss der Timeout recht groß gewählt werden. Bei Anwendungen die nur sehr sporadisch Anfragen bekommen und deren Verbindungen lange Zeit idle sein sollen lassen sich deswegen Timeouts fast garnicht verwenden.
  2. die Seite die lesend auf dem Socket wartet muss regelmäßig Daten senden oder erwarten. Dies wird gemeinhin als Heartbeat bezeichnet. Dieser hat aber den Nachteil dass zum einen das eingesetzte Protokoll solche Heartbeat Nachrichten erlauben (wenn ich auf eine Antwort warte und stattdessen eine Heartbeat Nachricht erhalte). Die Implementierung wird dadurch komplexer, braucht z.B. mehrere Threads oder Asynchrone methoden. Zudem hat das Versenden von Heartbeat Nachrichten auf einer Verbindung den Nachteil dass im Falle einer Unerreichbarkeit der TCP retry Mechanismus zuschlägt, der ggf. Relativ lange benötigen kann bis er Probleme feststellt,
  3. eine weitaus unproblematischere Möglichkeit ist der TCP Keepalive Mechanismus. Dieser wird entgegen seinem Namen hauptsächlich dazu verwendet abgerissene Connections zu erkennen. Der Mechanismus funktioniert so, dass auf der Seite einer TCP Verbindung auf der Keepalive aktiv ist (ggf. auf beiden) regelmäßig geprüft wird ob eine erfolgreiche Kommunikation stattfand, oder der Socket idle ist. Wenn er Idle ist weil nichts versendet oder empfangen wurde so sendet der Keepalive Mechanismus ein leeres TCP Paket. Die Anwendungen selbst bekommen davon nichts mit, nur der TCP Stack der Gegenseite bestätigt das Paket. in Fehler beim versenden führt zu einem Abbruch der Verbindung, genauso wie das ausbleiben von einer Antwort nach eingestellter Wiederholung.

Um das in der Praxis zu sehen habe ich eine Oracle 12c mit „Dead Client Detection“ konfiguriert. Dabei handelt es sich um eine Option welche den TCP Keepalive auf eingehenden Client Connections anschaltet (und auch gleich auf den angegebenen Wert in Minuten konfiguriert). Das ist deswegen ganz praktisch weil der default Wert unter Linux bei 2h liegt und damit nicht nur relativ nutzlos ist, sondern auch zum testen sehr langatmig.

Wenn ich jetzt eine Oracle Verbindung aufbaue, so werden nur Daten an den Server gesendet wenn ich im Client einen SQL Befehl absetze. Mit netstat oder dem neueren ss (socket statistics) kann man den Zustand der TCP timer einer einzelnen Verbindung betrachten. In diesem Fall verwende ich die -o option gibt die timer Information mit aus.

[oracle@bernd-db centos]$ netstat -tnpo
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0      0 10.14.100.82:22         10.0.103.42:53111       ESTABLISHED -   on (0.22/0/0)
tcp        0      0 127.0.0.1:58466         127.0.0.1:1521          ESTABLISHED 1207/ora_lreg_orcl   off (0.00/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (22.76/0/0)

Um nur die relevante Verbindung fortwährend zu betrachten verwende ich die -c option. Mittels grep beschränke ich mich auf eine Verbindung. (Dieses Filtern kann mit ss effizienter gemacht werden, aber auf meinem Testsystem gibt es keine große Anzahl an Verbindungen) Parallel dazu läuft folgender tcpdump Befehl

[oracle@bernd-db centos]$ sudo tcpdump  -ttt -nn --dont-verify-checksums -U -v host 10.0.103.42 and port 54076 &
[1] 18819
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

[oracle@bernd-db centos]$ netstat -tnpoc 2>/dev/null | grep 10.0.103.42:54076
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (8.66/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (7.65/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (6.64/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.64/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.63/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.62/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.60/0/0)

 00:00:00.000000 IP (tos 0x0, ttl 64, id 2241, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 3621868956, win 1184, length 0
 00:00:00.001712 IP (tos 0x0, ttl 124, id 10289, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [.], ack 1, win 256, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.60/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.59/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.59/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.58/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.57/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (53.93/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (52.92/0/0)
...

Was ist passiert? In diesem Beispiel sieht man die letzten 8 Sekunden des herabzählenden keepalive timers. In der ganzen Minute davor wurden keine Daten ausgetauscht, und so sendet der Kernel beim Ablauf des Timers ein leeres ACK Paket (Flags [.] length 0) und wartet 6 weitere Sekunden bevor er prüft ob dieses angekommen ist. Wenn ja setzt er einen neuen Timer auf (60s abzüglich der bereits gewarteten knapp 6s)

Der Keepalive Timer wird bei Oracle 12.2 mit der Option sqlnet.expire_time=1 in ${ORACLE_HOME}/network/admin/sqlnet.ora auf eine Minute konfiguriert. Das Wiederholungsintervall von 6s wird durch Oracle fest vorgegeben.

Wenn ich jetzt 18 Sekunden bevor der Timer abläuft eine Client Anfrage stelle, so ändert sich die Ausgabe etwas:

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (19.71/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (18.70/0/0)

 00:01:41.833122 IP (tos 0x0, ttl 124, id 10335, offset 0, flags [DF], proto TCP (6), length 61)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 1:22, ack 1, win 256, length 21
 00:00:00.000306 IP (tos 0x0, ttl 64, id 2242, offset 0, flags [DF], proto TCP (6), length 55)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 1:16, ack 22, win 1184, length 15
 00:00:00.007188 IP (tos 0x0, ttl 124, id 10337, offset 0, flags [DF], proto TCP (6), length 53)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 22:35, ack 16, win 256, length 13
 00:00:00.000077 IP (tos 0x0, ttl 64, id 2243, offset 0, flags [DF], proto TCP (6), length 55)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 16:31, ack 35, win 1184, length 15
 00:00:00.041996 IP (tos 0x0, ttl 124, id 10339, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [.], ack 31, win 256, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (17.69/0/0)

 00:00:00.881484 IP (tos 0x0, ttl 124, id 10342, offset 0, flags [DF], proto TCP (6), length 139)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 35:134, ack 31, win 256, length 99
 00:00:00.000610 IP (tos 0x0, ttl 64, id 2244, offset 0, flags [DF], proto TCP (6), length 234)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 31:225, ack 134, win 1184, length 194
 00:00:00.003408 IP (tos 0x0, ttl 124, id 10344, offset 0, flags [DF], proto TCP (6), length 61)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 134:155, ack 225, win 255, length 21
 00:00:00.000238 IP (tos 0x0, ttl 64, id 2245, offset 0, flags [DF], proto TCP (6), length 55)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 225:240, ack 155, win 1184, length 15
 00:00:00.041847 IP (tos 0x0, ttl 124, id 10346, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [.], ack 240, win 255, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (16.69/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (15.68/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (14.67/0/0)
...
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (7.62/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (6.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.60/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.59/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.58/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.57/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.57/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (42.22/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (41.21/0/0)

Es ist aber wieder zu sehen dass der Timer erst auf 0 zählt, dann erkennt dass ein Austausch vor 18 Sekunden stattfand, und deswegen einen neuen Timer mit der verbleibenden Restzeit von 40 Sekunden aufzieht aber kein ACK Paket versendet.

Um jetzt zu sehen was passiert wenn der Client keine Antworten versendet installiere ich einfach eine Firewall Regel die alle Pakete verwirft (DROP nicht REJECT):

[oracle@bernd-db centos]$ iptables -I INPUT -p tcp -s 10.0.103.42 --sport 54076 -j DROP

Im Folgenden habe ich die Antwort-Pakete (die tcpdump noch sieht) entfernt:

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.31/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.30/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.30/0/0)

 00:02:00.190237 IP (tos 0x0, ttl 64, id 2258, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 155, win 1184, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.31/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.30/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.29/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.28/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.27/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.27/0/1)

 00:00:06.014269 IP (tos 0x0, ttl 64, id 2259, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 155, win 1184, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.27/0/2)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.27/0/2)
...
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.04/0/9)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.03/0/9)

 00:00:06.014242 IP (tos 0x0, ttl 64, id 2267, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 155, win 1184, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.04/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.04/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.03/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.02/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.01/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.01/0/10)

 00:00:06.013377 IP (tos 0x0, ttl 64, id 2268, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [R.], seq 240, ack 155, win 1184, length 0

Wenn dieses mal der Timer abläuft ohne Datenaustausch, so wird das Keepalive Paket vom Server versendet und der Timer auf 6s gestellt. Da das Paket aber nicht durchkommt, so kam auch nach 6s keine Antwort zurück. Der Kernel auf Serverseite erkennt dies, zählt den Keepalive Zähler hoch (letzte Stelle innerhalb von timer (time/retries/keepalives)), sendet noch ein Paket und wartet erneut 6s.

Oracle hat den Retry auf 10 Probes konfiguriert, entsprechend wird bei der letzten Runde (nach einer Minue) kein Keepalive Paket mehr gesendet sondern die Connection resettet. (Flags [R.] - In meinem Fall bekommt der Client das RST nicht, wegen der Firewall). Hätte ich die Firewall Regel vor Ablauf der 10 retries entfernt, so hätte der normale Keepalive Ablauf wieder weiter gemacht.

Falls die Anwendung keine Konfiguration von TCP Keepalives pro Socket zulässt, so werden folgende Linux Kernel Werte verwendet:

[oracle@bernd-db oracle]$ sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9

Es gibt noch eine Besonderheit, wenn der Keepalive wiederholt wird, aber gleichzeitig auch Daten versendet werden, so wird der Keepalive Mechanismus ausgesetzt, stattdessen versucht der normale Retry Mechanismus Daten zu versenden, aber das soll Gegenstand eines anderen Blogposts sein.

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

Bernd Eckenfels on :

The aborted connections do show up in the Oracle alert log with the following rather large and ugly error message:
CODE:
Fatal NI connect error 12170.   VERSION INFORMATION:         TNS for Linux: Version 12.2.0.1.0 - Production         Oracle Bequeath NT Protocol Adapter for Linux: Version 12.2.0.1.0 - Production         TCP/IP NT Protocol Adapter for Linux: Version 12.2.0.1.0 - Production   Time: 13-NOV-2017 05:03:18   Tracing not turned on.   Tns error struct:     ns main err code: 12535 TNS-12535: TNS:operation timed out     ns secondary err code: 12560     nt main err code: 505 TNS-00505: Operation timed out     nt secondary err code: 110     nt OS err code: 0   Client address: (ADDRESS=(PROTOCOL=tcp)(HOST=10.0.103.42)(PORT=54076))

Add Comment

BBCode format allowed
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA