Hướng dẫn cài đặt và cấu hình pgRouting trên PostgreSQL: Tự dựng hệ thống tìm đường đi ngắn nhất

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Sự cố lúc 2 giờ sáng: Khi API định tuyến giao vận đứng hình

2 giờ sáng, chuông trực PagerDuty réo liên hồi. Cụm microservice gán đơn cho tài xế lăn ra báo lỗi 504 Gateway Timeout. Trên dashboard, hàng nghìn query tính cước treo cứng giữa đợt flash sale. Trước đó, hệ thống tính khoảng cách theo đường chim bay rồi gọi dồn dập sang API bản đồ bên thứ ba. Hậu quả là hóa đơn API cán mốc 3.000 USD/tháng, trong khi trần rate limit 100 req/s bị bóp nghẽn đúng giờ cao điểm.

Nhìn màn hình Grafana đỏ rực, team quyết định dọn dẹp mớ hỗn độn này. Thay vì tốn tiền gọi API ngoài, chúng tôi chuyển hẳn bài toán routing về database nội bộ. Bằng cách nạp dữ liệu giao thông vào PostgreSQL và kích hoạt pgRouting, mọi thuật toán tìm đường ngắn nhất sẽ chạy trực tiếp trên engine C cực kỳ tối ưu.

Bản chất mạng lưới đồ thị trong PostgreSQL

Database không thể tự hiểu hai tọa độ GPS nối với nhau thế nào nếu bạn chỉ lưu bảng điểm phẳng. Muốn tìm đường, dữ liệu phải được mô hình hóa thành đồ thị (Graph):

1. Cấu trúc Topology mạng lưới (Network Topology)

Mỗi con đường là một cạnh (Edge), còn ngã ba hay ngã tư đóng vai trò đỉnh giao cắt (Node hoặc Vertex). Một bảng dữ liệu chuẩn để pgRouting hoạt động cần tối thiểu các trường sau:

  • id: Định danh duy nhất của đoạn đường.
  • source: ID nút giao bắt đầu.
  • target: ID nút giao kết thúc.
  • cost: Trọng số chiều thuận (chiều dài theo mét hoặc thời gian di chuyển tính bằng giây).
  • reverse_cost: Trọng số chiều nghịch. Với đường hai chiều, giá trị này bằng cost; nếu là đường một chiều cấm đi ngược, ta đặt giá trị âm (thường là -1).
  • geom: Đối tượng hình học không gian (LineString) do PostGIS quản lý, sử dụng hệ tọa độ chuẩn WGS 84 (EPSG:4326).

2. PostGIS và pgRouting phân chia nhiệm vụ ra sao?

PostGIS đảm nhận phần hình học: lưu trữ kinh độ, vĩ độ, chuyển đổi hệ quy chiếu và tính khoảng cách thực tế. pgRouting đóng vai trò thư viện thuật toán chạy trên nền PostGIS. Nó cung cấp sẵn Dijkstra, A*, Travelling Salesperson (TSP) hay Driving Distance (vùng phục vụ theo bán kính thời gian) mà bạn không cần tự viết giải thuật từ đầu.

Các bước cài đặt và triển khai thực tế

Dưới đây là các bước dựng cụm routing trực tiếp trên môi trường Ubuntu 22.04 LTS chạy PostgreSQL 16.

Bước 1: Cài đặt PostGIS và pgRouting

Mở terminal và cài đặt hai package tương ứng với phiên bản PostgreSQL:

# Cập nhật danh sách package
sudo apt update

# Cài đặt PostGIS 3 và pgRouting cho PostgreSQL 16
sudo apt install -y postgresql-16-postgis-3 postgresql-16-pgrouting

Cài đặt xong, bạn đăng nhập vào PostgreSQL để kích hoạt extension trên database dự án:

sudo -u postgres psql -d transport_db
-- PostGIS bắt buộc phải bật trước vì pgRouting phụ thuộc vào nó
CREATE EXTENSION IF NOT EXISTS postgis;

-- Kích hoạt pgRouting
CREATE EXTENSION IF NOT EXISTS pgrouting;

-- Xác nhận cài đặt thành công
SELECT pgr_version();

Bước 2: Thiết kế bảng và nạp dữ liệu mạng lưới đường

Bảng roads được khởi tạo để mô phỏng các tuyến đường nội đô:

CREATE TABLE roads (
    id SERIAL PRIMARY KEY,
    osm_id BIGINT,
    name VARCHAR(100),
    source INTEGER,
    target INTEGER,
    cost DOUBLE PRECISION,
    reverse_cost DOUBLE PRECISION,
    geom GEOMETRY(LineString, 4326)
);

