TCP keepalive vs. application heartbeats
These get conflated constantly. They answer different questions.
Keepalive answers: is the socket still there
It is a kernel probe on an idle connection. The Linux defaults are useless for anything interactive:
$ sysctl net.ipv4.tcp_keepalive_time
net.ipv4.tcp_keepalive_time = 7200
Two hours before the first probe. Every NAT box between you and the peer will have dropped the mapping long before that, and neither end finds out until a write finally fails — possibly minutes later, possibly on the request that mattered.
Heartbeats answer: is the peer still working
This is the distinction people miss. Keepalive is handled by the kernel. A process that has deadlocked, or is stuck in a 40-second GC pause, or has lost its database connection and is failing every request — that process answers keepalives perfectly. The socket is fine. The service is not.
If you need to know the peer is alive, ask the kernel. If you need to know the peer is useful, you have to ask the application.
Picking
Keeping a NAT mapping warm: keepalive with sane timers, cheap and needs no code. Detecting a wedged peer: application heartbeat, on the same path your real requests take. Doing both is normal and not redundant.