Bài toán: Hàng triệu tài xế, mỗi 4 giây

Grab có khoảng 5 triệu tài xế hoạt động tại Đông Nam Á. Uber có hơn 5 triệu tài xế toàn cầu. Nếu mỗi tài xế gửi tọa độ GPS mỗi 4 giây, hệ thống phải nhận:

5.000.000 tài xế ÷ 4 giây = 1.250.000 gói tin GPS / giây

Đây là 1.25 triệu write operations mỗi giây — chỉ riêng cho dữ liệu vị trí, chưa tính các request khác. HTTP REST truyền thống không thể xử lý tải này hiệu quả.


Tại sao HTTP REST không phù hợp?

Chi phí kết nối (Connection Overhead)

Mỗi HTTP request yêu cầu:

  1. TCP Handshake (3-way handshake)
  2. TLS Handshake (thêm 1-2 round trips nếu dùng HTTPS)
  3. HTTP Headers (~200-800 bytes overhead cho mỗi request)
  4. Đóng kết nối (hoặc keep-alive timeout)

Với GPS payload chỉ khoảng 50-100 bytes (latitude, longitude, timestamp, speed, heading), overhead của HTTP chiếm gấp 5-10 lần dữ liệu thực.

Tốn pin điện thoại

Mỗi lần thiết lập kết nối HTTP mới, chip radio (4G/5G) của điện thoại phải chuyển từ trạng thái ngủ (idle) sang trạng thái hoạt động (active), tiêu tốn năng lượng đáng kể. Nếu tài xế chạy 8-12 tiếng/ngày, pin sẽ cạn rất nhanh.


Giải pháp: Các giao thức siêu nhẹ

1. MQTT (Message Queuing Telemetry Transport)

MQTT được thiết kế ban đầu cho các thiết bị IoT với băng thông hạn chế và pin nhỏ — hoàn hảo cho việc gửi tọa độ GPS liên tục.

Tại sao MQTT hiệu quả:

  • Kết nối mở liên tục (Persistent Connection): Chỉ handshake một lần, sau đó gửi tọa độ liên tục mà không cần thiết lập lại kết nối.
  • Header cực nhỏ: Chỉ 2 bytes overhead so với hàng trăm bytes của HTTP.
  • Quality of Service (QoS) linh hoạt:
    • QoS 0: Gửi và quên (fire-and-forget) — phù hợp cho GPS vì vài giây sau lại có tọa độ mới.
    • QoS 1: Đảm bảo gửi ít nhất một lần.
  • Last Will and Testament (LWT): Nếu tài xế mất kết nối đột ngột, MQTT broker tự động thông báo cho server rằng tài xế đã offline.
Luồng dữ liệu MQTT:

Driver App → MQTT Publish → MQTT Broker Cluster → Kafka
  Topic: "driver/{driver_id}/location"
  Payload: {
    "lat": 10.7769,
    "lng": 106.7009,
    "ts": 1717689600,
    "speed": 35.2,
    "heading": 127,
    "accuracy": 8
  }

2. gRPC Bidirectional Streaming

Uber sau này chuyển sang dùng gRPC Streams (và QUIC/HTTP3) thay vì MQTT cho nhiều luồng dữ liệu real-time.

Ưu điểm so với MQTT:

  • Protobuf serialization: Payload nhị phân nhỏ hơn JSON 3-10 lần.
  • Bidirectional: Server có thể đẩy dữ liệu ngược lại (cuốc xe, chỉ đường) trên cùng kết nối.
  • Flow control: gRPC có cơ chế kiểm soát luồng (backpressure) tích hợp, quan trọng khi server bị quá tải.
  • Multiplexing: HTTP/2 cho phép nhiều stream trên một kết nối TCP, tránh head-of-line blocking.
// Định nghĩa service gRPC cho Location Streaming
service LocationService {
  // Tài xế gửi stream tọa độ liên tục
  rpc StreamLocation(stream LocationUpdate) returns (stream ServerCommand);
}

message LocationUpdate {
  double latitude = 1;
  double longitude = 2;
  int64 timestamp_ms = 3;
  float speed_mps = 4;       // mét/giây
  float heading_degrees = 5;  // hướng đi (0-360)
  float accuracy_meters = 6;  // độ chính xác GPS
  string driver_id = 7;
}

message ServerCommand {
  oneof command {
    NewRideOffer ride_offer = 1;
    NavigationUpdate nav = 2;
    PingRequest ping = 3;
  }
}

Kỹ thuật tối ưu trên Mobile App

Batching — Gom nhiều tọa độ

Thay vì gửi mỗi tọa độ riêng lẻ, app gom (batch) 3-5 điểm GPS rồi gửi một cục:

Không batching:        Có batching:
GPS 1 → Send          GPS 1 ─┐
GPS 2 → Send          GPS 2 ─┤ → Send 1 batch
GPS 3 → Send          GPS 3 ─┘
GPS 4 → Send          GPS 4 ─┐
GPS 5 → Send          GPS 5 ─┤ → Send 1 batch
GPS 6 → Send          GPS 6 ─┘

6 network calls        2 network calls (tiết kiệm 67%)

Adaptive Frequency — Tần suất thích ứng

