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

4 พฤษภาคม 2569

วิศวกรรม

OpenAI สร้าง AI แบบเสียงที่มีความหน่วงต่ำและขยายรองรับผู้ใช้จำนวนมากได้อย่างไร

โดย Yi Zhang และ William McDonald สมาชิกทีมงานเทคนิค

เทคโนโลยี AI แบบเสียงจะฟังดูลื่นไหลก็ต่อเมื่อการโต้ตอบเกิดขึ้นไวเท่ากับการสนทนาจริงๆ หากเครือข่ายมีปัญหาผู้ใช้จะรับรู้ถึงความผิดปกติได้ทันทีว่าจังหวะการหยุดพูดฟังแปลกๆ ถูกขัดจังหวะ หรือพูดแทรกแล้วดีเลย์ เรื่องนี้เป็นประเด็นสำคัญสำหรับระบบเสียงของ ChatGPT สำหรับฝั่งนักพัฒนาที่ใช้งาน Realtime API สำหรับตัวเอเจนต์ในระบบเวิร์กโฟลว์ที่ต้องโต้ตอบกัน และสำหรับโมเดลต่างๆ ที่ต้องคอยประมวลผลเสียงไปพร้อมๆ กับตอนที่ผู้ใช้กำลังพูด

ในระดับสเกลของ OpenAI สิ่งนี้จึงตีความได้เป็นข้อกำหนดที่เป็นรูปธรรม 3 ประการ ดังนี้

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

ทีมงาน OpenAI ที่ดูแลด้านการโต้ตอบของ AI แบบเรียลไทม์ได้ปรับปรุงโครงสร้าง WebRTC ใหม่ เพื่อแก้ปัญหาข้อจำกัด 3 ประการได้แก่ การรับส่งข้อมูลสื่อสารแบบพอร์ตเดียวต่อหนึ่งเซสชันไม่เหมาะกับโครงสร้างพื้นฐานของ OpenAI ปัญหาเรื่องความเสถียรของระบบสร้างการเชื่อมต่อ (ICE) และการเข้ารหัสความปลอดภัยข้อมูล (DTLS) และการลดความหน่วงในการรับส่งข้อมูลรอบโลก บทความนี้จะนำเสนอโครงสร้างสถาปัตยกรรมแบบการใช้ระบบส่วนส่งต่อข้อมูลแยกจากส่วนรับส่งสัญญาณ (Transceiver) ซึ่งแนวทางนี้ช่วยให้ระบบยังทำงานร่วมกับไคลเอนต์ผ่านมาตรฐาน WebRTC ได้อย่างสมบูรณ์ ในขณะเดียวกันก็ปรับเปลี่ยนวิธีการส่งผ่านข้อมูลภายในระบบโครงสร้างพื้นฐานของ OpenAI

WebRTC ช่วยให้เราสร้างผลิตภัณฑ์ AI แบบเรียลไทม์ได้

WebRTC เป็นมาตรฐานเปิดสำหรับรับส่งข้อมูลเสียง วิดีโอ และข้อมูลทั่วไปที่มีความหน่วงต่ำระหว่างเบราว์เซอร์ แอปมือถือ และเซิร์ฟเวอร์ แม้มักถูกใช้ในการสื่อสารแบบตัวต่อตัว แต่เทคโนโลยีนี้ยังเป็นรากฐานที่ใช้งานได้จริงสำหรับระบบเรียลไทม์ระหว่างไคลเอนต์และเซิร์ฟเวอร์ เพราะช่วยสร้างมาตรฐานให้กับส่วนที่ยากที่สุดของการสื่อสารผ่านสื่อโต้ตอบ ทั้งการสร้างการเชื่อมต่อผ่าน NAT (Network Address Translation) การเข้ารหัสข้อมูลเพื่อความปลอดภัย (DTLS/SRTP) การจัดรูปแบบบีบอัดเสียง การควบคุมคุณภาพการรับส่งข้อมูล (RTCP: Real-time Transport Control Protocol) ไปจนถึงฟีเจอร์จัดการเสียงอย่างการตัดเสียงสะท้อนและการลดความกระตุกของสัญญาณ

