Tối ưu hóa lưu trữ Time-Series: Cách thiết kế quyết định chi phí và hiệu năng
Bài viết phân tích sâu về các quyết định thiết kế lưu trữ dữ liệu chuỗi thời gian, từ việc chuẩn hóa lược đồ đến lựa chọn lưu trữ dạng cột. Các thí nghiệm thực tế cho thấy cấu trúc dữ liệu và chiến lược phân vùng ảnh hưởng trực tiếp đến chi phí và hiệu suất truy vấn hơn cả việc chọn loại cơ sở dữ liệu.

Tối ưu hóa lưu trữ Time-Series: Cách thiết kế quyết định chi phí và hiệu năng
Mọi cơ sở dữ liệu chuỗi thời gian (time-series database) đều phải đối mặt với một tập hợp các quyết định thiết kế lưu trữ: cách sắp xếp các hàng (rows), khi nào nén dữ liệu, và dựa trên tiêu chí nào để phân vùng. Những quyết định này quyết định chi phí và hiệu suất truy vấn mạnh mẽ hơn nhiều so với việc lựa chọn chính xác loại cơ sở dữ liệu nào. Bài viết này sẽ đi sâu vào những nguyên tắc cơ bản đó, sử dụng các công cụ phổ biến như PostgreSQL và Apache Parquet để đo lường và đánh giá sự đánh đổi giữa các phương án thiết kế.
Dữ liệu chuỗi thời gian là gì?
Dữ liệu chuỗi thời gian là một chuỗi các phép đo được ghi lại theo thời gian. Trong khi một bản ghi cơ sở dữ liệu tiêu chuẩn thường theo dõi trạng thái hiện tại (ví dụ: "Số dư tài khoản là 50$"), dữ liệu chuỗi thời gian lại theo dõi lịch sử của các thay đổi (ví dụ: "Số dư tại 10:00, 10:01, 10:02...").
Các hệ thống hiện đại phát ra các tín hiệu liên tục về tình trạng và hoạt động:
- Sinh trắc học: Đồng hồ thông minh ghi nhịp tim mỗi 5 giây để theo dõi cường độ tập luyện.
- Hậu cần: Ứng dụng gọi xe theo dõi tọa độ GPS để tính tiền và thời gian đến.
- Tài chính: Bảng giá thị trường bắt hàng nghìn bản cập nhật giá mỗi giây.
- Cơ sở hạ tầng: Các công cụ giám sát theo dõi xu hướng CPU để dự đoán sự cạn kiệt tài nguyên.
Cảm biến rẻ tiền và lưu trữ giá rẻ cho phép ghi lại hầu hết mọi thứ ngay lập tức với toàn bộ lịch sử, không chỉ là các ảnh chụp nhanh. Điểm dữ liệu cốt lõi tuân theo một hình dạng lặp lại: dấu thời gian (timestamp), định danh (identifier) và giá trị (value). Sự đơn giản này có tính đánh lừa, bởi ở quy mô lớn, sự lặp lại chính là vấn đề.
Lưu trữ quan hệ: Phẳng (Flat) so với Chuẩn hóa (Normalized)
Để lưu trữ dữ liệu chuỗi thời gian trong một cơ sở dữ liệu quan hệ như PostgreSQL, chúng ta có thể lưu trữ tất cả các thuộc tính trong một bảng phẳng duy nhất hoặc chuẩn hóa các định danh vào một sổ đăng ký riêng.
Lược đồ Phẳng (Flat Schema)
Lược đồ phẳng lưu trữ mọi chiều (dimension) và chỉ số (metric) trong mỗi hàng. Bố cục này dễ triển khai và truy vấn nhưng gây ra sự dư thừa dữ liệu rất cao.
Lược đồ Chuẩn hóa (Normalized Schema)
Chuẩn hóa chuyển các định danh ổn định vào một bảng series_dim. Mỗi phép đo tham chiếu đến một series_id thay vì lặp lại các chuỗi ký tự.
So sánh chi phí lưu trữ giữa lược đồ phẳng và chuẩn hóa
Chi phí lưu trữ: Kết quả thí nghiệm
Chúng tôi đã đo lường chi phí của sự lặp lại bằng một thí nghiệm trên PostgreSQL 16 với một nghìn chuỗi và 2,8 triệu hàng. Việc chuẩn hóa đã giảm tổng dung lượng lưu trữ khoảng bốn mươi hai phần trăm (tiết kiệm 289 MB).
Hiệu quả này đến từ việc nhân các byte chiều dữ liệu. Với lược đồ phẳng, tổng số byte ≈ Số_hàng * (dấu thời gian + metric + chiều). Với lược đồ chuẩn hóa, tổng số byte ≈ Số_hàng * (dấu thời gian + metric + series_id) + Số_chuỗi * chiều.
Trong lược đồ chuẩn hóa, mỗi hàng chỉ mang một ID chuỗi nhỏ, trong khi các chuỗi ký tự chiều dữ liệu chỉ được lưu trữ một lần cho mỗi chuỗi duy nhất.
Độ cơ sở cao (High Cardinality): Khi Chuẩn hóa thất bại
Chuẩn hóa giúp ích khi nhiều hàng chia sẻ cùng một định danh. Lợi ích này sẽ thu hẹp lại khi các trường định danh có độ cơ sở cao và gần như duy nhất cho mỗi hàng.
Ví dụ, nếu request_id là một phần của định danh chuỗi, số lượng chuỗi sẽ tiệm cận số lượng hàng. Trong trường hợp này, việc loại bỏ trùng lặp trong lưu trữ chuẩn hóa trở nên kém hiệu quả. Về mặt thực tế, hãy giữ các chiều ổn định trong định danh chuỗi và giữ các ID theo sự kiện như request_id ở ngoài định danh chuỗi trừ khi mẫu truy vấn yêu cầu.
Thiết kế cho sự tiến hóa của lược đồ (Schema Evolution)
Một lược đồ với các cột cố định hoạt động tốt cho đến khi các thẻ (tags) phát triển. Các thẻ mới buộc phải thay đổi bảng, điền lại dữ liệu (backfill) và làm xáo trộn chỉ mục. Đối với các hệ thống chuỗi thời gian, một mô hình thực tế là giữ một sổ đăng ký series_dim được khóa bởi series_id và lưu trữ các thuộc tính chuỗi trong một trường chiều jsonb.
Cách tiếp cận này giữ cho việc nhập dữ liệu ổn định khi các thẻ thay đổi. Tuy nhiên, đánh đổi ở đây là việc lập chỉ mục trở thành một quyết định chính sách, không phải là một nhiệm vụ DDL một lần. Cần lưu ý các vấn đề như sự tăng trưởng của thẻ, sự lan rộng chỉ mục (index sprawl) và sự trôi loại dữ liệu (type drift).
Lưu trữ dạng cột (Columnar Storage) như đòn bẩy tiếp theo
Sau khi kiểm soát được định danh chuỗi, độ cơ sở và sự tiến hóa của lược đồ, đòn bẩy tiếp theo là bố cục lưu trữ. Lưu trữ dạng cột giữ mỗi cột lại với nhau. Đối với khối lượng công việc chuỗi thời gian, cách tiếp cận này có thể phục hồi một phần lợi ích lưu trữ của việc chuẩn hóa ngay cả với mô hình logic phẳng hơn, vì các giá trị chiều lặp lại nén tốt và các truy vấn quét ít byte hơn.
So sánh lưu trữ giữa PostgreSQL chuẩn hóa và Parquet dạng cột
Lưu trữ dạng cột và các công cụ truy vấn
Parquet là định dạng tệp, không phải là cơ sở dữ liệu. Việc chọn lưu trữ dạng cột cũng có nghĩa là chọn nơi các tệp đó sống và cái gì đọc chúng.
- Parquet trên Object Storage (S3): Tách chi phí lưu trữ khỏi tính toán; bạn trả giá hàng hóa để lưu giữ và chỉ trả tiền cho tính toán khi truy vấn.
- Apache Iceberg: Thêm một lớp siêu dữ liệu trên các tệp Parquet để giải quyết các vấn đề về tiến hóa lược đồ, giao dịch ACID và quản lý phân vùng tự động.
Trong thí nghiệm của chúng tôi với 2,8 triệu hàng, Parquet dạng cột đã chứng minh hiệu quả nén vượt trội so với PostgreSQL nhờ mã hóa từ điển (dictionary encoding) áp dụng cho mọi cột.
Lược đồ Rộng (Wide) so với Hẹp (Narrow) cho nhiều chỉ số
Khi nhiều chỉ số được phát ra cùng một dấu thời gian, hình dạng hàng thay đổi cả chi phí lưu trữ và độ phức tạp của truy vấn.
- Lược đồ Hẹp (Narrow): Lưu trữ một hàng cho mỗi chỉ số. Điều này lặp lại
(series_id, ts)cho mỗi hàng chỉ số. - Lược đồ Rộng (Wide): Lưu trữ một hàng với nhiều cột chỉ số. Điều này lưu trữ
(series_id, ts)một lần cho dấu thời gian đó.
Nếu một truy vấn cần nhiều chỉ số cùng một lúc, lược đồ hẹp thường cần một phép xoay (pivot) hoặc tự kết nối (self join), trong khi lược đồ rộng có thể trả về các chỉ số đó từ một lần đọc hàng đơn giản.
Quy tắc thực tế: Sử dụng rộng khi tập hợp chỉ số nhỏ, ổn định và được phát ra cùng nhau. Sử dụng hẹp khi các chỉ số thưa thớt, động hoặc được lấy mẫu độc lập.
Phân vùng (Partitioning): Thời gian trước, Không gian sau
Khi dữ liệu tăng lên, một bảng khổng lồ trở nên khó truy vấn hiệu quả và tốn kém hơn để bảo trì. Phân vùng chia tập dữ liệu thành các khối nhỏ hơn, có thể quản lý độc lập.
Phân vùng theo thời gian
Thời gian là trục phân vùng tự nhiên. Chia dữ liệu thành các khối theo phạm vi thời gian mang lại ba lợi ích:
- Cắt bỏ phân vùng (Partition pruning): Truy vấn cho giờ cuối cùng chỉ chạm phân vùng hiện tại.
- Hết hạn rẻ: Xóa một phân vùng cũ là thao tác siêu dữ liệu O(1).
- Bảo trì độc lập: Mỗi phân vùng có thể được làm sạch, lập chỉ mục hoặc phân tích độc lập.
Phân vùng hai chiều: Thời gian và Không gian
Ghi chuỗi thời gian có một đặc tính cụ thể: chúng tập trung quanh "bây giờ". Nếu bạn chỉ phân vùng theo thời gian, phân vùng hiện tại sẽ hấp thụ tất cả lưu lượng ghi trong khi các phân vùng lịch sử ngồi không. Đây là một điểm nóng ghi (write hotspot).
Giải pháp là phân vùng trên hai trục: thời gian và định danh chuỗi. Mỗi phân vùng kết quả bao gồm một phạm vi thời gian và một tập hợp con của chuỗi. Cách tiếp cận này phân phối các ghi trên nhiều phân vùng ngay cả trong cửa sổ thời gian hiện tại.
Trực quan hóa phân vùng chuỗi thời gian hai chiều
Giảm mẫu (Downsampling) và Lưu trữ
Dữ liệu chuỗi thời gian thô có vòng đời hữu ích ngắn ở độ phân giải đầy đủ. Việc đọc CPU năm giây quan trọng trong một sự cố đang diễn ra. Một tuần sau, xu hướng cấp phút là đủ. Một năm sau, trung bình cấp giờ là tất cả những gì bất kỳ ai truy vấn.
Giảm mẫu giảm độ phân giải dữ liệu theo thời gian bằng cách tính toán trước các tổng hợp ở các khoảng thô hơn. Chính sách lưu trữ xác định khi nào loại bỏ dữ liệu hoàn toàn. Cùng nhau, chúng kiểm soát đường cong tăng trưởng của chi phí lưu trữ.
Ví dụ, đi từ độ phân giải năm giây đến một phút cắt giảm số hàng gấp 12 lần. Đi từ một phút đến một giờ cắt giảm thêm 60 lần. Một chuỗi tạo ra 17.280 điểm thô mỗi ngày trở thành 1.440 ở mức một phút và 24 ở mức một giờ, mức giảm 720 lần.
Kết luận
Các quyết định lưu trữ bên dưới một cơ sở dữ liệu chuỗi thời gian — cách bạn mô hình hóa định danh, phân vùng trên cái gì, khi nào giảm mẫu và cách sắp xếp các cột — quyết định chi phí và hiệu suất mạnh mẽ hơn cả việc lựa chọn cơ sở dữ liệu. Những nguyên tắc cơ bản này áp dụng dù bạn đang chạy PostgreSQL, Parquet trên S3 hay một dịch vụ chuỗi thời gian được quản lý. Nếu làm đúng những điều này, bạn sẽ tránh được một lớp vấn đề xuất hiện dưới dạng hóa đơn bất ngờ và các bảng điều khiển chậm chạp bất kể bạn chọn cơ sở dữ liệu nào.
Bài viết liên quan

Phần mềm
Intel và AMD vá tổng cộng 70 lỗ hổng bảo mật trong Patch Tuesday tháng 5
13 tháng 5, 2026

Phần mềm
Google tung ra Antigravity 2.0: Ứng dụng lập trình thế hệ mới với công cụ CLI và gói đăng ký AI Ultra
19 tháng 5, 2026

Phần mềm
Plugin Checkmarx Jenkins bị xâm phạm trong cuộc tấn công chuỗi cung ứng
11 tháng 5, 2026