App không cần gửi tọa độ đều đặn mỗi 4 giây. Nó điều chỉnh tần suất dựa trên ngữ cảnh:

Trạng thái tài xếTần suất gửi GPSLý do
Đang chờ khách (idle)Mỗi 15-30 giâyTiết kiệm pin, tài xế không di chuyển nhiều
Đang di chuyển tìm kháchMỗi 4-5 giâyCần cập nhật vị trí cho matching
Đang chở khách (on-trip)Mỗi 2-3 giâyCần độ chính xác cao cho ETA và bản đồ
Tốc độ cao (>60 km/h)Mỗi 1-2 giâyDi chuyển nhanh, vị trí thay đổi nhiều
Đứng yên (dừng đèn đỏ)Tạm ngừng gửiGPS không thay đổi, tiết kiệm 100%

Dead Reckoning — Nội suy vị trí

Giữa hai lần nhận GPS, app khách hàng dùng Dead Reckoning (dự đoán quỹ đạo) để hiển thị xe chạy mượt mà trên bản đồ:

Vị trí GPS thực tế nhận được: ● (mỗi 4 giây)
Vị trí nội suy (interpolated): ○ (mỗi 100ms)

●───○───○───○───○───○───○───●───○───○───○───○───●
t=0                         t=4s                t=8s

Công thức nội suy đơn giản:
  predicted_lat = last_lat + (speed × cos(heading) × Δt)
  predicted_lng = last_lng + (speed × sin(heading) × Δt)

Lọc nhiễu GPS: Kalman Filter

Tín hiệu GPS từ điện thoại thường bị nhiễu (noisy) — đặc biệt trong thành phố có nhiều tòa nhà cao tầng, tín hiệu bị phản xạ (urban canyon effect). Kết quả: xe hiển thị trên bản đồ bị nhảy lung tung hoặc chạy xuyên qua nhà.

Kalman Filter hoạt động như thế nào?

Kalman Filter là thuật toán dự đoán → hiệu chỉnh liên tục:

Vòng lặp mỗi khi nhận GPS mới:

1. PREDICT (Dự đoán):
   Dựa trên vị trí, tốc độ, hướng đi trước đó,
   dự đoán vị trí hiện tại bằng mô hình vật lý.

   predicted_position = previous_position + velocity × Δt

2. UPDATE (Hiệu chỉnh):
   So sánh dự đoán với GPS thực tế vừa nhận.
   Tính trọng số: tin dự đoán hay tin GPS hơn?

   Nếu GPS có accuracy = 5m (chính xác): tin GPS nhiều hơn
   Nếu GPS có accuracy = 50m (nhiễu):    tin dự đoán nhiều hơn

   final_position = weighted_average(predicted, gps_measured)

Lyft Engineering mô tả trong blog kỹ thuật của họ rằng họ dùng Marginalized Particle Filter — phiên bản nâng cao hơn Kalman Filter — có thể theo dõi nhiều quỹ đạo khả dĩ đồng thời khi không chắc chắn xe đang ở đường nào (ví dụ: đường chính hay đường song hành).

Map Matching — Bắt xe về đúng đường

Sau khi lọc nhiễu, hệ thống thực hiện Map Matching — ghép tọa độ đã lọc vào mạng lưới đường bộ số hóa (digital road network):

GPS thô (sau Kalman):     Map Matched:

    ○  ○                     ●──●
   ○    ○                   ●    ●
  ○      ○   ──────►       ●      ●
   ○    ○                   ●    ●
    ○  ○                     ●──●

(Tọa độ nằm rải rác)     (Bám sát lòng đường)

Kiến trúc Location Pipeline hoàn chỉnh

Driver Phone GPS Sensor
        │
        ▼
  ┌───────────────┐
  │  Kalman Filter │ (Trên điện thoại)
  │  + Batching    │
  └───────┬───────┘
          │ gRPC Stream / MQTT
          ▼
  ┌───────────────┐
  │  Load Balancer │ (Nginx / Envoy)
  └───────┬───────┘
          │
          ▼
  ┌───────────────┐
  │  Location     │ → Validate, enrich, convert to H3 index
  │  Service      │
  └───────┬───────┘
          │ Produce to Kafka
          ▼
  ┌───────────────┐
  │  Apache Kafka  │  Topic: "driver.location.updates"
  └───┬───┬───┬───┘
      │   │   │
      ▼   ▼   ▼
   Redis  Flink  Data Lake
   GEO    (Stream  (S3/HDFS
   Cache  Processing) Analytics)

Các con số thực tế

MetricGiá trị ước tính
Số lượng tài xế online đồng thời (Grab SEA)~1.5 triệu
Tần suất gửi GPS trung bìnhMỗi 4 giây
Throughput Location Service~375.000 msg/s
Kích thước payload GPS (Protobuf)~40-60 bytes
Băng thông GPS raw~15-22 MB/s
Latency end-to-end (phone → Redis)< 200ms

Tiếp theo, chúng ta sẽ tìm hiểu cách Uber dùng thuật toán H3 để chia bản đồ thành hàng triệu ô lục giác và tìm tài xế gần nhất trong nháy mắt. Đọc tiếp Phần 2 — Geospatial Indexing: H3, S2 Geometry & Redis GEO.