Nguồn dữ liệu thực tế thường trích xuất từ OpenStreetMap (OSM) hoặc file Shapefile của đối tác logistics dưới dạng CSV/GeoJSON. Nếu cần chuyển đổi nhanh file CSV tọa độ sang JSON để nạp qua script, bạn có thể ghé toolcraft.app/vi/tools/data/csv-to-json — công cụ này parse trực tiếp trên trình duyệt máy bạn nên hoàn toàn yên tâm về bảo mật dữ liệu khách hàng.

Giờ hãy chèn 4 đoạn đường mẫu tạo thành một tam giác giao thông khép kín tại khu vực trung tâm Hà Nội (quanh tọa độ 105.85, 21.02):

INSERT INTO roads (id, name, geom)
VALUES
    (1, 'Đoạn Tràng Tiền A', ST_GeomFromText('LINESTRING(105.850 21.020, 105.855 21.020)', 4326)),
    (2, 'Đoạn Tràng Tiền B', ST_GeomFromText('LINESTRING(105.855 21.020, 105.860 21.020)', 4326)),
    (3, 'Đoạn Phan Chu Trinh', ST_GeomFromText('LINESTRING(105.855 21.020, 105.855 21.025)', 4326)),
    (4, 'Đoạn Hai Bà Trưng', ST_GeomFromText('LINESTRING(105.855 21.025, 105.860 21.020)', 4326));

Bước 3: Tự động đánh chỉ mục Topology với pgr_createTopology

Lúc này hai cột source và target vẫn rỗng. Thay vì tự dò từng ngã tư bằng tay, pgRouting cung cấp hàm pgr_createTopology để tự quét các đầu mút giao nhau:

-- Cú pháp: tên bảng, dung sai sai số (tolerance), tên cột hình học, tên cột khóa chính
SELECT pgr_createTopology('roads', 0.00001, 'geom', 'id');

Dung sai 0.00001 độ tương đương khoảng 1,1 mét trên thực địa. Khoảng dung sai này vừa đủ để gắn kết các điểm đầu mút bị lệch nhẹ do GPS. Sau khi thực thi, pgRouting tự sinh ra bảng phụ roads_vertices_pgr chứa danh sách đỉnh, đồng thời điền đầy đủ ID vào cột source và target của bảng roads.

Kế đến là khâu tính trọng số di chuyển. Ta dùng hàm ST_Length trên kiểu dữ liệu geography để lấy chiều dài thực tế theo mét:

-- Ép kiểu sang geography để PostGIS tính đúng độ dài mét trên mặt cầu elip
UPDATE roads
SET cost = ST_Length(geom::geography),
    reverse_cost = ST_Length(geom::geography);

-- Giả sử đoạn Phan Chu Trinh là đường một chiều cấm đi ngược, gán reverse_cost = -1
UPDATE roads SET reverse_cost = -1 WHERE id = 3;

-- Đánh B-tree index cho source và target để tăng tốc truy vấn đồ thị
CREATE INDEX idx_roads_source ON roads(source);
CREATE INDEX idx_roads_target ON roads(target);

Bước 4: Chạy thuật toán Dijkstra tìm đường ngắn nhất

Mọi thứ đã sẵn sàng. Bạn có thể gọi hàm pgr_dijkstra để tìm lộ trình tối ưu từ nút giao 1 đến nút giao 3:

SELECT 
    path.seq,
    path.node,
    path.edge,
    path.cost AS step_cost,
    path.agg_cost AS total_cost,
    r.name AS road_name,
    ST_AsGeoJSON(r.geom) AS geojson
FROM pgr_dijkstra(
    'SELECT id, source, target, cost, reverse_cost FROM roads',
    1, -- Node xuất phát
    3, -- Node đích đến
    directed := true
) AS path
LEFT JOIN roads r ON path.edge = r.id
ORDER BY path.seq;

Truy vấn trả về danh sách các chặng đi kèm chi phí tích lũy và chuỗi GeoJSON tương ứng. Backend chỉ việc serialize mảng GeoJSON này trả về cho app tài xế để vẽ polyline lên bản đồ trong tích tắc.

Hiệu quả thực tế sau khi chuyển dịch

Sau hai tuần chạy production, giải pháp mang lại sự cải thiện rõ rệt:

  • Tải CPU cụm API backend giảm sâu từ 92% xuống còn 14%.
  • Độ trễ p99 khi tính lộ trình giảm từ 420ms (gọi API ngoài) xuống vỏn vẹn 8-12ms ngay trong mạng nội bộ.
  • Tiết kiệm gần 3.000 USD mỗi tháng tiền cước API bản đồ thương mại.
  • Xóa bỏ hoàn toàn nguy cơ nghẽn cổ chai rate limit trong các đợt flash sale.

Nếu hệ thống giao vận của bạn đang phình to và gặp rào cản về chi phí API lẫn độ trễ, việc làm chủ PostGIS cùng pgRouting ngay trong database là khoản đầu tư kỹ thuật hoàn toàn xứng đáng.

Share: