OpenAI কীভাবে বড় পরিসরে কম-লেটেন্সির ভয়েস AI সরবরাহ করে
ই ঝাং এবং উইলিয়াম ম্যাকডোনাল্ড, টেকনিক্যাল স্টাফ সদস্যদের লেখা
ভয়েস AI তখনই স্বাভাবিক লাগে, যখন কথোপকথন কথার গতিতেই এগোয়. নেটওয়ার্ক বাধা হয়ে দাঁড়ালে, মানুষ তা সঙ্গে সঙ্গে অস্বস্তিকর বিরতি, মাঝপথে কাটা বাধা, বা দেরিতে barge-in হিসেবে টের পায়. এটি ChatGPT ভয়েসের জন্য গুরুত্বপূর্ণ, Realtime API দিয়ে তৈরি করা ডেভেলপারদের জন্য গুরুত্বপূর্ণ, ইন্টারঅ্যাকটিভ ওয়ার্কফ্লোতে কাজ করা এজেন্টদের জন্য গুরুত্বপূর্ণ, এবং এমন মডেলগুলোর জন্যও গুরুত্বপূর্ণ যেগুলোকে ব্যবহারকারী এখনও কথা বলার সময় অডিও প্রক্রিয়া করতে হয়.
OpenAI-এর পরিসরে, এর অর্থ দাঁড়ায় তিনটি নির্দিষ্ট প্রয়োজনীয়তা:
- সাপ্তাহিক ৯০০ মিলিয়নেরও বেশি সক্রিয় ব্যবহারকারীর জন্য বৈশ্বিক পরিসর
- দ্রুত সংযোগ স্থাপন, যাতে সেশন শুরু হওয়ার সঙ্গে সঙ্গেই ব্যবহারকারী কথা বলা শুরু করতে পারেন
- কম এবং স্থিতিশীল মিডিয়া রাউন্ড-ট্রিপ সময়, কম জিটার এবং প্যাকেট লসসহ, যাতে পালাক্রমে কথা বলা ঝরঝরে লাগে
OpenAI-তে রিয়েল-টাইম AI ইন্টারঅ্যাকশনের দায়িত্বে থাকা দলটি সম্প্রতি আমাদের WebRTC স্ট্যাক নতুনভাবে আর্কিটেক্ট করেছে, যাতে বড় পরিসরে একসঙ্গে সংঘর্ষে জড়াতে শুরু করা তিনটি সীমাবদ্ধতা সমাধান করা যায়: প্রতি সেশনে একটি পোর্টে মিডিয়া টার্মিনেশন OpenAI অবকাঠামোর সঙ্গে ভালোভাবে মানায় না, stateful ICE (Interactive Connectivity Establishment) এবং DTLS (Datagram Transport Layer Security) সেশনের স্থিতিশীল মালিকানা দরকার, এবং বৈশ্বিক রাউটিংকে প্রথম-হপ লেটেন্সি কম রাখতে হয়. এই পোস্টে, আমরা দেখাচ্ছি আমাদের তৈরি split relay plus transceiver আর্কিটেকচার, যা ক্লায়েন্টদের জন্য মানসম্মত WebRTC আচরণ বজায় রাখে, একই সঙ্গে OpenAI অবকাঠামোর ভেতরে প্যাকেট কীভাবে রাউট হয় তা বদলে দেয়.
WebRTC হলো ব্রাউজার, মোবাইল অ্যাপ এবং সার্ভারের মধ্যে কম-লেটেন্সির অডিও, ভিডিও ও ডেটা পাঠানোর একটি উন্মুক্ত মান. এটি প্রায়ই peer-to-peer কলিংয়ের সঙ্গে যুক্ত, তবে client-to-server রিয়েল-টাইম সিস্টেমের জন্যও এটি একটি কার্যকর ভিত্তি, কারণ এটি ইন্টারঅ্যাকটিভ মিডিয়ার কঠিন অংশগুলোকে মানসম্মত করে: সংযোগ স্থাপন ও NAT (Network Address Translation) traversal-এর জন্য ICE, এনক্রিপ্টেড ট্রান্সপোর্টের জন্য DTLS এবং SRTP (Secure Real-time Transport Protocol), অডিও কমপ্রেস ও ডিকোড করার জন্য codec negotiation, মান নিয়ন্ত্রণের জন্য RTCP (Real-time Transport Control Protocol), এবং echo cancellation ও jitter buffering-এর মতো client-side বৈশিষ্ট্য.
এই মানসম্মতকরণ AI পণ্যের জন্য গুরুত্বপূর্ণ. WebRTC না থাকলে, NAT পেরিয়ে সংযোগ কীভাবে স্থাপন করতে হবে, মিডিয়া কীভাবে এনক্রিপ্ট করতে হবে, codec কীভাবে negotiate করতে হবে (প্রেরণ ও decompression-এর জন্য নির্বাচিত coder-decoder), এবং পরিবর্তিত নেটওয়ার্ক পরিস্থিতির সঙ্গে কীভাবে মানিয়ে নিতে হবে—এসবের জন্য প্রতিটি ক্লায়েন্টের আলাদা সমাধান দরকার হতো. WebRTC থাকায়, আমরা এমন একটি protocol stack-এর ওপর নির্মাণ করতে পারি যা ব্রাউজার ও মোবাইল প্ল্যাটফর্মে আগে থেকেই বাস্তবায়িত, ফলে আমাদের নিজস্ব কাজকে রিয়েল-টাইম মিডিয়াকে মডেলের সঙ্গে সংযুক্ত করা অবকাঠামোর দিকে কেন্দ্রীভূত করতে পারি.
আমরা WebRTC ইকোসিস্টেমের ওপরও নির্মাণ করি, যার মধ্যে রয়েছে পরিণত open-source implementation এবং সেই মান-সংক্রান্ত কাজ, যা ব্রাউজার, মোবাইল অ্যাপ ও সার্ভারকে আন্তঃপরিচালনযোগ্য রাখে. Justin Uberti (WebRTC-এর মূল আর্কিটেক্টদের একজন) এবং Sean DuBois (Pion-এর নির্মাতা ও রক্ষণাবেক্ষণকারী)-এর মৌলিক কাজ আমাদের মতো দলগুলোর জন্য পরীক্ষিত মিডিয়া অবকাঠামোর ওপর নির্মাণ করা সম্ভব করেছে, যাতে নিম্নস্তরের ট্রান্সপোর্ট, এনক্রিপশন ও congestion-control আচরণ নতুন করে উদ্ভাবন করতে না হয়. সৌভাগ্যবশত Justin ও Sean এখন OpenAI-তে আমাদের সহকর্মী, এবং WebRTC ও রিয়েল-টাইম AI-কে কীভাবে আরও কাছাকাছি আনা যায়, সে বিষয়ে দিকনির্দেশনা দিচ্ছেন.
AI-এর জন্য সবচেয়ে গুরুত্বপূর্ণ বৈশিষ্ট্য হলো অডিও একটি ধারাবাহিক স্ট্রিম হিসেবে পৌঁছায়. একটি স্পোকেন এজেন্ট ব্যবহারকারী কথা বলার সময়ই transcription শুরু করতে, যুক্তি করতে, টুল কল করতে, বা বক্তৃতা তৈরি করতে পারে, সম্পূর্ণ আপলোডের জন্য অপেক্ষা না করে. এটাই সেই পার্থক্য, যা একটি সিস্টেমকে কথোপকথনমূলক মনে করায় এবং আরেকটিকে push-to-talk-এর মতো মনে করায়.
WebRTC বেছে নেওয়ার পর, পরবর্তী প্রশ্ন ছিল এটিকে কোথায় terminate করা হবে (যেখানে আমরা WebRTC সংযোগ গ্রহণ ও নিজের নিয়ন্ত্রণে রাখব—যেমন edge-এ) এবং সেই সেশনগুলোকে inference backend-এর সঙ্গে কীভাবে সংযুক্ত করা হবে. Termination গুরুত্বপূর্ণ, কারণ এটি নির্ধারণ করে আমরা রিয়েল-টাইম সেশন state, মিডিয়া ট্রান্সপোর্ট, রাউটিং, লেটেন্সি এবং failure isolation কীভাবে সামলাব.
একটি SFU, বা selective forwarding unit, হলো এমন একটি মিডিয়া সার্ভার যা প্রতিটি অংশগ্রহণকারীর কাছ থেকে একটি WebRTC স্ট্রিম গ্রহণ করে এবং বেছে বেছে সেগুলো অন্যদের কাছে ফরওয়ার্ড করে. এই মডেলে, SFU প্রতিটি অংশগ্রহণকারীর জন্য আলাদা WebRTC সংযোগ terminate করে, এবং AI সেশনে আরেকজন অংশগ্রহণকারী হিসেবে যোগ দেয়. এই পদ্ধতি স্বভাবতই বহু-পক্ষীয় পণ্যের জন্য ভালো মানানসই হতে পারে, যেমন group call, classroom বা collaborative meeting. এটি অডিও codec, RTCP message, data channel, recording এবং প্রতি-স্ট্রিম নীতিকে এক জায়গায় রাখে.1
এমনকি client-to-AI পণ্যের ক্ষেত্রেও, SFU প্রায়ই ডিফল্ট শুরুর বিন্দু হয়, কারণ এটি দলগুলোকে signaling, media routing, recording, observability এবং human handoff বা আরও অংশগ্রহণকারী যোগ করার মতো ভবিষ্যৎ বর্ধনের জন্য একটি পরীক্ষিত সিস্টেম পুনর্ব্যবহার করতে দেয়.
আমাদের workload আলাদা. বেশিরভাগ সেশন 1:1—একজন ব্যবহারকারী একটি মডেলের সঙ্গে কথা বলছেন, বা একটি অ্যাপ্লিকেশন একটি রিয়েল-টাইম এজেন্টের সঙ্গে কথা বলছে—এবং প্রতিটি টার্নেই লেটেন্সি-সংবেদনশীলতা আছে. এই ধরনের ট্রাফিকের জন্য, আমরা একটি transceiver মডেল বেছে নিয়েছি: একটি WebRTC edge service ক্লায়েন্ট সংযোগ terminate করে এবং তারপর মিডিয়া ও ইভেন্টকে মডেল inference, transcription, speech generation, tool use এবং orchestration-এর জন্য আরও সরল অভ্যন্তরীণ protocol-এ রূপান্তর করে.
এই নকশায়, transceiver-ই একমাত্র সেবা যা WebRTC session state-এর মালিক, যার মধ্যে রয়েছে ICE connectivity check, DTLS handshake, SRTP encryption key, এবং session lifecycle. এখানে “termination” বলতে বোঝায়, transceiver-ই সেই endpoint যা handshake সম্পন্ন করে এবং মিডিয়া এনক্রিপ্ট বা ডিক্রিপ্ট করে. এই state এক জায়গায় রাখায় session ownership নিয়ে ভাবা সহজ হয়েছে, এবং backend service-গুলোকে নিজেদের WebRTC peer-এর মতো আচরণ না করে সাধারণ সেবার মতো scale করতে দিয়েছে.
Transceiver মডেল বেছে নেওয়ার পর, আমাদের প্রথম implementation ছিল Pion-এর ওপর নির্মিত একটি একক Go service, যা signaling ও media termination—দুটিই সামলাত. এটি ChatGPT ভয়েস, Realtime API-এর WebRTC এন্ডপয়েন্ট, এবং বেশ কিছু গবেষণা প্রকল্প চালায়.
অপারেশনালভাবে, transceiver service দুটি কাজ করে:
- Signaling: SDP negotiation, codec নির্বাচন, ICE credentials, এবং session setup
- Media: downstream WebRTC সংযোগ terminate করা এবং inference ও orchestration-এর জন্য backend service-গুলোর সঙ্গে upstream connection বজায় রাখা
আমরা চেয়েছিলাম সেবাটি আমাদের বাকি অবকাঠামোর মতোই চলুক: Kubernetes-এ, যেখানে workload চাহিদা অনুযায়ী বাড়তে-কমতে পারে এবং host জুড়ে সরে যেতে পারে. কিন্তু প্রচলিত প্রতি-সেশনে-এক-পোর্ট WebRTC মডেল এই পরিবেশের সঙ্গে ভালোভাবে মানায় না, কারণ এটি বড় public UDP port range-এর ওপর নির্ভর করে, যেগুলো pod যোগ, অপসারণ বা reschedule হওয়ার সময় expose, secure এবং অক্ষুণ্ণ রাখা কঠিন.2
প্রথম সমস্যা ছিল প্রতি-সেশনে-এক-পোর্ট মডেলটি নিজেই. উচ্চ concurrency-তে, এর অর্থ হলো খুব বড় UDP port range expose ও manage করা.
- Cloud load balancer এবং Kubernetes service প্রতি service-এ দশ-হাজারেরও বেশি public UDP port ঘিরে তৈরি নয়. প্রতিটি অতিরিক্ত range load balancer config, health check, firewall policy, এবং rollout safety-তে অপারেশনাল জটিলতা বাড়ায়.3
- বড় UDP port range সুরক্ষিত রাখা কঠিন, কারণ এগুলো বাইরে থেকে পৌঁছানো যায় এমন surface area বাড়ায় এবং network policy audit করা কঠিন করে.
- এগুলো autoscaling-এর সঙ্গেও খাপ খায় না. Kubernetes-এ pod ক্রমাগত যোগ হয়, সরানো হয়, ও reschedule হয়. প্রতিটি pod-কে একটি বড় স্থিতিশীল port range reserve ও advertise করতে বললে সেই elasticity ভঙ্গুর হয়ে যায়.4
এ কারণেই অনেক WebRTC সিস্টেম প্রতি সার্ভারে একটি UDP port-এর দিকে অগ্রসর হয়, যেখানে সেই port-এর পেছনে application-level demultiplexing থাকে.5
প্রতি-সার্ভারে-এক-পোর্ট নকশা port count সমস্যার সমাধান করে, কিন্তু এগুলো দ্বিতীয় একটি সমস্যা তৈরি করে: পুরো fleet জুড়ে প্রতিটি session-এর ownership বজায় রাখা.
ICE এবং DTLS হলো stateful protocol. যে process একটি session তৈরি করেছে, সেটিকে সেই session-এর packet পেতেই হবে, যাতে এটি connectivity check validate করতে পারে, DTLS handshake সম্পন্ন করতে পারে, SRTP ডিক্রিপ্ট করতে পারে, এবং ICE restart-এর মতো পরবর্তী session পরিবর্তন প্রক্রিয়া করতে পারে. একই session-এর packet যদি অন্য process-এ পৌঁছে, setup ব্যর্থ হতে পারে বা মিডিয়া নষ্ট হতে পারে.
এতে আমাদের জন্য একটি নির্দিষ্ট লক্ষ্য তৈরি হয়েছিল: public internet-এ একটি ছোট, স্থির UDP surface expose করা, কিন্তু একই সঙ্গে প্রতিটি packet-কে সেই transceiver-এ রাউট করা, যা সংশ্লিষ্ট WebRTC session-এর মালিক.
আমরা সেখানে পৌঁছানোর জন্য বিভিন্ন উপায় মূল্যায়ন করেছি, যার মধ্যে TURN (Traversal Using Relays around NAT) রয়েছে, যেখানে একটি edge relay client allocation terminate করে এবং তাদের পক্ষ থেকে ট্রাফিক ফরওয়ার্ড করে.2
পদ্ধতি | সুবিধা | অসুবিধা |
প্রতি সেশনে আলাদা IP:port (native direct UDP নামেও পরিচিত) | সরাসরি client-to-server মিডিয়া পথ ডেটা path-এ কোনো forwarding layer নেই | প্রতি সেশনে একটি public UDP port প্রয়োজন বড় port range expose ও secure করা কঠিন Kubernetes এবং cloud load balancer-এর জন্য দুর্বল মানানসই |
প্রতি সার্ভারে আলাদা IP:port | প্রতি-সেশন exposure-এর তুলনায় public UDP footprint অনেক ছোট প্রতি সার্ভারে একটি shared socket বহু session demultiplex করতে পারে | একটি একক host-এ পরিষ্কারভাবে কাজ করে, কিন্তু shared load-balanced fleet জুড়ে নিজে থেকে নয় একটি একক host-এ session demultiplexing তখনই সাহায্য করে যখন packet সেই host-এ পৌঁছে যায়; load-balanced fleet জুড়ে প্রথম packet এখনও ভুল instance-এ পড়তে পারে, তাই প্রতিটি session-কে তার মালিক process-এর দিকে steer করার জন্য এখনও একটি deterministic উপায় দরকার |
TURN relay (protocol-terminating) | ক্লায়েন্টদের শুধু TURN relay address এবং port-এ পৌঁছালেই হয় Edge-এ policy কেন্দ্রীভূত করা যায় | TURN allocation setup round trip বাড়ায় TURN server জুড়ে allocation সরানো বা পুনরুদ্ধার করা এখনও কঠিন |
Stateless forwarder + stateful terminator (OpenAI-এর relay + transceiver) | ছোট public UDP footprint Transceiver এখনও পুরো WebRTC session-এর মালিক | মিডিয়া owning transceiver-এ পৌঁছানোর আগে একটি forwarding hop যোগ হয় Relay এবং transceiver-এর মধ্যে custom coordination দরকার |
আমরা যে আর্কিটেকচারটি প্রেরণ করেছি, তা packet routing-কে protocol termination থেকে আলাদা করে. Session setup-এর জন্য signaling এখনও transceiver-এ পৌঁছে, আর media প্রথমে relay দিয়ে প্রবেশ করে. Relay হলো একটি হালকা UDP forwarding layer, যার public footprint ছোট, আর transceiver হলো এর পেছনের stateful WebRTC endpoint.
Relay মিডিয়া ডিক্রিপ্ট করে না, ICE state machine চালায় না, বা codec negotiation-এ অংশ নেয় না. এটি গন্তব্য বেছে নেওয়ার জন্য যথেষ্ট packet metadata পড়ে, তারপর packet-টি সেই transceiver-এ ফরওয়ার্ড করে, যা session-এর মালিক. Transceiver এখনও একটি স্বাভাবিক WebRTC flow-ই দেখে এবং সব protocol state-এর মালিকই থাকে. ক্লায়েন্টের দৃষ্টিকোণ থেকে, WebRTC session সম্পর্কে কিছুই বদলায় না.
এই সেটআপে first-packet routing-ই মূল ধাপ. Packet path-এ নিজস্বভাবে কোনো session তৈরি হওয়ার আগেই relay-কে ক্লায়েন্টের প্রথম packet রাউট করতে হয়, বাহ্যিক lookup service-এ থেমে না গিয়ে.
প্রতিটি WebRTC session-এ আগে থেকেই একটি protocol-native routing hook থাকে: ICE username fragment, বা ufrag, যা session setup-এর সময় বিনিময় করা হয় এবং STUN connectivity check-এ প্রতিধ্বনিত হয়. আমরা server-side ufrag এমনভাবে তৈরি করি, যাতে relay গন্তব্য cluster ও owning transceiver অনুমান করার জন্য পর্যাপ্ত routing metadata পায়.
Signaling চলাকালে, transceiver session state allocate করে এবং SDP answer-এ একটি shared relay VIP ও UDP port ফেরত দেয়. VIP হলো relay fleet-এর সামনে থাকা একটি virtual IP address; port-এর সঙ্গে মিলিয়ে এটি ক্লায়েন্টকে একটি একক স্থিতিশীল গন্তব্য দেয়, যেমন `203.0.113.10:3478`, যদিও এর পেছনে অনেক relay instance থাকে. ক্লায়েন্টের প্রথম media-path packet সাধারণত একটি STUN (Session Traversal Utilities for NAT) binding request, যা ICE ব্যবহার করে advertised address-এ packet পৌঁছাতে পারে কি না তা যাচাই করতে.
Relay প্রথম STUN packet-এর এতটুকুই parse করে যে server ufrag পড়তে পারে, routing hint decode করতে পারে, এবং packet-টি owning transceiver-এ ফরওয়ার্ড করতে পারে. প্রতিটি transceiver একটি shared UDP socket-এ listen করে, অর্থাৎ একটি operating system endpoint যা একটি internal IP:port-এ bound, প্রতি session-এ একটি socket নয়. Relay যখন ক্লায়েন্টের source IP:port থেকে ওই transceiver destination পর্যন্ত একটি session তৈরি করে, তখন পরবর্তী DTLS, RTP ও RTCP packet ufrag পুনরায় decode না করেই সেই session-এর মধ্যে প্রবাহিত হয়.
Relay-এর session ইচ্ছাকৃতভাবেই ন্যূনতম, যেখানে packet forwarding জানাতে শুধু in-memory session থাকে, সঙ্গে পর্যবেক্ষণের জন্য প্রয়োজনীয় counter এবং session expiration ও cleanup-এর timer. এই নকশা packet routing-কে সরাসরি packet path-এই রাখে. Relay restart হয়ে session হারালে, পরবর্তী STUN packet ufrag routing hint থেকে session পুনর্গঠন করে. এটিকে আরও নির্ভরযোগ্য করতে, route স্থাপিত হওয়ার পর <client IP + Port, transceiver IP + Port>-এর mapping ধরে রাখতে একটি Redis cache ব্যবহৃত হয়, যাতে পরবর্তী STUN packet আসার আগেই এটি অনেক আগে পুনরুদ্ধার করা যায়.
যখন আমরা public UDP surface-কে অল্প কয়েকটি স্থিতিশীল address ও port-এ নামিয়ে আনলাম, তখন আমরা একই relay pattern বৈশ্বিকভাবে deploy করতে পারলাম. Global Relay হলো আমাদের ভৌগোলিকভাবে বিতরণকৃত relay ingress point-এর fleet, যেগুলো সব একই packet-forwarding আচরণ বাস্তবায়ন করে.
বিস্তৃত ভৌগোলিক ingress প্রথম client-to-OpenAI hop-কে ছোট করে, কারণ packet প্রথমে দূরবর্তী region-এ public internet পেরিয়ে যাওয়ার বদলে ব্যবহারকারীর কাছাকাছি—ভৌগোলিক অবস্থান ও network topology উভয় অর্থেই—একটি relay দিয়ে আমাদের নেটওয়ার্কে প্রবেশ করতে পারে. বাস্তবে এর মানে হলো কম লেটেন্সি, কম জিটার, এবং ট্রাফিক আমাদের backbone-এ পৌঁছানোর আগে এড়ানো সম্ভব এমন কম loss burst.6
আমরা signaling-এর জন্য Cloudflare geo এবং proximity steering ব্যবহার করি, যাতে প্রাথমিক HTTP বা WebSocket request কাছাকাছি একটি transceiver cluster-এ পৌঁছে. Request context সেশনের অবস্থান নির্ধারণ করে এবং কোন Global Relay ingress point ক্লায়েন্টকে advertise করা হবে তা ঠিক করে. SDP answer Global Relay address দেয়, আর ufrag-এ যথেষ্ট তথ্য থাকে যাতে Global Relay মিডিয়াকে নির্ধারিত cluster-এ রাউট করতে পারে এবং relay গন্তব্য transceiver-এ রাউট করতে পারে.
একসঙ্গে, geo-steered signaling এবং Global Relay setup ও media—দুটোকেই কাছাকাছি একটি entry path-এ নিয়ে আসে, একই সঙ্গে session-টিকে একটি transceiver-এ anchored রাখে. এতে signaling ও প্রথম ICE connectivity check-এর round-trip time কমে, যা সরাসরি ব্যবহারকারীকে কথা শুরু করার আগে কতক্ষণ অপেক্ষা করতে হবে তা কমিয়ে দেয়.
আমরা relay service-টি Go-তে লিখেছি এবং ইচ্ছাকৃতভাবেই implementation সীমিত রেখেছি. Linux-এ, kernel-এর networking stack মেশিনের network interface থেকে UDP packet গ্রহণ করে এবং একটি socket-এ পৌঁছে দেয়, যা একটি operating system endpoint; কোনো process IP:Port bind করার পর সেখান থেকে পড়ে. Relay userspace-এ চলে, তাই একটি সাধারণ Go process সেই socket থেকে packet header পড়ে, অল্প পরিমাণ flow state আপডেট করে, এবং WebRTC terminate না করেই packet ফরওয়ার্ড করে. আমাদের কোনো kernel-bypass framework দরকার হয়নি, যা userspace process-কে উচ্চতর packet rate-এর জন্য সরাসরি network queue poll করতে দিত, তবে অপারেশনাল জটিলতাও বাড়াত.
মূল নকশাগত সিদ্ধান্তগুলো:
- কোনো protocol termination নয়: Relay শুধু STUN header/ufrag parse করে; পরবর্তী DTLS, RTP ও RTCP-এর জন্য এটি cached state ব্যবহার করে, ফলে packet অস্বচ্ছই থাকে.
- ক্ষণস্থায়ী state: এটি flow state ও observability-এর জন্য client address থেকে transceiver destination-এর একটি ছোট, স্বল্প-timeout, in-memory map বজায় রাখে.
- Horizontal scalability: একাধিক relay instance একটি load balancer-এর পেছনে সমান্তরালে চলে. State কঠিন WebRTC state নয়, তাই restart হলে ট্রাফিক drop কম হয় এবং flow দ্রুত পুনরুদ্ধার হয়.
দক্ষতার ব্যবস্থা:
SO_REUSEPORTহলো একটি Linux socket option, যা একই মেশিনে একাধিক relay worker-কে একই UDP port bind করতে দেয়. তারপর kernel আগত packet-গুলোকে ওই worker-গুলোর মধ্যে বিতরণ করে, যা একক read-loop bottleneck এড়ায়.runtime.LockOSThreadপ্রতিটি UDP-পাঠকারী goroutine-কে নির্দিষ্ট একটি OS thread-এ pin করে.SO_REUSEPORT-এর সঙ্গে মিলিয়ে, এতে একই flow-এর packet (source ও destination IP:Port plus protocol) একই CPU core-এ থাকার প্রবণতা বাড়ে, ফলে cache locality উন্নত হয় এবং context switching কমে.- Pre-allocated buffer এবং ন্যূনতম copying parse ও allocation overhead কম রাখে, যাতে Go-তে garbage collection এড়ানো যায়.
এই implementation তুলনামূলকভাবে ছোট relay footprint দিয়ে আমাদের বৈশ্বিক রিয়েল-টাইম media traffic সামলেছে, তাই kernel bypass পথে না গিয়ে আমরা সহজতর নকশাই রেখে দিয়েছি.
এই আর্কিটেকচার আমাদের Kubernetes-এ WebRTC media চালাতে দেয়, হাজার হাজার UDP port expose না করেই. এটি গুরুত্বপূর্ণ, কারণ ছোট ও স্থির UDP surface নিরাপদ রাখা ও load balance করা সহজ, এবং বড় public port range reserve না করেই অবকাঠামোকে scale করতে দেয়. Kubernetes-এর ভালো অবকাঠামোগত সমর্থন এবং ছোট surface area-এর কারণে বেশি নিরাপত্তার পাশাপাশি, এই নকশা ক্লায়েন্টদের জন্য মানসম্মত WebRTC আচরণও বজায় রাখে এবং নিশ্চিত করে যে SFU-বিহীন নকশাই আমাদের workload-এর জন্য সঠিক ডিফল্ট ছিল. আমাদের অধিকাংশ session point-to-point, latency-sensitive, এবং inference service-গুলোকে WebRTC peer-এর মতো আচরণ করতে না হলে scale করা সহজ.
আরও বিস্তৃত শিক্ষা হলো, জটিলতা যোগ করার সেরা জায়গা হলো একটি পাতলা routing layer, প্রতিটি backend service-এ নয়, এবং custom client behavior-এও নয়. একটি protocol-native field-এ routing metadata encode করা আমাদের deterministic first-packet routing, ছোট public UDP footprint, এবং বিশ্বজুড়ে ব্যবহারকারীর কাছাকাছি ingress স্থাপনের মতো যথেষ্ট নমনীয়তা দিয়েছে.
কয়েকটি সিদ্ধান্ত বিশেষভাবে গুরুত্বপূর্ণ ছিল:
- Edge-এ protocol semantics অক্ষুণ্ণ রাখা. ক্লায়েন্টরা এখনও মানসম্মত WebRTC-ই ব্যবহার করে, যা browser ও mobile interoperability অটুট রাখে.
- কঠিন session state এক জায়গায় রাখা. Transceiver ICE, DTLS, SRTP এবং session lifecycle-এর মালিক; relay শুধু packet ফরওয়ার্ড করে.
- Setup-এ আগে থেকেই উপস্থিত তথ্যের ভিত্তিতে রাউট করা. ICE ufrag আমাদের অতিরিক্ত hot-path lookup dependency ছাড়াই first-packet routing hook দিয়েছে.
- Kernel bypass-এ যাওয়ার আগে সাধারণ ব্যবহারের ক্ষেত্রে অপ্টিমাইজ করা.
SO_REUSEPORT, thread pinning, এবং কম-allocation parsing-এর যত্নশীল ব্যবহারে তৈরি একটি সীমিত Go implementation-ই আমাদের workload-এর জন্য যথেষ্ট ছিল.
রিয়েল-টাইম ভয়েস AI তখনই কাজ করে, যখন অবকাঠামো লেটেন্সিকে অদৃশ্য বলে মনে করায়. আমাদের জন্য, এর অর্থ ছিল আমাদের WebRTC deployment-এর গঠন বদলানো, কিন্তু WebRTC সম্পর্কে ক্লায়েন্টরা যা আশা করে তা না বদলিয়ে.
লেখক
তথ্যসূত্র
2. GitHub - l7mp/stunner: WebRTC-এর জন্য একটি Kubernetes মিডিয়া গেটওয়ে(একটি নতুন উইন্ডোতে খোলে)
3. সংক্ষেপে WebRTC পোর্ট [উদাহরণ] - BlogGeek.me(একটি নতুন উইন্ডোতে খোলে)
4. Kubernetes-এ ডিপ্লয় করুন - LiveKit ডকস(একটি নতুন উইন্ডোতে খোলে)
6. Cloudflare Calls: শেষ পর্যন্ত নেমে যাওয়া লক্ষ লক্ষ cascading tree(একটি নতুন উইন্ডোতে খোলে)


