Làm việc trong lĩnh vực IoT (Internet of Things) nên tôi khá cần một hệ quản trị cơ sở dữ liệu chuyên dụng cho kiểu dữ liệu time-series (chuỗi thời gian), để lưu trữ dữ liệu do các thiết bị giám sát gửi lên.
Mặc dù IoT dựa trên những cơ sở công nghệ đã được phát triển từ lâu, thậm chí hệ CSDL time-series cũng không phải là khái niệm mới, nhưng mỗi nhà có một nhu cầu, nên mãi cho tới hồi đầu năm nay (2020), vẫn chưa có một hệ cơ sở dữ liệu nào thỏa mãn ý của tôi cả (khi viết bài này thì đã tìm được cái ưng ý).
Vậy nhu cầu của tôi khác với các đơn vị khác như thế nào? Đó là:
- Lưu trữ vĩnh viễn.
- Tự tóm tắt dữ liệu cũ.
- Chạy ổn trong máy tính nhúng.
Đa số các hệ CSDL time-series ra đời trước đây là phục vụ cho việc theo dõi "sức khỏe" của server (CPU/RAM usage) nên nó chỉ cần giữ lại dữ liệu trong một khoảng thời gian ngắn, vì vậy nhu cầu 1) không đáp ứng được. Ban đầu, khi mới bắt tay vào làm phần mềm cho AgriConnect, tôi tạm dùng PostgreSQL cho nhu cầu này, kết hợp với mẹo tạo chỉ mục (index) để đảm bảo tốc độ truy cập những dữ liệu mới ghi (để vẽ thành biểu đồ và để tính toán ra quyết định điều khiển thiết bị). Thời kỳ đó phần mềm của tôi nhắm chạy trên board BeagleBone Black nên những hệ CSDL viết bằng Java như Cassandra bị loại từ "vòng gửi xe", dù chúng hay được nhắc đến tên trong các platform về IoT sẵn có (các platform này cũng viết bằng Java nên cũng bị tôi bỏ qua, sorry các bạn fan của Java).
PostgreSQL kết hợp với chiến thuật tạo chỉ mục hợp lý thì khá ổn cho nhu cầu hay gặp (vẽ biểu đồ, điều khiển thiết bị dựa trên dữ liệu đo đạc), cho đến khi chúng tôi bắt đầu có nhu cầu kết xuất (export) dữ liệu cũ nhằm đánh giá hiệu quả mùa vụ. Dữ liệu cũ này không được đánh chỉ mục nên khi kết xuất thì chạy khá nặng, làm BeagleBone quá tải luôn. Vì vậy tôi bắt đầu tìm kiếm thêm, vừa lúc đó thì InfluxDB ra đời (có lẽ nó ra đời trước đó rồi nhưng vẫn đang phát triển, còn sơ khai nên tôi không tìm thấy).
Sau khi chuyển đổi từ PostgreSQL sang InfluxDB thì những ngày đầu, kết qủa rất phấn khởi: Do chuyên về time-series, với cách lưu trữ phù hợp, nên việc truy xuất dữ liệu gần đây hay dữ liệu cũ đều nhanh như nhau, tôi có thể dẹp bỏ script tạo lại chỉ mục dành cho PostgreSQL trước đó. Ngoài ra InfluxDB có chức năng tự tóm tắt dữ liệu cũ nên tiết kiệm được không gian lưu trữ.
Có điều niềm vui với InfluxDB chẳng kéo dài được lâu. Khi hệ thống hoạt động một thời gian, dữ liệu tích lũy đủ nhiều và InfluxDB càng cập nhật lên phiên bản mới thì nó càng bộc lộ vấn đề: ngốn quá nhiều RAM, chạy quá nặng. Để có thể duy trì hệ thống, tôi đã làm mọi cách để tối ưu bên phần của mình: sửa lại code, cắt bỏ nhiều lớp trừu tượng (ODM/ORM), tiết giảm xuống tới mức chỉ dùng câu SQL thuần khi truy cập InfluxDB, cắt một phần chức năng export ra viết lại bằng Rust. Tuy nhiên, cố gắng bên phía mình thôi vẫn chưa đủ, InfluxDB vẫn ục ịch ì ra đấy như trêu ngươi. Theo tôi, InfluxDB có một số quyết định công nghệ khiến "chúng ta không thuộc về nhau":
-
InfluxDB dường như hướng đến việc truy cập từ front-end, nên chọn HTTP 1 làm giao thức truyền tải. Giao thức HTTP không những là dạng text (InfluxDB không dùng HTTP/2) mà còn bắt buộc kèm theo một mớ header, khiến tăng overhead: không những nội dung truyền đi nặng thêm mà cả hai phía đều phải tốn thêm công parse các header HTTP nữa (ngoài ra, là một giao thức dạng text đồng nghĩa với việc thêm bước encode/decode base64 khi cần truyền dữ liệu nhị phân). Để ý là hệ CSDL phía backend như PostgreSQL dùng giao thức dạng binary cho tinh gọn. Hậu quả của việc dùng HTTP là như kết quả profile dưới đây, các phần code khác của tôi đã được tối ưu và chạy còn nhẹ hơn thư viện client để truy cập InfluxDB:
-
Dùng ngôn ngữ Go. Nói câu này có lẽ sẽ gây tranh cãi nơi fan của Go. Nhưng thực tình, tôi đã bắt gặp tâm sự của tác giả InfluxDB đâu đó rằng, anh ta lỡ chọn Go vì khi bắt tay vào xây dựng, anh ta chưa biết đến Rust. Ngoài ra, việc chọn Go hình như cũng vì InfluxDB muốn tốc độ ra sản phẩm nhanh. Chỉ trong vài năm mà công ty đằng sau InfluxDB đã cho ra một bộ nhiều sản phẩm liên quan chứ không phải mỗi database, có thể thấy ưu tiên của họ không phải là hiệu năng cao, nên việc chọn Go là hợp lý với hướng đi đó.
Khi tìm kiếm phương án thay thế InfluxDB, tôi đã bắt gặp TimescaleDB. TimescaleDB ra đời sau InfluxDB nên lúc tôi biết về nó thì nó vẫn chưa đủ tính năng để tôi có thể đem về dùng. Tuy nhiên, tôi đã thấy ngay triển vọng về hiệu năng cao, vì:
- TimescaleDB được viết dưới dạng extension của PostgreSQL, nên cũng viết bằng C.
- Cũng vì là extension của PostgreSQL nên giao tiếp với nó bằng giao thức của PostgreSQL, không đụng phải overhead như HTTP của InfluxDB.
- Khi dùng giao thức của PostgreSQL thì tôi có cơ hội dùng một thư viện tốc độ rất cao của Python là asyncpg (theo benchmark trong link thì nó còn ăn đứt cả Go).
Khi nhìn thấy tiềm năng đó thì tôi rất háo hức, và chờ đợi. Chờ đợi mãi khi PostgreSQL chuyển dần dần từ 9.6 lên 10, 11 thì mới thấy TimescaleDB có đủ tính năng tôi cần. Cũng phải mất hơn nửa năm để tôi sắp xếp công việc, thời gian để có thể bắt tay vào thực hiện một sự thay đổi lớn về cấu trúc công nghệ bên trong platform IoT của AgriConnect.
Ban đầu có một trở ngại suýt nữa khiến tôi phải đình chỉ công việc, đó là TimescaleDB không cung cấp gói *.deb cho BeagleBone Black. May thay tôi tìm được script đóng gói cho Ubuntu của họ trên PPA, đem về chỉnh chọt một hồi cũng ra được script đóng gói dành cho BeagleBone. Các gói *.deb dành cho BeagleBone đang được lưu trên kho của AgriConnect.
Khi tôi viết bài này, phần mềm của AgriConnect đã chạy với TimescaleDB được 3 tuần trên các server của khách hàng. Sau 3 tuần quan sát thì thấy sự khác biệt thật rõ rệt. Nếu như trước đây tôi phải dành riêng một server chỉ để chạy mỗi InfluxDB, giảm tải cho khác server khác thì nay server đó phải thất nghiệp ngồi không. Ngay cả khi lần này đã mang lớp ORM trở lại thì vẫn chạy nhẹ hơn cả khi dùng InfluxDB với lớp ORM/ODM bị lược bỏ.