အခန်း ၁၆ :: Performance and Scalability

Software ၏ functional requirement များ မှန်ကန်စွာ အလုပ်လုပ်ရုံသာမက၊ သုံးစွဲသူများအတွက် လျင်မြန်သော တုံ့ပြန်မှု (fast response) နှင့် တည်ငြိမ်သော အတွေ့အကြုံ (stable experience) ကို ပေးစွမ်းနိုင်ရန်မှာလည်း အလွန်အရေးကြီးသည်။ Performance Engineering ဆိုသည်မှာ software ၏ performance ကို စနစ်တကျ တိုင်းတာခြင်း၊ သုံးသပ်ခြင်း၊ နှင့် မြှင့်တင်ခြင်းတို့ကို ပြုလုပ်သည့် discipline ဖြစ်သည်။
Scalability ဆိုသည်မှာ user load သို့မဟုတ် data ပမာဏ တိုးလာသည့်အခါ system က ထို workload ကို ကောင်းမွန်စွာ ကိုင်တွယ်နိုင်စွမ်း ဖြစ်သည်။
၁၆.၁ Performance Requirements and Benchmarking
Performance ကိစ္စ ပြောရင် "မြန်ရမယ်" လို့ ယေဘုယျ ပြောလို့ မရပါဘူး။ ဘယ်လောက် မြန်ရမှာလဲ။ ဘယ်နေရာမှာ မြန်ရမှာလဲ ဆိုတာ တိတိကျကျ သတ်မှတ်ထားဖို့ လိုပါတယ်။ ဒါကို Non-functional Requirement အနေနဲ့ တိုင်းတာလို့ရတဲ့ ဂဏန်းတွေနဲ့ သတ်မှတ်ရပါမယ်။
ဘာတွေ တိုင်းတာမလဲ (Key Metrics)
Latency (Response Time)
Request တစ်ခု ပို့လိုက်ရင် ဘယ်လောက်ကြာမှ အဖြေ ပြန်ရသလဲ ဆိုတာပါ။ ဒါပေမယ့် Average ပဲ ကြည့်လို့ မရပါဘူး။
ဥပမာ - "p95 Latency < 200ms" လို့ သတ်မှတ်လေ့ ရှိပါတယ်။ ဆိုလိုတာက Request ၁၀၀ မှာ ၉၅ ခုက 200ms အောက် (≤ 200ms) ဖြစ်ရမယ်လို့ ဆိုလိုတာပါ။ (p95 = 95th Percentile)။ Average က နည်းနေပေမယ့် တချို့ User တွေမှာ အရမ်းကြာနေတာမျိုး မဖြစ်အောင် p95, p99 တွေကို ကြည့်ရတာပါ။
တိုင်းတာတဲ့ အခါမှာလည်း Server မှာ ကြာတဲ့ အချိန် လား၊ User ဆီ အရောက်ပြန်ပို့တဲ့ အချိန် (End-to-End) လား ဆိုတာ တိတိကျကျ ခွဲခြား သိဖို့ လိုပါတယ်။
Throughput
အချိန်ယူနစ်တစ်ခု (ဥပမာ - ၁ စက္ကန့်) အတွင်းမှာ System က အလုပ် ဘယ်လောက် ပြီးနိုင်သလဲ ဆိုတာပါ။
Unit တွေ အနေနဲ့ RPS (Requests Per Second) သို့မဟုတ် TPS (Transactions Per Second) နဲ့ တိုင်းတာလေ့ ရှိပါတယ်။
ဥပမာ - "Server က တစ်စက္ကန့်ကို Request ၁၀၀၀ (1000 RPS) လက်ခံနိုင်ရမယ်" ဆိုတာမျိုးပါ။
Error Rate
Request ၁၀၀ လာရင် ဘယ်နှစ်ခါ Fail ဖြစ်လဲ ဆိုတာပါ။
Fail ဖြစ်တယ် ဆိုရာမှာ 5xx Errors (Server ဘက်က မှားတာ)၊ Timeouts (ကြာလွန်းလို့ ပြတ်သွားတာ) နဲ့ Rate Limits (များလွန်းလို့ ပိတ်ချတာ) တွေ အကုန် ပါဝင်ပါတယ်။ User ဘက်က မှားတဲ့ 4xx Errors တွေကိုတော့ Error အဖြစ် ထည့်တွက်မလား၊ မတွက်ဘူးလား ဆိုတာ ကိုယ့်ရဲ့ SLO (Service Level Objective) ပေါ် မူတည်ပြီး ဆုံးဖြတ်ရပါမယ်။
ဘယ်လို စစ်ဆေးမလဲ
Benchmarking ဆိုတာကတော့ ကိုယ့် System ရဲ့ ပုံမှန် အခြေအနေမှာ ဘယ်လောက် နိုင်သလဲ ဆိုတာ Baseline တစ်ခု သတ်မှတ်ဖို့ မှတ်တမ်းတင်ထားတာပါ။ Environment တူညီဖို့ လိုသလို၊ System က စစချင်း (Cold start) မှာ နှေးတတ်တဲ့ အတွက် Warm-up လုပ်ပြီးမှ တိုင်းတာသင့်ပါတယ်။
Load Testing ကတော့ ပုံမှန် လာနေကျ User ပမာဏ (Expected Load) လောက် ဝင်လာရင် ခံနိုင်ရည် ရှိမရှိ စမ်းသပ်တာပါ။
Stress Testing ကတော့ System ပျက်သွားတဲ့အထိ တမင် ဖိအားပေးပြီး Break Point ကို ရှာတာပါ။ ဘယ်လောက်ထိ ခံနိုင်လဲ သိချင်လို့ပါ။
Soak Testing ဆိုတာလည်း ရှိပါသေးတယ်။ သူကတော့ အကြာကြီး (ဥပမာ - ၂၄ နာရီ) Run ထားပြီးတော့ Memory Leak ဖြစ်မဖြစ်၊ Resource တွေ ပြည့်မလာဘူးလား ဆိုတာ စစ်ဆေးတာပါ။ Apache JMeter လို Tool မျိုးနဲ့ စမ်းသပ်နိုင်ပါတယ်။
၁၆.၂ Performance Analysis and Profiling
Performance ပြဿနာ ရှင်းမယ် ဆိုရင် အရင်ဆုံး ဘယ်နေရာမှာ ကြာနေလဲ ဆိုတာ အရင် ရှာရပါမယ်။ ဒါကို Bottleneck (ပုလင်းလည်ပင်း) ရှာတယ်လို့ ခေါ်ပါတယ်။ ပုလင်းလည်ပင်း ကျဉ်းနေရင် ရေတွေ ဘယ်လောက် များများ ထွက်လို့ မရသလို၊ System မှာလည်း တစ်နေရာက ကြာနေရင် ကျန်တဲ့ နေရာတွေပါ လိုက်နှေးနေတတ်ပါတယ်။
Pareto Principle (80/20 Rule) အရ ပြောရရင် ကြာချိန်ရဲ့ ၈၀% ဟာ Code ရဲ့ ၂၀% လောက်ကပဲ ဖြစ်စေတာ များပါတယ်။ ဆိုလိုတာက Code အများကြီး ပြင်စရာ မလိုပါဘူး။ အဓိက ကြာစေတဲ့ အချက် (Critical Part) ကို ရှာတွေ့ဖို့ပဲ လိုပါတယ်။
ဒါကြောင့် ဘယ်နေရာ ကြာလဲ သိရအောင် Profiler Tools တွေ သုံးရပါတယ်။ Profiler က Code ကို Run နေရင်းနဲ့ Function တစ်ခုချင်းစီ က CPU Time ဘယ်လောက်ကြာလဲ၊ Memory ဘယ်လောက်စားလဲ ဆိုတာ အတိအကျ တိုင်းတာပေးနိုင်ပါတယ်။
Developer က ကိုယ့်ထင်မြင်ချက် (Guesswork) နဲ့ လျှောက်မပြင်သင့်ပါဘူး။ Profiler က ပြတဲ့ Data ကို ကြည့်ပြီးမှ အမှန်တကယ် ကြာနေတဲ့ နေရာကို ပြင်ဆင် (Optimize) လုပ်သင့်ပါတယ်။
၁၆.၃ Performance Optimization Techniques
Code ရေးတဲ့ အခါ ဘာတွေ သတိထားရမလဲ။
Algorithmic Optimization
Algorithm ရွေးချယ်မှု မှားယွင်းခြင်းက Performance ကို အထိခိုက်ဆုံးပါပဲ။
ဥပမာ - Data အလုံး ၁ သန်းကို စီချင်တယ် (Sort လုပ်ချင်တယ်) ဆိုပါစို့။
Bubble Sort ကို သုံးမယ်ဆိုရင် Time Complexity က O(n²) ဖြစ်တဲ့အတွက် အဆမတန် ကြာသွားပါလိမ့်မယ်။
Merge Sort သို့မဟုတ် Quick Sort ကို သုံးမယ်ဆိုရင် O(n log n) ပဲ ကြာတဲ့အတွက် အများကြီး ပိုမြန်ပါတယ်။
ဒါကြောင့် ကိုယ်ရေးတဲ့ Code က Loop တွေ ဘယ်နှစ်ထပ် ဖြစ်နေလဲ (Nested Loops)၊ Big O Notation အရ ဘယ်လောက် Complexity ရှိလဲ ဆိုတာ Developer တိုင်း သိထားသင့်ပါတယ်။
Asynchronous Operations
I/O Operation တွေ ဖြစ်တဲ့ File ဖတ်တာ၊ Network (API) ခေါ်တာ တွေက CPU ကို မလိုအပ်ပဲ စောင့်ခိုင်းထားသလို ဖြစ်စေပါတယ်။
Code ကို Asynchronous (Non-blocking I/O) ပုံစံ ပြောင်းလိုက်မယ်ဆိုရင်၊ I/O စောင့်နေတဲ့ အချိန်မှာ CPU က အလကား မနေပဲ တခြား Request တွေကို လုပ်ပေးနိုင်တဲ့ အတွက် Throughput တက်လာပါလိမ့်မယ်။
သတိပြုရန်: Async လုပ်လိုက်လို့ Task တစ်ခုချင်းစီရဲ့ ကြာချိန် (Latency) လျော့သွားတာ မဟုတ်ပါဘူး။ တစ်ပြိုင်နက် လုပ်နိုင်တဲ့ အရည်အသွေး (Concurrency) ကောင်းလာတာသာ ဖြစ်ပါတယ်။ Memory ပေါ်မှာ တွက်ချက်ရတဲ့ (CPU Bound) အလုပ်တွေ ဆိုရင်တော့ Async လုပ်လည်း မထူးပါဘူး။
Resource Pooling
Database Connection ဖွင့်တာတွေက "Expensive Operation" (စရိတ်ကြီးတဲ့ အလုပ်) တွေ ဖြစ်ပါတယ်။ TCP Handshake လုပ်ရတာတို့၊ Authentication လုပ်ရတာတို့က အချိန်ကြာပါတယ်။
ဒါကြောင့် Connection တွေကို ခဏခဏ ဖွင့်လိုက် ပိတ်လိုက် မလုပ်ပဲ ဖွင့်ပြီးသား Connection တွေကို Connection Pool တစ်ခုထဲမှာ သိမ်းထားပြီး လိုအပ်တဲ့ အချိန် ယူသုံး၊ ပြီးရင် ပြန်ထည့်ထားတာမျိုး လုပ်သင့်ပါတယ်။
၁၆.၄ Scalability Patterns (Horizontal vs. Vertical Scaling)
User တွေ များလာရင် Server နိုင်တော့မှာ မဟုတ်ပါဘူး။ အဲဒီအခါ ဘယ်လို ဖြေရှင်းမလဲ။
Vertical Scaling (Scaling Up)
လက်ရှိ Server ရဲ့ Hardware (CPU, RAM, SSD) ကို အဆင့်မြှင့်တာပါ။
အားသာချက်: ဘာ Code မှ ပြင်စရာ မလိုပါဘူး။ ချက်ချင်း လုပ်လို့ ရပါတယ်။
အားနည်းချက်: Hardware မှာ အကန့်အသတ် (Physical Limit) ရှိပါတယ်။ Server တစ်လုံးတည်း ဖြစ်တဲ့အတွက် ပျက်သွားရင် စနစ်တစ်ခုလုံး ရပ်သွားနိုင်ပါတယ် (Single Point of Failure)။
Horizontal Scaling (Scaling Out)
Server အလုံးရေ ကို တိုးပြီး Load မျှ သုံးတဲ့ နည်းလမ်းပါ။ Server တစ်လုံးတည်းက လုပ်မယ့်အစား ၃ လုံးလောက် ခွဲပြီး လုပ်လိုက်တာပါ။
အားသာချက်: လိုအပ်သလို အကန့်အသတ်မရှိ တိုးချဲ့လို့ ရပါတယ်။ Fail ဖြစ်ရင်လည်း တခြား Server တွေ ရှိနေတဲ့အတွက် စနစ် မရပ်သွားပါဘူး (High Availability)။ Cloud ခေတ်မှာ ဒါကို ပို အသုံးများပါတယ်။
အားနည်းချက်: Load Balancer ခံဖို့ လိုလာမယ်။ Database တွေကို မျှသုံးဖို့ (Distributed System) စီစဉ်ရတာ ရှုပ်ထွေးနိုင်ပါတယ်။
graph TD
subgraph "Vertical Scaling (Scaling Up)"
A["Server (Small)"] --> B["Server (Big)"]
end
subgraph "Horizontal Scaling (Scaling Out)"
Client --> F(Load Balancer)
C[Server 1]
D[Server 2]
E[Server 3]
F --> C
F --> D
F --> E
end
၁၆.၅ Caching Strategies
Caching ဆိုတာ မကြာခဏ သုံးနေရတဲ့ Data တွေကို ယူရခက်တဲ့ နေရာ (Database) ကနေ ယူမယ့်အစား၊ ယူရလွယ်တဲ့ နေရာ (RAM) မှာ ခဏ သိမ်းထားတာပါ။ ဒါဆိုရင် Data လိုချင်တိုင်း Database ကို သွားသွား ခေါက်နေစရာ မလိုတော့လို့ ပိုမြန်ပါတယ်။
Caching Flow (Cache-Aside Pattern)
အောက်ပါ Diagram တွင် Application သည် data လိုချင်သည့်အခါ Cache ကို အရင်စစ်ဆေးပုံ (Cache Hit vs Cache Miss) ကို ပြသထားသည်။
sequenceDiagram
autonumber
participant Client
participant App as Application
participant Cache
participant DB as Database
Client->>App: Request Data (ID: 101)
App->>Cache: Get Data (ID: 101)
alt Cache Hit (Data exists)
Cache-->>App: Return Data
else Cache Miss (Data not found)
Cache-->>App: Not Found
App->>DB: Query Data (ID: 101)
DB-->>App: Return Result
App->>Cache: Set Data (ID: 101)
end
App-->>Client: Return Response
၁၆.၅.၁ Cache Write Strategies
Data အသစ်ဝင်လာရင် Cache ထဲကို ဘယ်လို ထည့်မလဲ ဆိုတာ မူဝါဒ (Policy) ထားရှိဖို့ လိုပါတယ်။
1. Write-through
Data လာရင် Cache ထဲကိုလည်း ထည့်တယ်၊ Database ထဲကိုလည်း တစ်ခါတည်း (Synchronous) ထည့်ပါတယ်။
ကောင်းချက်: Data Consistency အတွက် စိတ်ချရဆုံးပါ။ Cache ထဲမှာလည်း အမြဲ Data အသစ် ရှိနေမယ်။
ဆိုးချက်: နေရာ ၂ ခုလုံး လိုက်ရေးနေရလို့ Write Latency (ရေးချိန်) ပိုကြာပါတယ်။
sequenceDiagram
autonumber
participant Client
participant App
participant Cache
participant DB
Client->>App: Write Data
Note right of App: Update both Cache & DB
App->>Cache: Save Data
App->>DB: Save Data
Cache-->>App: Success
DB-->>App: Success
App-->>Client: Acknowledge Write
2. Write-around
Database ထဲကိုပဲ တိုက်ရိုက် ထည့်လိုက်ပါတယ်။ Cache ကို ကျော်သွားတယ်။ ပြန်သုံးချင်တဲ့ အချိန်ကျမှ Database ကနေ ယူပြီး Cache ထဲ ထည့် (Lazy Load) ပါတယ်။
ကောင်းချက်: ခဏခဏ ပြန်မသုံးတဲ့ Data တွေ Cache ထဲ ရောက်မလာတော့ဘူး။ Cache Memory နေရာ ပိုသက်သာတယ်။
ဆိုးချက်: စစချင်း ခေါ်တဲ့ အခါ Cache Miss ဖြစ်ပြီး Database ကို သွားယူရတဲ့ အတွက် ပထမတစ်ခေါက်တော့ ကြာပါမယ်။
sequenceDiagram
autonumber
participant Client
participant App
participant Cache
participant DB
Client->>App: Write Data
Note right of App: Update DB only (Bypass Cache)
App->>DB: Save Data
DB-->>App: Success
App-->>Client: Acknowledge Write
Note over Client, DB: Later, when reading data...
Client->>App: Read Data
App->>Cache: Get Data
Cache-->>App: Miss (Not Found)
App->>DB: Get Data
DB-->>App: Return Data
App->>Cache: Save Data (Lazy Load)
App-->>Client: Return Data
3. Write-back (Write-behind)
ဒါကတော့ Cache ထဲကိုပဲ အရင် ထည့်လိုက်တာပါ။ User ကို ချက်ချင်း အိုကေ ပြန်ပြောလိုက်တယ်။ ပြီးမှ နောက်ကွယ်ကနေ (Asynchronous) Database ထဲကို လိုက်ထည့်တာပါ။
ကောင်းချက်: Write Performance အရမ်းကောင်းပါတယ်။ User က Database ပြီးတဲ့အထိ စောင့်နေစရာ မလိုပါဘူး။
ဆိုးချက်: Database ထဲ မရောက်ခင် Cache Server ပျက်သွားရင် Data ပျောက်သွားနိုင်ပါတယ် (Data Loss Risk)။
sequenceDiagram
autonumber
participant Client
participant App
participant Cache
participant DB
Client->>App: Write Data
Note right of App: Update Cache only
App->>Cache: Save Data (Dirty)
Cache-->>App: Ack
App-->>Client: Success (Fast Response)
Note over Cache, DB: Asynchronously...
loop Background Sync
Cache->>DB: Flush Dirty Data
DB-->>Cache: Success
end
၁၆.၅.၂ Entity Caching vs. Query Caching
ဒါကလည်း အရေးကြီးပါတယ်။ ဘာကို Cache လုပ်မှာလဲ ပေါ့။
1. Entity Caching ( ID နဲ့ သိမ်းခြင်း)
User တစ်ယောက်ချင်းစီ ID နဲ့ သိမ်းတာမျိုးပါ။ user:101 ဆိုပြီး Key-Value ပုံစံနဲ့ သိမ်းလိုက်တယ်။
User Update လုပ်ရင် အဲဒီ Key တစ်ခုတည်း ဖျက်လိုက်ရင် ရပြီ။ ရှင်းတယ်။ Manage လုပ်ရတာ လွယ်ပါတယ်။
2. Query Caching (Complex Pattern)
SELECT * FROM users WHERE age > 10 ဆိုတဲ့ Query ရလဒ်ကြီး တစ်ခုလုံးကို Cache လုပ်လိုက်တာပါ။
ပြဿနာကတော့ Invalidation (Cache ဖျက်တာ) ပါပဲ။ User 101 ကို အသက် ၁၁ နှစ် (age: 11) လို့ ပြင်လိုက်ရင်၊ ဒီ User ပါဝင်နေတဲ့ Query တွေ အကုန်လုံး (ဥပမာ age > 5, age < 20 etc.) လိုက်မှားကုန်ပါပြီ။ ဘယ် Query တွေနဲ့ ငြိနေလဲ လိုက်ရှာဖို့က အရမ်း ခက်ခဲပါတယ်။
ဒါကြောင့် ဒီလို Complex Query တွေ Cache လုပ်မယ်ဆိုရင် Write-through တွေ မလုပ်ပဲ TTL (Time To Live) တိုတိုလေး ထားပြီး သက်တမ်းကုန်ရင် ပျက်သွားအောင် စောင့်တာ လက်တွေ့အကျဆုံးပါပဲ။
၁၆.၅.၃ Memory ပြည့်သွားရင် ဘယ်လိုလုပ်မလဲ (Cache Eviction Policies)
Cache ဆိုတာ RAM ပေါ်မှာ သိမ်းတာ ဆိုတော့ နေရာ အကန့်အသတ် ရှိပါတယ်။ ပြည့်သွားရင် အဟောင်းတွေ ဖျက်ထုတ်ရပါတယ်။ ဘယ်ဟာကို ရွေးဖျက်မလဲ ဆိုတဲ့ မူဝါဒတွေ ရှိပါတယ်။
LRU (Least Recently Used): မသုံးတာ ကြာဆုံး ကောင်ကို ရွေးထုတ်တယ်။ (ဒါက General Purpose အတွက် အကောင်းဆုံးပါ)
LFU (Least Frequently Used): သုံးတဲ့ အကြိမ်ရေ အနည်းဆုံး ကောင်ကို ရွေးထုတ်တယ်။
FIFO (First-In, First-Out): အရင် ရောက်တဲ့ကောင် အရင် ထွက်။
TTL (Time-To-Live): Data တစ်ခုချင်းစီကို သက်တမ်းသတ်မှတ်ပေးလိုက်တာပါ။ သက်တမ်းကုန်ရင် သူ့အလိုလို ပျက်သွားပါလိမ့်မယ်။
၁၆.၅.၄ ဖြစ်တတ်တဲ့ ပြဿနာများ (Common Pitfalls)
1. Cache Penetration
ပြဿနာ: Hacker က Cache ထဲမှာ မရှိသလို၊ Database ထဲမှာလည်း မရှိတဲ့ Key တွေ (ဥပမာ - ID အတုတွေ) နဲ့ တမင် လာခေါ်တာပါ။
Fail ဖြစ်သွားတဲ့ Request တွေက Cache မှာ မရှိတော့ Database အထိ တောက်လျှောက် ရောက်လာပြီး Database ကို ဝန်ပိစေပါတယ်။
ဖြေရှင်းနည်း:
Bloom Filter သုံးပြီး မရှိနိုင်တဲ့ Key တွေကို အရင် စစ်ထုတ်လိုက်တာ။
Database မှာ မတွေ့ရင် Null Value (မရှိကြောင်း) ကိုပါ Cache ထဲမှာ ခဏ (TTL တိုတိုလေးနဲ့) သိမ်းထားလိုက်တာမျိုး လုပ်နိုင်ပါတယ်။
2. Cache Stampede (Dog-piling)
ပြဿနာ: လူသုံးများတဲ့ Data (Hot Key) တစ်ခု သက်တမ်းကုန်သွားတဲ့ အချိန်မှာ၊ User အများကြီး တစ်ပြိုင်နက် ဝင်လာတာပါ။ Cache မရှိတော့ အားလုံး Database ကို ဝိုင်းခေါ်သလို ဖြစ်သွားပြီး Database Down သွားနိုင်ပါတယ်။
ဖြေရှင်းနည်း:
Mutex Locking: တစ်ယောက်ကိုပဲ Database သွားယူခိုင်းပြီး ကျန်တဲ့ လူတွေကို ခဏ စောင့်ခိုင်းတာမျိုး။
Early Expiration: TTL မကုန်ခင် ကြိုပြီး Background ကနေ Refresh လုပ်ထားတာမျိုး။
3. Cache Avalanche
ပြဿနာ: Cache Key အများကြီးက တိုင်ပင်ထားသလို တချိန်တည်း သက်တမ်းကုန် (Expire) သွားတာပါ။ ဒါဆို Request တွေ အကုန်လုံး Database ပေါ် ပြုံကျလာပါလိမ့်မယ်။
ဖြေရှင်းနည်း:
Randomize TTL (Jitter): သက်တမ်းကုန်မယ့် အချိန် (TTL) ကို တစ်ခုနဲ့ တစ်ခု မတူအောင် နည်းနည်း စီ လွှဲထားလိုက်ပါ။ ဥပမာ - ၁၀ မိနစ် အတိ မထားပဲ ၉ မိနစ် နဲ့ ၁၁ မိနစ် ကြား Random ထားလိုက်တာမျိုးပါ။
Cache အမျိုးအစားများ
In-Memory Cache
Application ရဲ့ RAM ထဲမှာပဲ သိမ်းတာပါ။ ဒါက အမြန်ဆုံးပါပဲ။
Distributed Cache
Redis, Memcached လိုမျိုး သီးသန့် Server နဲ့ သိမ်းတာပါ။ Server တွေ အများကြီး က ဝိုင်းသုံးလို့ ရတဲ့ အားသာချက် ရှိပါတယ်။
Content Delivery Network (CDN)
Static file တွေ (ပုံတွေ၊ CSS တွေ) ကို ကမ္ဘာအနှံ့က Server တွေပေါ် ဖြန့်သိမ်းထားတာပါ။ User နဲ့ အနီးဆုံး Server ကနေ ပို့ပေးတော့ အရမ်း မြန်ပါတယ်။
၁၆.၆ Database Performance and Optimization
System တော်တော်များများ နှေးရတဲ့ အဓိက တရားခံက Database မှာ ဖြစ်လေ့ရှိပါတယ်။
Database Indexing
စာအုပ်မှာ မာတိကာ ပါသလိုပါပဲ။ Database မှာ Index ခံတယ်ဆိုတာ B-Tree (သို့မဟုတ် Hash) Data Structure နဲ့ သီးသန့် စီထားတာပါ။
Read: Table တစ်ခုလုံး လိုက်ရှာစရာ မလိုပဲ (
O(N)), Index Tree ပေါ်ကနေ တန်းသွားလို့ရတာ (O(log N)) ကြောင့် Read Performance အရမ်းတက်ပါတယ်။Write Trade-off: ဒါပေမယ့် Index တွေ များလွန်းရင်တော့ Data ထည့်တဲ့အခါ (Write) တိုင်း Index Tree ကိုပါ လိုက်ပြင်နေရတဲ့အတွက် Write နှေးသွားနိုင်ပါတယ်။ Storage နေရာလည်း ပိုယူပါတယ်။
Database Pagination
Data အများကြီးကို တစ်ခါတည်း ယူလိုက်ရင် Memory Pressure တက်ပြီး Database Crash ဖြစ်နိုင်ပါတယ်။
Offset Pagination:
OFFSET 10000 LIMIT 10လို သုံးတာပါ။ Data နည်းရင် ပြဿနာ မရှိပေမယ့်၊ Data များလာရင် Database က ရှေ့က Row ၁ သောင်းလုံးကို ဖတ်ပြီးမှ ကျော်သွားရတာမို့ (Scan Time) အရမ်းနှေးပါတယ်။Cursor-based Pagination:
WHERE id > last_seen_id LIMIT 10ဆိုပြီး သုံးတာပါ။ ရှေ့ကဟာတွေကို ဖတ်စရာမလိုပဲ ရောက်တဲ့နေရာက ဆက်သွားတာမို့ Data သန်းချီ ရှိလည်း Performance မကျပါဘူး။ (အားနည်းချက်က Random Page Jump လုပ်မရတာပါပဲ)
Database Partitioning vs. Sharding
Partitioning: Table ကြီး တစ်ခုကို Logical အပိုင်းလိုက် (ဥပမာ - ၂၀၂၃ စာရင်း၊ ၂၀၂၄ စာရင်း) ခွဲသိမ်းတာပါ။ Server က တစ်လုံးတည်း (Single Instance) ပါပဲ။ Partition Key (ဥပမာ Year column) ပါရင် ရှာရတာ အရမ်းမြန်ပါတယ်။
Sharding: ဒါက Data တွေကို Server မတူညီတဲ့ နေရာတွေမှာ ခွဲသိမ်းတာ (Distributed) ပါ။ Horizontal Scaling သဘောတရား ဖြစ်သွားပါပြီ။ Application ဘက်ကနေ ဘယ် User Data က ဘယ် Shard (Server) မှာ ရှိလဲ ဆိုတာ သိဖို့ လိုလာပါပြီ။
N+1 Query Problem
Loop ပတ်ပြီး Query ခေါ်မိတဲ့ ပြဿနာပါ။
ဥပမာ - Blog Post ၁၀ ခု ယူမယ် (၁ ခါ)။ ပြီးမှ Loop ပတ်ပြီး Post တစ်ခုချင်းရဲ့ Author ကို လိုက်ယူမယ် (၁၀ ခါ)။ စုစုပေါင်း Query ၁၁ ခါ (1 + N) ဖြစ်သွားပါတယ်။
ဖြေရှင်းနည်း:
ORM တွေမှာ Eager Loading (with('author')) သုံးပြီး တစ်ခါတည်း JOIN လုပ်ယူလိုက်ရင် Query တစ်ခေါက်တည်းနဲ့ ပြီးပါတယ်။
Query Optimization
Database Query တွေ နှေးနေရင် EXPLAIN ANALYZE လို command မျိုး သုံးပြီး Execution Plan ကို စစ်ဆေးရပါတယ်။
"Full Table Scan" ဖြစ်နေလား၊ Index မထိပဲ ဖြစ်နေလား၊ Join တွေက မှားနေလား ဆိုတာ Database Engine ရဲ့ လုပ်ဆောင်ချက်ကို အသေးစိတ် ကြည့်ပြီး ပြင်ဆင်ရပါမယ်။
Connection Pooling
(ဒါက ၁၆.၃ မှာ ပြောခဲ့တဲ့ Resource Pooling ပါပဲ) Database Connection တွေကို အရှင် မွေးထားပြီး ပြန်သုံးတဲ့ နည်းလမ်းပါ။
၁၆.၇ Load Balancing and High Availability
Load Balancer
Server တွေ အများကြီး သုံးတဲ့အခါ Traffic ကို မျှပေးမယ့် (Traffic Distribution) ကောင် လိုပါတယ်။ ဒါက Load Balancer ပါ။
Load Balancing Algorithm အမျိုးမျိုး ရှိပါတယ်:
Round Robin: တစ်ယောက် တစ်လှည့်စီ ပို့တာပါ။ (Server A -> Server B -> Server A...)
Least Connections: လက်ရှိ အလုပ် အနည်းဆုံး (Connection အနည်းဆုံး) ဖြစ်နေတဲ့ Server ဆီ ပို့တာပါ။
IP Hash: User ရဲ့ IP ပေါ် မူတည်ပြီး Server တစ်လုံးတည်းဆီကိုပဲ အမြဲ ပို့ပေးတာပါ။ (Sticky Session လိုချင်ရင် သုံးပါတယ်)
graph TD
Client1(Client) --> LB[Load Balancer]
Client2(Client) --> LB
LB --> Server1[Server 1]
LB --> Server2[Server 2]
LB --> Server3[Server 3]
High Availability (HA)
System တစ်ခုလုံး ဘယ်တော့မှ မရပ်သွားအောင် (No Single Point of Failure) လုပ်ဆောင်ခြင်းပါ။
Redundancy: Component တစ်ခု (Server/DB) ပျက်ရင် အစားထိုးဖို့ နောက်တစ်ခု အပို (Backup/Standby) ရှိနေရပါမယ်။
Failover: Primary Server ပျက်သွားတာနဲ့ Standby Server က အလိုအလျောက် နေရာဝင်ယူပြီး ဆက်လုပ်ပေးနိုင်ရပါမယ်။
Multi-AZ: Data Center တစ်ခုလုံး မီးပျက်ရင်တောင် မရပ်အောင် တခြား Zone (Availability Zone) တစ်ခုမှာပါ Server တွေ ဖြန့်ထားတာမျိုးပါ။
graph TD
C[Clients] --> LB[(Load Balancer - Multi-AZ / HA)]
subgraph "Availability Zone A"
S1[App Server A]
DB1[(Primary DB)]
end
subgraph "Availability Zone B"
S2[App Server B]
DB2[(Standby/Replica DB)]
end
LB --> S1
LB --> S2
S1 --> DBE[(DB Endpoint/Proxy)]
S2 --> DBE
DBE --> DB1
DBE -. failover switch .-> DB2
DB1 -- Replication --> DB2
DB2 -. Promote on failover .-> DB1