การสร้างมาตรฐานดังกล่าวมีความสำคัญอย่างยิ่งต่อผลิตภัณฑ์ AI เพราะหากไม่มี WebRTC ไคลเอนต์แต่ละรายคงต้องหาทางออกที่ต่างกันในการสร้างการเชื่อมต่อผ่าน NAT การเข้ารหัสสื่อ การเเลือกใช้คอมเพรสเซอร์ (ชุดรหัสสำหรับส่งและถอดรหัสข้อมูล) รวมถึงการปรับตัวตามสภาพเครือข่ายที่เปลี่ยนแปลง แต่เมื่อมี WebRTC เราจึงสามารถต่อยอดจากชุดโปรโตคอลที่มีการใช้งานแพร่หลายอยู่แล้วทั้งในเบราว์เซอร์และแพลตฟอร์มมือถือ เพื่อทุ่มเททรัพยากรไปกับการพัฒนาโครงสร้างพื้นฐานที่เชื่อมต่อสื่อแบบเรียลไทม์เข้ากับโมเดล AI โดยเฉพาะ

เราต่อยอดจากระบบนิเวศของ WebRTC ทั้งในส่วนของซอฟต์แวร์โอเพนซอร์สที่เสถียรแล้ว และการทำงานร่วมกันตามมาตรฐานที่ช่วยให้เบราว์เซอร์ แอปมือถือ และเซิร์ฟเวอร์ทำงานร่วมกันได้อย่างไร้รอยต่อ ผลงานพื้นฐานของ Justin Uberti (หนึ่งในผู้ออกแบบ WebRTC รุ่นบุกเบิก) และ Sean DuBois (ผู้สร้างและผู้ดูแล Pion) ช่วยให้ทีมงานอย่างเราสร้างระบบบนโครงสร้างพื้นฐานสื่อที่ผ่านการพิสูจน์การใช้งานจริงมาอย่างโชกโชน แทนที่จะต้องเสียเวลาสร้างระบบรับส่งข้อมูล การเข้ารหัส และการควบคุมความหนาแน่นของเน็ตเวิร์กขึ้นมาใหม่ทั้งหมด เราโชคดีมากที่ทั้ง Justin และ Sean มาร่วมงานกับเราที่ OpenAI เพื่อช่วยวางแนวทางในการผสาน WebRTC เข้ากับ AI แบบเรียลไทม์ให้ใกล้ชิดกันยิ่งขึ้น

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

การเลือกสถาปัตยกรรมสื่อ

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

ตัวเลือกที่ 1: แนวทางแบบ SFU ที่กำหนดให้ AI เข้าร่วมในฐานะผู้ใช้งาน (Participant) คนหนึ่งในเซสชัน WebRTC

SFU หรือ Selective Forwarding Unit คือเซิร์ฟเวอร์กระจายสัญญาณสื่อที่รับข้อมูล WebRTC จากผู้ใช้งานรายบุคคลแล้วเลือกส่งต่อไปยังผู้รับคนอื่นๆ ในโมเดลนี้ระบบนี้จะให้ SFU ดูแลการยุติการเชื่อมต่อ WebRTC ของผู้เข้าร่วมทุกคนแยกกัน และดึง AI เข้ามาร่วมในเซสชันเหมือนเป็นผู้ใช้งานคนหนึ่ง ซึ่งตอบโจทย์บริการที่เน้นการใช้งานหลายคน เช่น การคุยกลุ่มหรือการเรียนออนไลน์ เพราะระบบสามารถควบคุมทั้งเรื่องการบีบอัดเสียง การบันทึก และการตั้งค่าของแต่ละสตรีมได้จากจุดเดียว1

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

ตัวเลือก 2: แนวทางแบบ Transceiver ทำการยุติโปรโตคอล WebRTC ณ จุดบริการส่วนหน้า และแปลงไปเป็นโปรโตคอลสำหรับระบบ Backend

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

