Một lỗi chí mạng ngây ngô của mấy bạn junior khi xây mấy cái app gọi xe là cắm thẳng cái API Gateway vào luôn Hệ thống Định tuyến (Routing Engine).
Answer-first: Graphhopper là một con quái vật ngốn CPU cực bạo (CPU-intensive). Nếu bạn bắt nó tính thời gian dự kiến (ETA) tới tận 10,000 ông tài xế đang online trong thành phố, mấy con server nhà bạn sẽ tan chảy theo đúng nghĩa đen. Bạn bắt buộc phải nhét Chỉ Mục Không Gian (Spatial Indexing) (như Uber H3 hay Redis GEO) vào làm “Màng lọc thô” (Pre-filter) siêu tốc độ. Cái chỉ mục này sẽ bới bèo ra bọ tìm cho ra 50 tài xế gần nhất “theo đường chim bay” chạy trơn tru trên RAM, và chỉ 50 mạng đó mới được đẩy xuống cho Graphhopper cày ải tính toán ETA nặng nề.
Trong bài mổ xẻ sâu này, chúng ta sẽ khám phá mấy cái chiêu thiết kế hệ thống (system design patterns) cỡ đẳng cấp Staff (Staff-level) của Chỉ mục Không gian, vượt ra ngoài mấy cái tutorial xoàng xĩnh để vươn tới kiến trúc chuẩn production.
1. Uber H3 vs Google S2 (Bài toán Láng giềng đều nhau)
Khi đem băm nát thế giới ra thành từng ô lưới (grid), lịch sử ghi nhận đa phần hệ thống xài hình vuông (kiểu như con Google S2). Nhưng Uber đã lật bàn làm chao đảo thế giới bằng cách quăng con H3 ra, xài Hình Lục Giác (Hexagons). Ủa tại sao?
Answer-first: Ném lên một cái lưới hình vuông, cái thằng láng giềng nằm chéo góc về mặt toán học (mathematically) sẽ cách xa mình hơn thằng láng giềng chung vách. Trò này đẻ ra cái bệnh “thiên vị phương hướng” (directional bias) khi bạn tung chiêu quét bán kính (radius searches). Lục giác phá vỡ định kiến này vì cả 6 thằng láng giềng đều chung vách và hoàn toàn cách đều với thằng ở trung tâm (equidistant from the center).
Khi bạn phân cuốc cho tài xế (dispatch), bạn phải dò lùng trong những cái vòng tròn đồng tâm cứ bành trướng bự dần ra (gọi là vòng k-rings). Mấy cái hình lục giác cách đều nhau của H3 đứng ra bảo kê cho cái bán kính này nở ra đồng đều (uniformly) về mọi hướng, biến việc phân cuốc trở nên minh bạch và vô đối về mặt toán học (mathematically sound).
Mẹo của dân Pro về Độ Phân Giải (Resolution): Đừng có xài chết một độ phân giải. Đầu sỏ trong ngành (Industry leaders) luôn vác Độ phân giải 9 (Resolution 9 - cỡ ~0.1 km²) ra để dí (matching) tài xế cho chính xác, và xài Độ phân giải 6 (Resolution 6 - cỡ ~250 km²) để quét bao quát diện rộng áp mưu đẩy giá cước lên cao (surge pricing).
2. Bệnh Méo Mó Diện Tích (Area Distortion): Khối 20 Mặt (Icosahedron) vs Bản đồ Phẳng (Web Mercator)
Ủa sao không quất quách một cái lưới hình vuông trùm lên đầu Google Maps (Web Mercator) cho rảnh nợ?
Answer-first: Web Mercator bản chất nó là một phép chiếu phẳng (planar projection) và nó bóp méo (severely distorts) điên cuồng diện tích vật lý ở mấy vùng quanh cực trái đất (biến hòn đảo Greenland trông bự chà bá hơn cả lục địa Châu Phi). Nếu bạn hùng hổ tính “Mật độ Tài xế trên mỗi kilomet vuông” ném lên cái lưới Mercator, phép toán của bạn sẽ vỡ vụn (completely breaks) ở mấy thành phố nằm ở vĩ độ cao.
H3 lé né cái bẫy này bằng cách ném Trái Đất lên một Khối 20 Mặt (Icosahedron). Trò ảo thuật này lấy nhân cách bảo kê rằng một cái ô H3 nằm vất vưởng ở đường xích đạo (equator) cũng có diện tích vật lý giống y chang như đúc một ô H3 nằm tê cóng ở Iceland, duy trì tính nguyên vẹn về thống kê (statistical integrity) trên phạm vi toàn thế giới.
3. Bí Kíp Lưu Trữ: Redis GEO vs PostGIS
Cuộc cãi vã không hồi kết: Nên xài cái database không gian quan hệ (relational spatial database) hay nhét thẳng vào RAM làm cache in-memory?
Answer-first: Hệ thống chuẩn production thì ôm cả hai.
- Xài Redis GEO để chứa tọa độ của mấy ông tài xế sống dở chết dở chạy ngoài đường (live, transient). Nó ôm đám Geohashes ném sạch vào RAM, dâng hiến cái tốc độ kinh hoàng chưa tới 1 mili-giây (sub-millisecond latencies) cho mấy cái trò “bới tìm tài xế gần nhất”.
- Xài PostGIS (
ST_DWithin) để chứa mấy cái hình học (geometries) trường tồn (permanent) và phức tạp nhức não như ranh giới nhà kho, vùng phục vụ (service zones), hay xài để ngồi phân tích dữ liệu cũ (historical analytics).
Điểm Nghẽn Chia Mảnh của Redis (The Redis Sharding Bottleneck): Redis nhai mấy câu lệnh GEO trên một cái luồng duy nhất (single thread). Nếu bạn tống cổ 1 triệu ông tài xế sống vào nhét chung một cái key global_drivers, một cái lõi CPU (CPU core) sẽ phải è lưng gánh mọi phép toán hình học và lăn ra đơ (max out) ở 100% ngay tắp lự. Bạn buộc phải vác trò Chia Mảnh (Sharding) ra xài chung với hash tags địa lý (kiểu như drivers:{hcmc}:geo và drivers:{hanoi}:geo).
4. Thiết Kế Hệ Thống Cấp Cao: Kafka & Phép Nén Không Gian (Spatial Compaction)
Làm thế nào mấy nền tảng bự chà bá ghép cặp (match) khách với tài xế ngay tắp lự mà không bị bệnh kẹt database (database locking)?
Phân Mảnh Không Gian (Spatial Partitioning) Với Kafka: Bằng cách lấy cái mã ô (Cell ID) H3 làm Kafka Message Key, toàn bộ tọa độ GPS hắt ra từ đám tài xế và hành khách lảng vảng chung một khu vực sẽ bị tóm cổ ném vào chung một cái partition duy nhất của Kafka (và hiển nhiên là sẽ chui vào cùng một cái Consumer node xử lý). Chiêu này trao quyền cho hệ thống ghép cặp (match) đám tài xế ngay trên thanh RAM lân cận (xài RocksDB) mà không tốn một milimet bước nhảy vắt ngang mạng lưới (zero cross-network hops) nào.
Phép Nén Không Gian (Spatial Compaction): Để nhét được mớ vùng dịch vụ bành trướng, phức tạp (Hàng Rào Ảo - Geofences) mà không muốn cái RAM khóc thét đi tong hàng Gigabyte, Uber xài hàm h3.compact(). Thuật toán này đệ quy nhồi nhét (recursively collapses) 7 cái ô lục giác con lắt nhắt gộp lại thành 1 ô lục giác mẹ khổng lồ, ép rớt dữ liệu không gian xuống tận 80%.
Khi đã chăng sẵn cái màng lọc thô Spatial Indexing (Spatial Indexing pre-filter) vào khuôn, bước tiếp theo là đóng gói mấy thứ cồng kềnh này nhét vào một cái API Gateway đẳng cấp. Nhảy tiếp vào Phần 4: Tích Hợp API Golang & Microservices (Kratos & Dapr) để lột trần mọi mánh khóe bọc lót bên trong.
Hỏi Nhanh Đáp Gọn (FAQ): Các Trường Hợp Góc (Edge Cases) Trên Production & Bẫy Chết Người (Gotchas)
k-ring ra mần hàm Trung bình động (Moving Average - hay còn gọi là Convolutional Smoothing) quét qua mấy cái ô láng giềng. Quả này đẻ ra một cái ranh giới giá thoai thoải (gentle price gradient), đập tan nát cái bệnh nhấp nháy giá ảo diệu (flickering effect) nếu có gã xui xẻo nào đứng dạng háng ngay chóc giữa lằn ranh.polyfill của H3 chỉ thèm rước một cái ô vào danh sách nếu đúng cái tâm điểm chóc giữa (centroid) của nó lọt vào trong hình đa giác (polygon). Để chữa cháy cho cái rìa răng cưa, hãy lấy cái lưới mịn hơn (higher resolution) chà vào đợt trám ban đầu, hoặc đắp thêm một lớp vữa (buffer) kRing quanh ba cái ô vùng biên để bảo kê diện tích phủ (full coverage).geometry nằm trong hệ tọa độ WGS84, cái đơn vị nó xài là ĐỘ (DEGREES). Bạn vừa mới ra lệnh cho PostGIS khoanh bán kính 1000 ĐỘ (quấn cái Trái Đất đứt hơi tận 3 vòng). Bạn buộc phải đúc ép (cast) cột của mình về kiểu geography thì cái đơn vị nó mới chuyển sang xài mét (meters) nhé.ST_Distance < 5000. Hàm ST_Distance là thằng CHỐNG index (NOT index-aware) và nó ép nguyên cái bảng phải lục soát lòi kèn (full table scan). Bạn BẮT BUỘC phải xài ST_DWithin ngay bên trong cục WHERE (thằng này nó xài ké được chỉ mục hộp không gian của GiST - GiST spatial bounding box index), và chỉ được nhét ST_Distance vào vế ORDER BY mà thôi.