Bối cảnh

Ứng dụng mobile (Android/iOS) hiển thị trạng thái real-time của hàng nghìn IoT device. State thay đổi liên tục, yêu cầu kết nối WS ổn định khi app foreground.


Nguyên tắc thiết kế

  • Không giữ WS khi background — mobile platform không hỗ trợ đáng tin cậy, không cần thiết vì không có user nhìn vào
  • Background gap là acceptable — chỉ cần recover nhanh khi app resume
  • Proactive health check thay vì reactive eventonDone/onError/AppLifecycle không reliable trên cả Android và iOS
  • Snapshot + delta — khi reconnect lấy full state 1 lần, sau đó WS chỉ push thay đổi

Architecture tổng thể

┌─────────────────────────────────────────────┐
│                 Mobile App                  │
│                                             │
│  ┌─────────────┐      ┌──────────────────┐  │
│  │  Ping       │      │  WS Manager      │  │
│  │  Scheduler  │─────▶│  - connect()     │  │
│  │  (every 3s) │      │  - disconnect()  │  │
│  └─────────────┘      │  - send()        │  │
│         │             └────────┬─────────┘  │
│         │                      │            │
│  ┌──────▼──────┐      ┌────────▼─────────┐  │
│  │  Health     │      │  Message Handler │  │
│  │  Monitor    │      │  - pong → update │  │
│  │  lastPongAt │      │    lastPongAt    │  │
│  └──────┬──────┘      │  - data → UI     │  │
│         │             └──────────────────┘  │
│         │ dead?                             │
│         ▼                                   │
│  ┌─────────────┐      ┌──────────────────┐  │
│  │  Recovery   │─────▶│  Snapshot API    │  │
│  │  Flow       │      │  (REST)          │  │
│  └─────────────┘      └──────────────────┘  │
└─────────────────────────────────────────────┘
                │ WS
                ▼
┌─────────────────────────────────────────────┐
│              WS Gateway (Server)            │
│  - Handle ping/pong                         │
│  - Push state delta                         │
└─────────────────────────────────────────────┘

Ping/Pong Health Check

Cơ chế

t=0s  → Send PING,  lastPongAt = T-3s  → OK (gap = 3s < threshold 6s)
t=3s  → Send PING,  lastPongAt = T-3s  → OK
t=6s  → Send PING,  lastPongAt = T-6s  → DEAD (gap = 6s ≥ threshold)
         → trigger recovery flow

Thông số

Parameter Giá trị Lý do
Ping interval 3s Đủ nhạy, không tốn battery
Pong threshold 6s = 2× interval, tránh false positive
Reconnect delay 1s Nhường network stack ổn định trước

PING message format

// Client → Server
{ "type": "ping", "ts": 1718000000000 }

// Server → Client  
{ "type": "pong", "ts": 1718000000000 }

Dùng application-level ping/pong thay vì WS protocol frame để dễ debug và đồng nhất giữa các platform.


Recovery Flow

Detect dead (lastPongAt gap > threshold)
        │
        ├─ 1. Set isReconnecting = true
        ├─ 2. Pause ping scheduler
        ├─ 3. Show stale indicator trên UI
        │
        ├─ 4. Gọi REST GET /api/devices/snapshot
        │        └─ Nhận full current state của tất cả device
        │
        ├─ 5. Update UI với snapshot data
        │
        ├─ 6. Reconnect WS (exponential backoff nếu fail)
        │
        ├─ 7. Resume ping scheduler
        ├─ 8. Set isReconnecting = false
        └─ 9. Clear stale indicator

Exponential backoff khi reconnect thất bại

Attempt 1 → wait 1s
Attempt 2 → wait 2s
Attempt 3 → wait 4s
Attempt 4 → wait 8s
...
Max       → wait 60s

State Management

Biến trạng thái cần track

lastPongAt      : DateTime?   // timestamp pong cuối nhận được
isReconnecting  : bool        // guard tránh double-trigger
isStale         : bool        // UI indicator
retryCount      : int         // cho exponential backoff

Guard isReconnecting

Nhiều nguồn có thể trigger recovery cùng lúc (onDone, onError, ping timeout). Dùng isReconnecting để đảm bảo chỉ 1 flow chạy tại một thời điểm:

if (isReconnecting) return   // bỏ qua, đang xử lý rồi
isReconnecting = true
... recovery flow ...
isReconnecting = false

Background Behavior

Trạng thái app WS Ping scheduler Hành động khi resume
Foreground Connected Running Bình thường
Background Có thể die Paused/killed Check health → recover nếu cần
Resume Unknown Restart Chờ ping cycle kế tiếp detect

Không cố giữ WS sống khi background. Ping scheduler sẽ detect và recover khi app foreground trở lại.


Snapshot API

Endpoint phục vụ recovery, cần đảm bảo:

  • Cache ở server (Redis, TTL ~2-3s) — tránh overload khi nhiều client reconnect cùng lúc (mất điện, restart app hàng loạt)
  • Trả về trạng thái hiện tại của toàn bộ device, không phải event history
  • Response nhanh < 500ms — user đang chờ nhìn thấy data
GET /api/devices/snapshot
Response: { "ts": 1718000000000, "devices": [ { "id": "...", "state": "..." }, ... ] }

UX Guidelines

  • Hiển thị "Đang cập nhật..." ngay khi detect dead — đừng để user nhìn data cũ mà không biết
  • Sau khi có snapshot → remove indicator, WS tiếp quản delta updates
  • Không hiển thị error popup cho mỗi lần reconnect — chỉ show indicator nhẹ
  • Nếu reconnect thất bại sau N lần → show lỗi rõ ràng hơn kèm nút retry thủ công

Không dùng Push Notification cho case này

Push Notification (FCM/APNs) không phù hợp vì:

  • Rate limit: FCM ~600 msg/device/day, APNs còn thấp hơn
  • Delivery delay: 1-5 giây, không acceptable cho real-time state
  • Hàng nghìn device × liên tục thay đổi = bị throttle ngay lập tức
  • Không đảm bảo thứ tự

Push Notification chỉ dùng cho critical alert (device offline đột ngột, vượt ngưỡng cảnh báo) — số lượng ít, không liên tục.