ในการออกแบบนี้ Transceiver จะเป็นบริการเพียงหนึ่งเดียวที่ถือครองสถานะของเซสชัน WebRTC ทั้งหมด ซึ่งรวมถึงการตรวจสอบการเชื่อมต่อของ ICE, การทำ DTLS Handshake, กุญแจเข้ารหัส SRTP และวงจรชีวิตของเซสชัน โดย "การยุติการเชื่อมต่อ" ในที่นี้หมายถึงการที่ Transceiver เป็นจุดปลายทางที่ทำ Handshake จนเสร็จสมบูรณ์ รวมถึงเข้ารหัสและถอดรหัสข้อมูลสื่อ การรวมสถานะไว้ในที่เดียวช่วยให้จัดการความเป็นเจ้าของเซสชันได้ง่ายขึ้น และทำให้บริการส่วนหลังสามารถขยายตัวได้เหมือนบริการทั่วไปแทนที่จะต้องทำหน้าที่เป็นจุดเชื่อมต่อ WebRTC ด้วยตัวเอง

ปัญหาหลักในการติดตั้งระบบ: เมื่อ WebRTC ต้องทำงานร่วมกับ Kubernetes

หลังจากตัดสินใจเลือกโมเดล Transceiver แล้ว เราเริ่มพัฒนาระบบขั้นแรกด้วยบริการภาษา Go เพียงหนึ่งเดียวที่สร้างขึ้นบน Pion ซึ่งทำหน้าที่จัดการทั้งการส่งสัญญาณ และการยุติการเชื่อมต่อสื่อ โดยระบบนี้เป็นขุมพลังเบื้องหลังของทั้ง ChatGPT Voice จุดเชื่อมต่อ WebRTC ของ Realtime API และโครงการวิจัยอื่นๆ อีกมากมาย

ในเชิงปฏิบัติการ Transceiver นี้มีหน้าที่หลัก 2 อย่างด้วยกัน

  • การส่งสัญญาณ: ทำหน้าที่จัดการ SDP เลือก Codec, ยืนยัน ICE และตั้งค่าเซสชัน
  • ด้านข้อมูลสื่อ: ทำหน้าที่ยุติการเชื่อมต่อ WebRTC จากฝั่งผู้ใช้งาน และเชื่อมโยงสัญญาณต่อไปยังระบบประมวลผล AI และส่วนควบคุมการทำงานที่อยู่เบื้องหลัง

เราต้องการให้บริการนี้ทำงานเหมือนกับโครงสร้างพื้นฐานส่วนอื่นๆ ของเรา นั่นคือการรันบน Kubernetes ที่สามารถปรับเพิ่มลดเวิร์กโหลดและย้ายข้ามโฮสต์ตามความต้องการได้แต่โมเดล WebRTC แบบหนึ่งพอร์ตต่อหนึ่งเซสชันทั่วไปนั้นทำงานร่วมกับสภาพแวดล้อมนี้ได้ยาก เนื่องจากต้องใช้ช่วงพอร์ต UDP สาธารณะขนาดใหญ่ ซึ่งยากต่อการเปิดใช้งาน ซึ่งยากต่อการเปิดใช้งาน การรักษาความปลอดภัย และการคงสถานะพอร์ตเอาไว้เมื่อมีการเพิ่ม ลบ หรือจัดสรรตำแหน่งพ็อดใหม2

พอร์ตไม่เพียงพอต่อการใช้งาน

