ข้ามไปยังเนื้อหา

การตรวจจับ Dead Connection

Heartbeat จากบทเรียนที่แล้วส่งชีพจรออกไป แต่ชีพจรที่คุณส่งนั้นไร้ค่าหากคุณไม่ ตรวจสอบว่าคำตอบกลับมา บทเรียนนี้ว่าด้วยการตรวจสอบนั้น: วิธีเปลี่ยน “pong ไม่มาถึง” ให้กลายเป็นการตัดสินใจที่มั่นใจว่าจะประกาศว่า connection ตายแล้วรื้อทิ้ง

ความพยายามแรกที่ผิดและพบบ่อยคือการส่ง ping บน timer แล้วหยุดแค่นั้น นั่นไม่ตรวจจับอะไรเลย การตรวจจับทั้งหมดเกิดขึ้นใน ช่องว่างระหว่าง ping ที่คุณส่งกับ pong ที่คุณคาดหวัง Logic คือ:

  1. ส่ง ping จดไว้ว่าตอนนี้มี pong ค้าง อยู่กับคุณ
  2. เริ่ม deadline — เช่น interval ถัดไปหรือไม่กี่วินาที
  3. ถ้า pong ที่ตรงกันมาถึงก่อน deadline ดีแล้ว: เคลียร์หนี้ connection ยังมีชีวิต
  4. ถ้า deadline ผ่านไปโดยที่หนี้ยังค้างอยู่ แสดงว่าคุณมี missed pong ให้ถือว่า connection ตายแล้ว

ดังนั้นตัวแปรที่มีประโยชน์ที่สุดใน client ของคุณคือสิ่งที่คล้าย ๆ pongOutstanding (หรือจำนวน ping ที่ยังไม่ได้รับคำตอบ) พอค่านี้ข้ามเกณฑ์เมื่อไหร่ก็ลงมือได้ client ใน production ส่วนใหญ่ยอมให้ miss สักหนึ่งถึงสองครั้งก่อนประกาศการตาย — pong ที่หายไปครั้งเดียวอาจเป็นแค่อาการสะดุดชั่วขณะ แต่สามครั้งติดกันคือศพ

ตัวร้ายเบื้องหลังทั้งหมดนี้คือ connection แบบ half-open connection จะเป็น half-open เมื่อฝ่ายหนึ่งยังเชื่อว่าต่ออยู่ ส่วนอีกฝ่ายหายไปแล้วอย่างเงียบ ๆ ลองนึกภาพการโทรศัพท์ที่โทรศัพท์ของอีกฝ่ายดับไปกลางประโยค: คุณยังถือหูฟังอยู่ พูดอยู่ ไม่ได้ยินอะไรเลย โดยไม่มีเสียงสัญญาณบอกว่าสายตัดไปแล้ว

สิ่งนี้เกิดขึ้นตลอดเวลาในเน็ตเวิร์กจริง:

  • ฝาแลปท็อปปิด OS แช่แข็ง socket โดยไม่มีการ close อย่างสะอาด
  • อุปกรณ์มือถือสลับจาก Wi-Fi ไปเป็นเซลลูลาร์ ทิ้ง socket เก่าไป
  • NAT หรือ load balancer ขับ connection ที่ idle ออกจากตารางอย่างเงียบ ๆ

ในทุกกรณี ไม่มี event close ยิงขึ้น บนฝ่ายที่ยังอยู่รอด เพราะไม่มี FIN packet มาถึงเลยสักตัว WebSocket ของคุณจึงยังบอกว่า OPEN วิธี เดียว ที่จะรู้ความจริงคือส่งอะไรบางอย่างออกไปแล้วดูว่าไม่มีอะไรกลับมา — ซึ่งนั่นคือสิ่งที่ heartbeat บวก deadline มอบให้คุณพอดี

เมื่อตัดสินใจแล้วว่า connection ตาย ก็ไม่ต้องสุภาพ การ close() อย่างสะอาดจะพยายามทำ handshake กับ peer ที่ หายไปแล้ว — ไม่มีใครให้ handshake ด้วย จึงมีสิทธิ์ค้าง ให้ terminate แทน: ทิ้ง socket ที่ตายแล้วทันทีแล้วไป reconnect ตรง ๆ เบราว์เซอร์เปิดเผย close() ส่วน library ws ของ Node เพิ่ม terminate() มาเพื่อกรณีโหด ๆ แบบนี้โดยเฉพาะ Flow การตัดสินใจ:

stateDiagram-v2
  [*] --> Healthy
  Healthy --> Waiting: send ping, pong now owed
  Waiting --> Healthy: pong arrived in time
  Waiting --> Suspect: deadline passed, pong missed
  Suspect --> Healthy: a later pong arrived
  Suspect --> Dead: misses exceeded threshold
  Dead --> Terminate: abandon socket, do not handshake
  Terminate --> [*]: hand off to reconnect
การตัดสินใจว่า connection ตายแล้ว

เดโมด้านล่างใช้ socket ในหน้าที่มี WebSocket API จริง mock นี้ตอบ ping สองสามตัวแรกตามปกติ — แล้วก็เงียบไป จำลอง connection ที่ตายไปแล้วโดยไม่ยิง onclose (half-open connection) client รัน heartbeat ที่มี deadline และตั้งธงว่า connection ตายในวินาทีที่ pong เกินกำหนด

JavaScript

คุณจะเห็น ping/pong ที่แข็งแรงสองคู่ จากนั้น mock ก็เงียบไป client ส่งต่อไปเรื่อย ๆ จำนวน pong ที่ไม่ได้รับคำตอบไต่สูงขึ้น พอข้ามเกณฑ์ client ก็ประกาศว่า connection ตายแล้ว terminate ทิ้ง — โดยไม่ต้องรอ onclose ที่ไม่มีวันมา นั่นคือการตรวจจับ: คุณหยุดเชื่อความเงียบและลงมือกับคำตอบที่หายไป

Detection Methodความแม่นยำOverhead
Heartbeat + missed pong counterสูง — ตรวจจับ half-open ได้ส่ง ping ทุก interval
OS TCP keepaliveต่ำ — อาจใช้เวลาชั่วโมงไม่มี application code
Connection timeout บน serverปานกลางserver ต้องตรวจ idle time

รอ onclose Event สำหรับ Dead Connection อาการ:

  • half-open connection ไม่ยิง onclose — รอตลอดไป
  • server ถือ zombie connection โดยไม่รู้ตัว
  • ใช้ heartbeat + missed pong เพื่อตรวจจับ ไม่ต้องรอ event

Threshold สูงเกินไป — ตรวจจับช้า อาการ:

  • threshold 10 missed pong — รอนานเกินก่อนประกาศว่าตาย
  • UX แย่ — user รู้สึกว่า app ค้าง
  • threshold 2-3 missed pong เหมาะสมสำหรับส่วนใหญ่

💡 ตัวอย่างจากของจริง

Discord Gateway:

  • ถ้าไม่ได้รับ Heartbeat ACK — ส่ง non-resumable close code แล้ว reconnect ทันที
  • ไม่รอ TCP timeout

Cloudflare Durable Objects:

  • ตรวจสอบ WebSocket activity ทุก 60 วินาที
  • ถ้าไม่มี activity → close connection เพื่อประหยัด resource
Dead Connection ScenarioสาเหตุDetection Method
Client network dropwifi ขาด, mobile network เปลี่ยนHeartbeat timeout
Firewall/NAT timeoutidle connection ถูก reset โดยไม่แจ้งHeartbeat ไม่ได้ pong
Process crashbrowser tab close, app killOS ส่ง TCP RST — onclose fire
Server-side timeoutserver close idle connectionClose frame opcode

เชื่อ TCP Connected ว่า Application Connected อาการ:

  • TCP connection ยังอยู่แต่ data ไม่ผ่าน (half-open connection)
  • server ส่ง message ไปแต่ client ไม่ได้รับ — ไม่มี error
  • heartbeat ที่ application level จำเป็น — TCP status ไม่เพียงพอ

Zombie Connection ที่ Server ไม่รู้ อาการ:

  • client disconnect แบบ ungraceful (network cut, process kill)
  • server ยัง track connection อยู่ใน map — memory leak
  • ตั้ง heartbeat timeout: ถ้าไม่มี pong ใน N วินาที → remove และ close

💡 ตัวอย่างจากของจริง

AWS API Gateway WebSocket:

  • ตั้ง idle connection timeout — ปิด connection ที่ไม่มี message นาน 10 นาที
  • แอปต้องส่ง heartbeat ถ้าต้องการ connection ค้างนาน

nginx WebSocket proxy:

  • proxy_read_timeout 3600s — เพิ่ม timeout สำหรับ long-lived WebSocket connection
  • ถ้าไม่ตั้ง → nginx ปิด connection ทุก 60 วินาที (default)
อะไรคือสิ่งที่ตรวจจับ dead connection ได้จริง ๆ?
Half-open connection คืออะไร?
ทำไมต้อง terminate แทนการ close อย่างสะอาดสำหรับ connection ที่คุณประกาศว่าตายแล้ว?