← บล็อก

เรียลไทม์ในเอเชีย: ทำไมที่ตั้งของเซิร์ฟเวอร์ WebSocket จึงสำคัญ

· Hibira

ทำไมระยะทางจึงกำหนดค่าต่ำสุดของเวลาแฝงในการสื่อสารแบบเรียลไทม์ ค่าต่ำสุดจากฮ่องกงไปทั่วเอเชีย และวิธีวัดเวลาไปกลับของผู้ใช้ของคุณเอง

ข้อความแชต ราคาที่ขยับ หรือสัญลักษณ์ "กำลังพิมพ์" จะรู้สึกว่าเกิดขึ้นทันทีเมื่อมาถึงภายในประมาณหนึ่งในสิบวินาที เวลาส่วนใหญ่ในงบนั้นหมดไปกับเครือข่าย และเวลาเครือข่ายส่วนใหญ่ถูกกำหนดโดยสิ่งเดียวที่คุณเลือกได้ นั่นคือที่ตั้งของเซิร์ฟเวอร์ WebSocket

ระยะทางกำหนดค่าต่ำสุด

แสงในใยแก้วนำแสงเดินทางได้ราว 200 กม. ต่อมิลลิวินาที หรือประมาณสองในสามของความเร็วในสุญญากาศ การเดินทางไปกลับเซิร์ฟเวอร์ที่อยู่ห่าง 1,000 กม. จึงใช้เวลาอย่างน้อย 10 มิลลิวินาที ก่อนจะนับการกำหนดเส้นทาง การรอคิว หรือการประมวลผล สายเคเบิลจริงไม่ได้วางตามเส้นทางที่สั้นที่สุดบนผิวโลก และเราเตอร์จริงก็เพิ่มความล่าช้า เวลาไปกลับที่วัดได้จริงจึงมักเป็นสองถึงสี่เท่าของค่าต่ำสุดนี้ เราวัดจากฮ่องกงไปไทเปได้ราว 30 ms ขณะที่ค่าต่ำสุดคือ 8 ms

นี่คือค่าต่ำสุดจากฮ่องกง ที่ที่ Hibira ทำงานอยู่ในปัจจุบัน (AWS ap-east-1) ไปยังเมืองต่างๆ ในภูมิภาค:

ตัวเลขเหล่านี้มาจากฟิสิกส์ ไม่ใช่ผลทดสอบ ไม่มีเครือข่ายใดทำได้ดีกว่านี้ และเครือข่ายของคุณจะช้ากว่า แถวสุดท้ายมีไว้เพื่อเทียบขนาด บริการจำนวนมากในสหรัฐฯ รันโครงสร้างพื้นฐานเรียลไทม์ไว้ที่สหรัฐฯ ฝั่งตะวันออก รวมถึงคลัสเตอร์เริ่มต้นของ Pusher คือ mt1 Pusher มีคลัสเตอร์ในเอเชียด้วย หากคุณใช้ Pusher อยู่ ลองตรวจสอบว่าแอปของคุณสร้างไว้ในคลัสเตอร์ใด

ไทเป8 msมะนิลา11 msกรุงเทพฯ17 msโซล21 msสิงคโปร์26 msโตเกียว29 msจาการ์ตา33 msแอชเบิร์น (สหรัฐฯ ตะวันออก)131 ms
เวลาไป-กลับขั้นต่ำจากฮ่องกง ซึ่งกำหนดโดยความเร็วแสงในไฟเบอร์

ทำไมเรื่องนี้สำคัญกับเรียลไทม์มากกว่าหน้าเว็บ

หน้าเว็บเสียเวลาไปกลับเพียงไม่กี่ครั้งระหว่างโหลด แต่ฟีเจอร์เรียลไทม์เสียเวลานี้ในทุกการโต้ตอบ:

  • สถานะกำลังพิมพ์และเคอร์เซอร์ ส่งอีเวนต์ฝั่งไคลเอนต์หลายครั้งต่อวินาที เวลาไปกลับที่ช้าทำให้ตามผู้ใช้ไม่ทัน
  • แชต เดินทางจากผู้ใช้หนึ่งไปยังเซิร์ฟเวอร์ของคุณ ไปยังบริการ WebSocket แล้วออกไปถึงทุกคน ทุกช่วงเพิ่มระยะทางของตัวเอง
  • Presence (ใครออนไลน์อยู่) อัปเดตเมื่อสมาชิกเข้าและออก การอัปเดตที่ช้าทำให้เห็นคนเป็นออนไลน์ทั้งที่ออกไปแล้ว
  • การเชื่อมต่อใหม่ หลังโทรศัพท์สลับเครือข่ายต้องทำ TLS handshake และการสมัครรับข้อมูลใหม่ ซึ่งเป็นการไปกลับหลายครั้งติดกัน

นับทุกช่วง

เมื่อแบ็กเอนด์ของคุณเผยแพร่อีเวนต์ อีเวนต์จะเดินทาง:

  1. จากการกระทำของผู้ใช้ไปยังแบ็กเอนด์ของคุณ
  2. จากแบ็กเอนด์ของคุณไปยัง HTTP API ของบริการ WebSocket
  3. จากบริการ WebSocket ไปยังทุกไคลเอนต์ที่สมัครรับข้อมูล

ช่วงที่ 1 และ 3 ขึ้นอยู่กับว่าผู้ใช้ของคุณอยู่ที่ไหน ช่วงที่ 2 ขึ้นอยู่กับว่าแบ็กเอนด์ของคุณอยู่ที่ไหน แบ็กเอนด์ในโตเกียวที่เผยแพร่ผ่านบริการ WebSocket ในเวอร์จิเนีย จะข้ามมหาสมุทรแปซิฟิกสองครั้งสำหรับอีเวนต์ระหว่างผู้ใช้สองคนในไทเป วางบริการ WebSocket ไว้ใกล้ผู้ใช้ของคุณ และถ้าทำได้ ให้วางแบ็กเอนด์ไว้ใกล้บริการนั้นด้วย

ผู้ใช้ส่งข้อความ
①
แบ็กเอนด์ของคุณ
② HTTP API
บริการ WebSocket
③ WebSocket
ผู้ติดตามทุกคน
ทุกอีเวนต์ต้องผ่านสามช่วง ช่วง ① และ ③ ขึ้นกับว่าผู้ใช้อยู่ที่ไหน

วัดด้วยตัวเอง

สิ่งที่การทดสอบนี้วัด: pusher:ping/pusher:pong วัดเวลาไปกลับของ WebSocket ที่เชื่อมต่อแล้วกับบริการเรียลไทม์ ไม่ใช่เวลาตั้งแต่แบ็กเอนด์เผยแพร่จนสมาชิกได้รับข้อความ และไม่รวมการเชื่อมต่อครั้งแรก การกระจายข้อความ หรือการเรนเดอร์ของเบราว์เซอร์ ควรทดสอบหลายรอบจากเครือข่ายที่ผู้ใช้จริงใช้ รวมถึงมือถือ และดูค่ามัธยฐานกับ p95 เพราะ DNS, TLS, เส้นทางและความหนาแน่นของเครือข่ายมีผลต่อค่าเหล่านี้

ตารางเป็นเพียงจุดเริ่มต้น สิ่งที่สำคัญคือเครือข่ายของผู้ใช้ของคุณ โปรโตคอล Pusher มีข้อความ ping ที่เซิร์ฟเวอร์ตอบกลับทันที คุณจึงจับเวลาไปกลับได้โดยไม่ต้องเผยแพร่อะไร บันทึกโค้ดนี้เป็น ping.mjs แล้วรันด้วย Node.js 22 ขึ้นไป (ซึ่งมี WebSocket ในตัว):

// node ping.mjs YOUR_APP_KEY [host]
const [key, host = 'ws.hibira.io'] = process.argv.slice(2);
const ws = new WebSocket(`wss://${host}/app/${key}?protocol=7&client=js&version=8.4.0`);
const times = [];
let sent = 0;

const ping = () => {
  sent = performance.now();
  ws.send(JSON.stringify({ event: 'pusher:ping', data: {} }));
};

ws.onmessage = (msg) => {
  const { event } = JSON.parse(msg.data);
  if (event === 'pusher:pong') times.push(performance.now() - sent);
  if (event !== 'pusher:connection_established' && event !== 'pusher:pong') return;
  if (times.length < 20) return void setTimeout(ping, 250);
  times.sort((a, b) => a - b);
  console.log(`best ${times[0].toFixed(1)} ms, median ${((times[9] + times[10]) / 2).toFixed(1)} ms`);
  ws.close();
};

รันจากที่ที่ผู้ใช้ของคุณอยู่: คลาวด์เซิร์ฟเวอร์ในแต่ละประเทศที่คุณให้บริการ หรือแล็ปท็อปของเพื่อนร่วมงานที่ใช้อินเทอร์เน็ตบ้าน ค่ามัธยฐานคือตัวเลขที่ใช้เปรียบเทียบ ส่วนค่าดีที่สุดแสดงว่าเส้นทางเข้าใกล้ค่าต่ำสุดแค่ไหน เครือข่ายมือถือเพิ่มความล่าช้าของตัวเองเข้าไปอีก จึงควรทดสอบผ่านฮอตสปอตของโทรศัพท์ด้วย

หากต้องการมุมมองจากผู้ใช้จริง ให้จับเวลาที่ pusher-js ใช้ตั้งแต่ connecting จนถึง connected แล้วส่งไปยังระบบวิเคราะห์ของคุณพร้อมประเทศของผู้ใช้

สิ่งที่เรากำลังทำ

ปัจจุบัน Hibira ทำงานในฮ่องกงเพราะอยู่ใกล้ใจกลางของเอเชียตะวันออกและเอเชียตะวันออกเฉียงใต้ โตเกียวคือภูมิภาคถัดไปที่เรากำลังเตรียม จากนั้นจึงเป็นสิงคโปร์ แอปแต่ละแอปจะอยู่ในภูมิภาคเดียว คุณจึงเลือกภูมิภาคที่ใกล้ผู้ใช้ส่วนใหญ่ของคุณที่สุดได้

หากอยากเห็นตัวเลขของผู้ใช้ของคุณเองก่อนย้าย แพ็กเกจฟรี ก็เพียงพอสำหรับรันสคริปต์ด้านบนกับแอปของคุณ