ปัญหาแรกเริ่มมาจากตัวโมเดลที่ต้องใช้หนึ่งพอร์ตต่อหนึ่งการเชื่อมต่อ ซึ่งหากมีการใช้งานหนาแน่น จะต้องคอยเปิดและดูแลช่วงพอร์ต UDP ที่มีขนาดกว้างมาก

  • ระบบ Load Balancer บนคลาวด์และ Kubernetes ไม่ได้ถูกสร้างมาให้จัดการพอร์ต UDP ทีละหลายหมื่นพอร์ต เพราะยิ่งเพิ่มช่วงพอร์ตมากเท่าไร การจัดการก็ยิ่งซับซ้อนขึ้น ทั้งเรื่องการเซตค่า Load Balancer การเช็กสถานะระบบ การคุมความปลอดภัยผ่านไฟร์วอลล์ และความปลอดภัยในการติดตั้งระบบในแต่ละรอบ3
  • ช่วงพอร์ต UDP ที่กว้างมากนั้นสร้างอุปสรรคต่อการรักษาความปลอดภัย พราะเท่ากับเป็นการขยายจุดเสี่ยงให้ภายนอกโจมตีได้มากขึ้น และทำให้การไล่ตรวจสอบกฎความปลอดภัยของเครือข่ายทำได้ลำบาก
  • ช่วงพอร์ตที่กว้างเกินไปยังไม่สอดคล้องกับการขยายระบบอัตโนมัติอีกด้วย เนื่องจาก Kubernetes มักจะเพิ่ม ลบ และจัดสรรตำแหน่งของพ็อดอยู่ตลอดเวลา การกำหนดให้ทุกพ็อดต้องคอยจองพอร์ตคงที่ขนาดใหญ่ ส่งผลให้ระบบเสียความยืดหยุ่นและทำให้การปรับขนาดทำได้ยาก4

ด้วยเหตุนี้ระบบ WebRTC จำนวนมากจึงเปลี่ยนมาใช้พอร์ต UDP เพียงพอร์ตเดียวต่อหนึ่งเซิร์ฟเวอร์ แล้วจึงใช้การแยกสัญญาณในระดับแอปพลิเคชันอยู่เบื้องหลังพอร์ตนั้นเพื่อจัดการข้อมูล5

การรักษาความต่อเนื่องของสถานะ

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

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

โจทย์นี้ทำให้เราได้เป้าหมายที่ชัดเจน นั่นคือการเปิดพื้นที่พอร์ต UDP สู่สาธารณะเพียงช่วงสั้นๆ และคงที่ แต่ในขณะเดียวกันต้องสามารถส่งต่อทุกแพ็กเก็ตไปยัง Transceiver ที่เป็นเจ้าของเซสชัน WebRTC นั้นๆ ได้อย่างถูกต้อง

การเปรียบเทียบสถาปัตยกรรมด้านสื่อของ WebRTC

เราประเมินวิธีการต่างๆ ไว้หลายทาง ซึ่งรวมถึงการใช้ TURN (Traversal Using Relays around NAT) โดยให้ Edge Relay เป็นตัวจัดการการจัดสรรทรัพยากรของไคลเอนต์ และทำหน้าที่ส่งต่อข้อมูลแทนผู้ใช้งานเหล่านั้น2

Approach

Pros

Cons

Unique IP:port per session (also known as native direct UDP)

Direct client-to-server media path

No forwarding layer in the data path

Requires one public UDP port per session

Large port ranges are difficult to expose and secure

Poor fit for Kubernetes and cloud load balancers

Unique IP:port per server

Much smaller public UDP footprint than per-session exposure

One shared socket per server can demultiplex many sessions

Works cleanly on a single host, but not across a shared load-balanced fleet by itself

Session demultiplexing on a single host only helps after a packet reaches that host; across a load-balanced fleet, the first packet can still land on the wrong instance, so you still need a deterministic way to steer each session to the process that owns it


TURN relay (protocol-terminating)

Clients only need to reach the TURN relay address and port

Can centralize policy at the edge

TURN allocations add setup round trips

Moving or recovering allocations across TURN servers is still difficult

Stateless forwarder + stateful terminator (OpenAI’s relay + transceiver)

Small public UDP footprint

Transceiver still owns the full WebRTC session

Adds one forwarding hop before media reaches the owning transceiver

Requires custom coordination between relay and transceiver

ภาพรวมสถาปัตยกรรม: Relay + Transceiver

