nirva|deploy
2026-05-18·10 min read·NirvaDeploy team
#autoscaling#performance#ops#Thai-SaaS

Autoscaling สำหรับ Thai SaaS — เมื่อไหร่ scale, scale เท่าไหร่, ระวังอะไร

"เว็บล่ม" = สูญเสีย customer trust + revenue. แต่ scale เผื่อตลอด = burn money

When to scale

Signals จาก NirvaDeploy metrics

ดูที่ /dashboard/[projectId]/[serviceId] — ถ้าเห็นข้อใดข้อหนึ่งบ่อยๆ → ต้อง scale:

SignalThresholdสาเหตุ
💻 CPU peak> 80% sustained 5 minCPU-bound (parsing, ML, compression)
🧠 RAM peak> 85% sustainedMemory leak หรือ workload โต
🌐 Network RX> 50 MB/sTraffic spike (sale, viral content)
⏱ p95 latency> 1sService slow under load
💥 Crashes/hour≥ 1OOM (out of memory) kills

ใช้ Claude สั่งดู:

"CPU + RAM ของ service web ตอนนี้เท่าไหร่"

How to scale บน NirvaDeploy

Phase 1-3: Manual scaling (ทำเอง)

ใช้ MCP tool หรือ web dashboard:

"Scale service web เป็น 3 replicas"
nirvadeploy_scale_service(service_id="svc-001", replicas=3)

Plan limits:

  • Free / Hobby: replicas = 1 เท่านั้น
  • Pro: 1-10 replicas
  • Business: 1-10 replicas (Phase 2.x: unlimited)
  • Enterprise: custom

Phase 4: Auto-scaling (Phase 4 own infra)

# .nirvadeploy.yaml (Phase 4+)
service: web
autoscale:
  min_replicas: 2
  max_replicas: 10
  target_cpu: 70       # scale up when avg CPU > 70%
  target_memory: 80    # scale up when avg RAM > 80%
  scale_up_cooldown: 60s
  scale_down_cooldown: 300s  # ระวัง flap — รอนานก่อน scale down

Phase 4 plan: ใช้ Kubernetes HPA หรือ Dokploy native scaler (TBD)

Capacity planning

Calculate baseline

Step 1: Measure single replica capacity

# Load test ด้วย vegeta (ลง 1 replica ก่อน)
echo "GET https://your-service.nirvadeploy.com/api/products" \
  | vegeta attack -duration=60s -rate=50 \
  | vegeta report

Result might be:

  • p50 latency: 80ms
  • p95 latency: 250ms
  • p99 latency: 800ms
  • Success rate: 99.5%
  • Throughput sustained: ~45 req/s before degradation

Step 2: Plan for peak

  • Average traffic: 1,000 req/min = 17 req/s → 1 replica พอ
  • Peak traffic (lunch 11am-1pm): 5,000 req/min = 83 req/s → ต้อง 2-3 replicas
  • Sale event (11.11 noon): 20,000 req/min = 333 req/s → ต้อง 8-10 replicas

Rule of thumb:

replicas = ceil(peak_qps / single_replica_capacity_qps × 1.5)
                                                       ^^^^
                                          50% headroom for safety

Database bottleneck (ระวัง!)

Scale app ไม่ scale DB — ถ้า Postgres กลายเป็น bottleneck:

  • 10 replicas × 20 connections = 200 conns → exceed default Postgres limit (100)
  • Solution 1: use connection pooler (PgBouncer) — recommended
  • Solution 2: tune max_connections ใน Postgres config
  • Solution 3: read replicas (Phase 4)
# Use SQLAlchemy pool settings
engine = create_async_engine(
    DATABASE_URL,
    pool_size=5,            # per replica
    max_overflow=10,
    pool_recycle=3600,      # recycle conn every 1 hr
    pool_pre_ping=True,     # ping before use (avoid stale)
)

Common pitfalls

1. "Scale up = ลดราคา"

❌ Wrong. Scale up = ราคาเพิ่ม (โดยตรงตาม replicas × tier price).

✅ Right strategy:

  • Optimize code first (profile slow queries)
  • Add caching (Redis) before scaling out
  • Scale only when optimization ไม่พอ

2. State in memory (broken at scale)

ถ้า service เก็บ session ใน memory → user request 1 ไป replica A, request 2 ไป replica B → session lost.

❌ Bad:

sessions = {}  # in-memory dict
@app.post("/login")
async def login(...):
    sessions[user_id] = token  # only replica A เห็น

✅ Good:

import redis
r = redis.from_url(REDIS_URL)
@app.post("/login")
async def login(...):
    await r.setex(f"session:{user_id}", 3600, token)  # ทุก replica เห็น

3. ลืม graceful shutdown

ตอน scale down → service ถูก SIGTERM. ต้อง:

  • หยุดรับ request ใหม่
  • รอ in-flight requests จบ (ปกติ 30s grace)
  • Flush logs / close DB connections
@app.on_event("shutdown")
async def shutdown():
    await db.disconnect()  # close PG pool
    await redis.close()
    await job_queue.shutdown(wait=True)

4. Cold start spikes

Scale up จาก 1→5 → 4 new replicas need to:

  1. Pull container image (5-15s)
  2. Boot Python interpreter (1-2s)
  3. Load deps + warm caches (5-30s ขึ้นกับ app)

Total cold start: 11-47s — ระหว่างนี้ traffic ตกที่ replicas เก่าทั้งหมด → อาจ overload

Mitigation:

  • Pre-warm before peak (scale up 30 min ก่อน)
  • Health check endpoint ที่ smart (return 200 only หลัง warm-up)
warmed_up = False

@app.on_event("startup")
async def warmup():
    global warmed_up
    # Preload models, warm caches
    await preload_db_connection_pool()
    await load_ml_model()
    warmed_up = True

@app.get("/health")
async def health():
    if not warmed_up:
        return JSONResponse({"status": "warming"}, status_code=503)
    return {"status": "ok"}

NirvaDeploy load balancer respect 503 → ไม่ส่ง traffic จนกว่า /health = 200

5. Scale-down flapping

หาก traffic ขึ้นๆ ลงๆ → scaler ขยับ replicas ขึ้นลงตลอด → unstable

Mitigation:

  • scale_down_cooldown ยาวกว่า scale_up_cooldown (5 min vs 1 min)
  • Min replicas ≥ 2 (สำหรับ HA แล้วยัง — single point of failure)

Cost optimization

Idle hour scaling

Thai SaaS pattern: ปกติ traffic peak 11am-2pm + 6pm-10pm. Night idle.

Strategy: Scale down เหลือ 1 replica ตอน 2am-7am.

# Phase 4+ scheduled scaling
autoscale:
  schedule:
    - cron: "0 7 * * *"     # 7am bangkok
      min_replicas: 3
    - cron: "0 2 * * *"     # 2am bangkok
      min_replicas: 1

Saves 30-40% on infra cost without affecting users

Database read replicas

Heavy read workload → main DB CPU 90%? — add read replica:

# Route reads to replica, writes to primary
write_engine = create_engine(WRITE_DB_URL)
read_engine = create_engine(READ_DB_URL)

async def get_products():
    async with read_engine.connect() as conn:
        return await conn.execute("SELECT * FROM products")  # ↓ load on primary

Phase 4: NirvaDeploy will offer managed read replicas for Postgres add-on

CDN for static assets

Don't serve static from your service:

// Next.js: assets auto-uploaded to Cloudflare R2 + served via CDN
// (Phase 2.x — NirvaDeploy will integrate)

Saves bandwidth + reduces service load drastically

Monitoring checklist

ก่อน scale up — เช็คก่อน:

  • [ ] Slow queries ใน database (use EXPLAIN ANALYZE)
  • [ ] N+1 queries (ORM lazy loading without eager)
  • [ ] Synchronous LLM calls blocking event loop (use async)
  • [ ] Large response payload (return only needed fields)
  • [ ] Missing index (Postgres pg_stat_user_indexes)
  • [ ] Logging too verbose (writes log = slow)
  • [ ] Inefficient JSON serialization (use orjson instead of stdlib)

Often 1 optimization > 5 replicas

Summary

  1. Measure first — single-replica capacity via load test
  2. Optimize before scaling — 1 good index > 3 extra replicas
  3. Watch DB bottleneck — connection pool sizing
  4. Stateless services — sessions in Redis, not memory
  5. Graceful shutdown — handle SIGTERM properly
  6. Schedule for traffic patterns — Thai business hours
  7. CDN for static — don't waste replica CPU on assets

NirvaDeploy MCP makes manual scaling trivial:

"Scale service api เป็น 5 replicas, ขยายตามดู metrics"

Phase 4 จะมี auto-scaling — Dokploy + Hetzner native HPA


คำถาม performance? Email [email protected] — เรา reply within 12h


ชอบบทความนี้?

ลอง NirvaDeploy ฟรี

Free tier ตลอดชีพ · ไม่ต้องใส่บัตร · Sign in ด้วย Line/Google

เริ่มเลย →