Skip to main content

Author

Akira Noda - VoicePing Inc.

TL;DR

ကျွန်ုပ်တို့၏ WebSocket proxy server ကို Python မှ Go သို့ rewrite လုပ်ပြီး CPU usage ကို 1/10 နှင့် memory consumption ကို 1/100 သို့ လျှော့ချနိုင်ခဲ့ပါသည်။ Project သည် resource efficiency တိုးတက်မှုသာမက crucial concurrency lesson တစ်ခုလည်း သင်ပေးခဲ့ပါသည်:
Locks များကို တတ်နိုင်သမျှ သေးငယ်စွာနှင့် နည်းနည်းသာ ထားပါ။

Context

ကျွန်ုပ်တို့၏ system သည် VoicePing တွင် အသုံးပြုသော real-time STT (speech-to-text) နှင့် translation pipeline ဖြစ်ပြီး client device တစ်ခုချင်းစီသည် speech-to-text နှင့် multiple languages translation အတွက် ကျွန်ုပ်တို့၏ backend သို့ audio stream လုပ်ပါသည်။ WebSocket proxy server သည် ဤ pipeline ၏ အလယ်တွင် ရှိပါသည်:
Architecture

System architecture overview

  • Client တစ်ခုချင်းစီသည် STT proxy နှင့် persistent WebSocket session ထိန်းသိမ်းသည်
  • Proxy သည် audio packets များကို GPU-based inference servers များထဲမှ တစ်ခုသို့ relay လုပ်သည်
  • Transcribed text နှင့် translations များကို စောင့်ဆိုင်းပြီး partial transcripts stream back လုပ်သည်
ဤ architecture သည် concurrent real-time audio sessions ထောင်ပေါင်းများစွာ ကို sub-second latency ဖြင့် handle လုပ်ရပါသည်။ သို့သော် ကျွန်ုပ်တို့၏ ယခင် Python-based proxy သည် bottleneck ဖြစ်လာခဲ့ပါသည်။

Before: Python Proxy (Inefficient)

ကျွန်ုပ်တို့၏ ပထမ proxy server ကို Python (FastAPI + asyncio + websockets) တွင် implement လုပ်ပြီး multiple worker processes ဖြင့် Gunicorn အသုံးပြု၍ deploy လုပ်ထားပါသည်။
MetricBefore (Python)After (Go)
CPU usage~12 cores, 40-50%~12 cores, 4-5%
Memory usage~25 GB~10 MB

Python ဘာကြောင့် ရုန်းကန်ခဲ့သလဲ

Python ၏ architecture သည် systemic bottlenecks များစွာ ဖန်တီးခဲ့ပါသည်: Single-Threaded Event Loop: asyncio model သည် coroutines ထောင်ပေါင်းများစွာကို thread တစ်ခုတွင် multiplex လုပ်သည်။ တစ်ချိန်တည်းမှာ coroutine တစ်ခုသာ run ပြီး အခြားများသည် loop yield control ပြုလုပ်သည်အထိ စောင့်ရပါသည်။ Gunicorn Multiprocessing: CPU cores အားလုံးကို အသုံးပြုရန် multiple worker processes spawn လုပ်ရပါသည်။ Process တစ်ခုချင်းစီသည် full Python runtime နှင့် app state load လုပ်ပြီး memory usage ကို linearly multiply လုပ်ပါသည်။ ထို့ကြောင့် Go တွင် rewrite လုပ်ခဲ့ပါသည်။

Revised Design

Revised Design

Atomics နှင့် channels ပါဝင်သော Lock-free design

Redesign သည် shared state minimize ခြင်း နှင့် responsibilities isolate ခြင်းတို့အပေါ် focus လုပ်ခဲ့ပါသည်:
ComponentPurpose
Queue for available connectionsEnqueue/dequeue သည် internal locks ကို automatically handle လုပ်သည်
sync.Map for in-use connectionsLock-free concurrent map
Atomic variablesHealth flags နှင့် counters
Dedicated goroutine per connectionIndependent health checks
Component တစ်ခုချင်းစီသည် pool-wide locks မပါဘဲ independently operate လုပ်ပါသည်။

Event-Driven Reconciliation

Reconciliation

Reconciliation worker pattern

Solution သည် event-driven reconciliation loop ဖြစ်ပါသည်:
  • Operation တစ်ခုချင်းစီသည် channel (messageCh) ထဲသို့ message ပို့သည်
  • Reconciliation goroutine သည် ဤ messages များကို sequentially process လုပ်သည်
  • ၎င်းသည် race conditions မရှိစေရန် ensure လုပ်သည်

Local Performance Check

Test Setup

ComponentConfiguration
ProxyGo-based WebSocket proxy
Backends3 x Echo WebSocket servers
Load3,000 connections simultaneously (no ramp-up)
Traffic1 KB text messages @ 100 msg/s per connection

Results

MetricValue
Concurrent sessions~3,000 stable
Throughput~300K messages/sec
Peak memory~150 MB
Average memory~60 MB
CPU usage~4-5% of 12 cores

Conclusion

Go-based proxy အသစ်ကို deploy လုပ်ပြီးနောက် performance, scalability နှင့် stability တို့တွင် major improvements များ တွေ့ရှိခဲ့ပါသည်:
CategoryPythonGoImprovement
CPU usage~12 cores x 40-50%~12 cores x 4-5%~90% reduction
Memory usage~25 GB~60-150 MB~99% reduction
ScalabilityHundreds limitedThousands sustains10x scale
Go rewrite သည် language change သာမက concurrency model transformation တစ်ခု ဖြစ်ခဲ့ပါသည်။
အဓိက ရယူချက်: Concurrency ကို shared mutable state under protection အဖြစ်မဟုတ်ဘဲ independent, communicating processes များအဖြစ် design ပါ။

References

  1. Go Concurrency Patterns - golang.org/doc/effective_go
  2. gorilla/websocket - github.com/gorilla/websocket