สถาปัตยกรรมที่เรานำมาใช้งานจริงนั้นแยกส่วนการจัดเส้นทางแพ็กเก็ตออกจากการสิ้นสุดโปรโตคอล โดยที่สัญญาณควบคุมยังคงส่งไปถึง Transceiver เพื่อตั้งค่าเซสชัน ในขณะที่ข้อมูลสื่อจะไหลผ่านตัวรีเลย์ (Relay) เป็นด่านแรก ซึ่งรีเลย์นี้ทำหน้าที่เป็นชั้นส่งต่อข้อมูล UDP ที่ทำงานได้รวดเร็วและใช้พอร์ตสาธารณะเพียงเล็กน้อย ส่วน Transceiver ที่อยู่เบื้องหลังจะรับหน้าที่เป็นจุดสิ้นสุด WebRTC ที่คอยรักษาสถานะการเชื่อมต่อ

รีเลย์ทำหน้าที่ส่งต่อแพ็กเกจไปยัง Transceiver แบบไร้สถานะ

รีเลย์จะไม่ทำหน้าที่ถอดรหัสสื่อ ไม่รันสถานะ ICE หรือเข้าจัดการ Codec มันทำเพียงแค่อ่านข้อมูลเบื้องต้นของแพ็กเก็ตเพื่อหาทางไปต่อ จากนั้นจึงส่งต่อแพ็กเก็ตไปยัง Transceiver ที่เป็นเจ้าของเซสชันนั้นๆ ซึ่ง Transceiver จะยังคงมองเห็นลำดับข้อมูล WebRTC ตามปกติและเป็นเจ้าของสถานะโปรโตคอลทั้งหมด ส่วนในมุมของผู้ใช้งานนั้นจะไม่มีอะไรในเซสชัน WebRTC ที่เปลี่ยนแปลงไป

การกำหนดเส้นทางด้วยข้อมูลรับรอง ICE

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

ทุกเซสชันของ WebRTC มีกลไกการนำทางข้อมูลภายในตัวโปรโตคอลอยู่แล้ว นั่นคือ ICE Username Fragment หรือ ufrag ซึ่งเป็นตัวระบุสั้นๆ ที่ระบบจะแลกเปลี่ยนกันในช่วงตั้งค่าเซสชัน และปรากฏซ้ำในการตรวจสอบการเชื่อมต่อของ STUN เราเลยออกแบบให้ ufrag ฝั่งเซิร์ฟเวอร์เก็บข้อมูลเส้นทางเอาไว้ในตัว เพื่อให้รีเลย์รู้ได้ทันทีว่าต้องส่งแพ็กเก็ตไปที่คลัสเตอร์ไหนและ Transceiver ตัวใด

แผนภาพลำดับแสดงวิธีการสร้างการเชื่อมต่อ

ในระหว่างขั้นตอนส่งสัญญาณ Transceiver จะจัดสรรสถานะของเซสชันและส่งที่อยู่ปลายทางร่วมกับพอร์ต UDP กลับมาในคำตอบ SDP โดยที่อยู่ IP เสมือนที่ทำหน้าที่เป็นหน้าด่านให้กับกลุ่มรีเลย์ เมื่อใช้งานร่วมกับพอร์ตแล้วจะช่วยให้ไคลเอนต์มีปลายทางที่คงที่เพียงจุดเดียว เช่น 203.0.113.10:3478 แม้ว่าจะมีรีเลย์ทำงานอยู่เบื้องหลังเป็นจำนวนมากก็ตาม โดยปกติแล้วข้อมูลชิ้นแรกที่ส่งมาจะเป็นคำร้องขอตรวจสอบเส้นทาง หรือ STUN (Session Traversal Utilities for NAT) เพื่อให้ระบบแน่ใจว่าข้อมูลจะสามารถวิ่งไปถึงที่อยู่ที่แจ้งไว้ได้จริง

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

เซสชันของรีเลย์ถูกออกแบบให้มีขนาดเล็กที่สุด โดยประกอบด้วยหน่วยความจำในตัวเพื่อระบุเส้นทางการส่งต่อแพ็กเกจ พร้อมด้วยตัวนับสำหรับการมอนิเตอร์และตัวจับเวลาเพื่อสิ้นสุดเซสชัน การออกแบบนี้ช่วยให้การกำหนดเส้นทางแพ็กเกจเกิดขึ้นโดยตรงบนเส้นทางข้อมูล หากรีเลย์เริ่มต้นใหม่และสูญเสียเซสชัน แพ็กเกจ STUN ถัดไปจะสร้างเซสชันขึ้นใหม่จากข้อมูล ufrag และเพื่อให้เสถียรยิ่งขึ้นเราได้ใช้ระบบความจำส่วนกลางมาช่วยจดจำเส้นทางที่เคยเชื่อมต่อไว้แล้วอย่าง <IP ของไคลเอนต์ + พอร์ต, Transceiver IP + พอร์ต> ระบบนี้ช่วยให้กู้คืนข้อมูลได้รวดเร็วก่อนที่แพ็กเกจ STUN ถัดไปจะมาถึง

เครือข่ายรีเลย์ทั่วโลกและการส่งสัญญาณควบคุมตามตำแหน่งทางภูมิศาสตร์

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

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

ชั้นการทำงาน Global Relay จะคอยรับข้อมูลจากไคลเอนต์เพื่อส่งต่อให้กลุ่มเครื่องรับส่งสัญญาณประมวลผลต่อ

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

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

การปรับใช้รีเลย์และประสิทธิภาพการทำงานของระบบ

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

การตัดสินใจหลักในการออกแบบ

  • ไม่มีการยกเลิกโปรโตคอลกลางทาง: รีเลย์จะอ่านเฉพาะส่วนหัวของข้อมูล STUN และรหัสชื่อผู้ใช้เท่านั้น หลังจากนั้นจะใช้ข้อมูลเส้นทางที่บันทึกไว้สำหรับ DTLS, RTP และ RTCP โดยไม่เปิดอ่านหรือแก้ไขใดๆ
  • การจัดเก็บข้อมูลชั่วคราว: ระบบจะจำข้อมูลแผนผังเส้นทางจากที่อยู่ของผู้ใช้งานไปยัง Transceiver ปลายทางไว้เพียงเล็กน้อยในหน่วยความจำและกำหนดเวลาหมดอายุไว้สั้นมาก เพื่อใช้ในการนำทางข้อมูลและตรวจสอบสถานะการทำงานของระบบเท่านั้น
  • การขยายแนวนอน: เราสามารถเพิ่มรีเลย์หลายตัวให้ทำงานขนานกันเพื่อช่วยกันรับงานได้ เนื่องจากข้อมูลที่เก็บไว้ไม่ใช่สถานะการเชื่อมต่อหลักของ WebRTC การเริ่มระบบใหม่จึงส่งผลกระทบต่อการรับส่งข้อมูลน้อยมาก และสามารถกลับมาส่งต่อข้อมูลได้อย่างรวดเร็ว

มาตรการด้านประสิทธิภาพ

  • SO_REUSEPORT คือตัวเลือกการตั้งค่าช่องทางเชื่อมต่อในระบบ Linux ที่อนุญาตให้รีเลย์ หลายตัวในเครื่องเดียวกันสามารถจองใช้พอร์ต UDP เดียวกันได้ จากนั้นระบบปฏิบัติการจะทำหน้าที่กระจายแพ็กเก็ตข้อมูลที่วิ่งเข้ามาไปให้แต่ละตัวช่วยกันจัดการ วิธีนี้ช่วยแก้ปัญหาเรื่องข้อมูลมากองรวมกันจนทำให้อ่านข้อมูลไม่ทันเพียงจุดเดียว
  • runtime.LockOSThread จะช่วยยึดชุดคำสั่งที่ทำหน้าที่อ่านข้อมูล UDP แต่ละชุดไว้กับเธรดของระบบปฏิบัติการโดยเฉพาะ เมื่อใช้งานร่วมกับ SO_REUSEPORT ข้อมูลที่ส่งมาจากแหล่งเดิมก็จะวิ่งไปหา CPU ตัวเดิมตลอด วิธีนี้ช่วยให้ระบบไม่ต้องเสียเวลาสลับไปมาและสามารถเรียกใช้ข้อมูลที่บันทึกไว้ในหน่วยความจำความเร็วสูงได้ทันที ทำให้เครื่องทำงานได้ลื่นไหลขึ้นมาก
  • การเตรียมบัฟเฟอร์ไว้ล่วงหน้าและการลดขั้นตอนการคัดลอกข้อมูล ช่วยให้ระบบไม่ต้องเสียเวลาประมวลผลใหม่ให้ยุ่งยาก และเลี่ยงไม่ให้ภาษา Go ต้องมาคอยเคลียร์ขยะในหน่วยความจำบ่อยๆ

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

ผลลัพธ์และบทเรียนที่ได้

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

บทเรียนสำคัญที่เราได้รับคือ การเพิ่มความซับซ้อนไว้ในชั้นการนำทางข้อมูลที่บางเบาเพียงจุดเดียว ย่อมดีกว่าการกระจายความซับซ้อนนั้นไปไว้ในระบบเบื้องหลังหรือกำหนดพฤติกรรมเฉพาะให้ฝั่งผู้ใช้งาน การใส่ข้อมูลเส้นทางลงในฟิลด์มาตรฐานของโปรโตคอลช่วยให้เรากำหนดทิศทางการวิ่งของข้อมูลในก้าวแรกได้อย่างแม่นยำ ช่วยลดพื้นที่การรับส่งข้อมูลผ่าน UDP และมีความยืดหยุ่นเพียงพอที่จะวางจุดรับข้อมูลไว้ใกล้กับผู้ใช้งานทั่วโลกได้

ประเด็นหลักที่เราเลือกให้ความสำคัญเป็นพิเศษ

  • รักษาความหมายของโปรโตคอลไว้ที่ ฝั่งผู้ใช้งานยังคงใช้มาตรฐาน WebRTC แบบปกติ ซึ่งช่วยให้การทำงานร่วมกับเบราว์เซอร์และแอปพลิเคชันบนมือถือยังคงสมบูรณ์และใช้งานได้ทันที
  • เก็บสถานะการเชื่อมต่อหลักไว้ที่จุดเดียวโดยให้ Transceiver เป็นผู้ดูแลทั้งระบบการเชื่อมต่อ (ICE), ความปลอดภัย (DTLS/SRTP) และวงจรของเซสชันทั้งหมด ส่วนระบบรีเลย์จะทำหน้าที่เพียงแค่ส่งต่อแพ็กเก็ตข้อมูลเท่านั้น
  • เลือกเส้นทางจากข้อมูลที่มีอยู่แล้วตอนเริ่มการเชื่อมต่อ รหัสชื่อผู้ใช้ (ufrag) ของโปรโตคอล ICE ช่วยให้เรากำหนดทิศทางการส่งข้อมูลได้ตั้งแต่แรก โดยไม่ต้องเสียเวลาดึงข้อมูลเพิ่มเติมจากระบบอื่นในระหว่างที่กำลังส่งรับข้อมูลความเร็วสูง
  • เพิ่มประสิทธิภาพให้ถึงขีดสุดก่อนจะหันไปใช้เทคนิคที่ซับซ้อน ปรับแต่งโค้ดภาษา Go อย่างละเอียดร่วมกับการใช้ SO_REUSEPORT และการจัดการเธรดอย่างมีประสิทธิภาพ ช่วยให้การประมวลผลข้อมูลทำได้รวดเร็วตตามต้องการ โดยไม่จำเป็นต้องข้ามขั้นตอนการทำงานของชุดคำสั่งหลักของระบบ

ระบบ AI สำหรับเสียงแบบเรียลไทม์จะทำงานได้อย่างสมบูรณ์ก็ต่อเมื่อโครงสร้างพื้นฐานสามารถลดความหน่วงจนผู้ใช้ไม่รู้สึก สำหรับเรา นั่นหมายถึงการปรับเปลี่ยนรูปแบบการติดตั้งใช้งาน WebRTC ใหม่ โดยยังคงรักษามาตรฐานการทำงานที่ไคลเอนต์คาดหวังจาก WebRTC ไว้เช่นเดิม