Mở rộng cơ sở dữ liệu Postgres là một nghi thức thông thường cho các ứng dụng đang phát triển. Khi bạn thấy các bảng của mình mở rộng với hàng triệu hoặc thậm chí hàng tỷ hàng, các truy vấn nhanh chóng của bạn bắt đầu bị trễ và chi phí cơ sở hạ tầng ngày càng tăng bắt đầu tạo ra một bóng đen dài trên kết quả kinh doanh của bạn. Bạn đang rơi vào một câu hỏi hóc búa: bạn không muốn chia tay PostgreSQL yêu quý của mình, nhưng có vẻ như bạn sẽ cần một cách hiệu quả hơn để xử lý các tập dữ liệu ngày càng tăng của mình.
Trong bài viết này, chúng tôi sẽ kể cho bạn câu chuyện về cách chúng tôi xây dựng cơ chế nén cột linh hoạt, hiệu suất cao cho PostgreSQL để cải thiện khả năng mở rộng của nó. Bằng cách kết hợp lưu trữ theo cột với các thuật toán nén chuyên dụng, chúng tôi có thể đạt được tốc độ nén ấn tượng chưa từng có trong bất kỳ cơ sở dữ liệu quan hệ nào khác (+95%).
Bằng cách nén tập dữ liệu, bạn có thể phát triển cơ sở dữ liệu PostgreSQL của mình hơn nữa. Như chúng ta sẽ thấy trong bài viết này, thiết kế nén hiệu quả cao này cho phép bạn giảm kích thước của các bảng PostgreSQL lớn lên tới 10-20 lần. Bạn có thể lưu trữ nhiều dữ liệu hơn trên các ổ đĩa nhỏ hơn (hay còn gọi là tiết kiệm tiền) đồng thời cải thiện hiệu suất truy vấn. Tính năng nén theo thang thời gian cũng hoàn toàn có thể thay đổi, giúp việc quản lý và vận hành cơ sở dữ liệu trở nên dễ dàng: bạn có thể thêm, thay đổi và thả các cột trong bảng nén cũng như có thể CHÈN, CẬP NHẬT và XÓA dữ liệu trực tiếp.
Chào mừng bạn đến với PostgreSQL có khả năng mở rộng cao hơn!
segmentby
segmentby
orderby
Nhưng trước khi đi vào chi tiết về cách chúng tôi xây dựng tính năng nén, hãy dành vài phút để trả lời câu hỏi này: tại sao lại cần thêm cơ chế nén cơ sở dữ liệu mới vào PostgreSQL?
Trước tiên chúng ta hãy hiểu nhu cầu của các ứng dụng hiện đại và một chút về lịch sử phần mềm.
Chúng tôi yêu thích Postgres: chúng tôi tin rằng đây là nền tảng tốt nhất để xây dựng các ứng dụng vì sự kết hợp giữa độ tin cậy, tính linh hoạt và hệ sinh thái phong phú mà bất kỳ cơ sở dữ liệu nào khác khó có thể sánh được. Nhưng Postgres đã ra đời cách đây nhiều thập kỷ—sự mạnh mẽ này không phải không có nhược điểm.
Ngày nay, các nhà phát triển đang sử dụng PostgreSQL cho nhiều mục đích hơn là trường hợp sử dụng OLTP (Xử lý giao dịch trực tuyến) truyền thống mà nó được biết đến nhiều nhất. Nhiều ứng dụng đòi hỏi nhiều dữ liệu—chạy 24/7 và xử lý khối lượng dữ liệu ngày càng tăng—được cung cấp bởi PostgreSQL:
Cơ sở dữ liệu PostgreSQL đang được sử dụng để tiếp nhận một lượng lớn dữ liệu cảm biến từ hệ thống quản lý giao thông, mạng tiện ích và màn hình an toàn công cộng.
Các công ty năng lượng đang sử dụng PostgreSQL để lưu trữ và phân tích số liệu từ lưới điện thông minh và các nguồn năng lượng tái tạo.
Trong lĩnh vực tài chính, PostgreSQL là cốt lõi của hệ thống theo dõi dữ liệu đánh dấu thị trường trong thời gian thực.
Các nền tảng thương mại điện tử đang sử dụng PostgreSQL để theo dõi và phân tích các sự kiện do tương tác của người dùng tạo ra.
Postgres thậm chí còn được sử dụng làm cơ sở dữ liệu vector để hỗ trợ làn sóng ứng dụng AI mới.
Do đó, các bảng Postgres đang phát triển rất nhanh và việc các bảng có hàng tỷ hàng là điều bình thường mới trong quá trình sản xuất.
Thật không may, PostgreSQL vốn không được trang bị đầy đủ để xử lý khối lượng dữ liệu này: hiệu suất truy vấn bắt đầu bị chậm và việc quản lý cơ sở dữ liệu trở nên khó khăn. Để giải quyết những hạn chế này, chúng tôi đã xây dựng
Xây dựng cơ chế nén hiệu suất cao cho PostgreSQL là một bước mở khóa quan trọng tương tự. Những bộ dữ liệu ngày càng phát triển này không chỉ là thách thức để đạt được hiệu suất tốt mà việc tích lũy dữ liệu của chúng còn dẫn đến ổ đĩa ngày càng lớn hơn và chi phí lưu trữ cao hơn. PostgreSQL cần một giải pháp.
Nhưng còn phương pháp TOAST hiện tại của PostgreSQL thì sao? Mặc dù có cái tên tuyệt vời 🍞😋,
TOAST là cơ chế tự động mà PostgreSQL sử dụng để lưu trữ và quản lý các giá trị lớn không vừa với các trang cơ sở dữ liệu riêng lẻ. Mặc dù TOAST kết hợp tính năng nén như một trong những kỹ thuật để đạt được điều này nhưng vai trò chính của TOAST không phải là tối ưu hóa không gian lưu trữ trên bảng.
Ví dụ: nếu bạn có cơ sở dữ liệu 1 TB được tạo thành từ các bộ dữ liệu nhỏ, TOAST sẽ không giúp bạn biến 1 TB đó thành 80 GB một cách có hệ thống, bất kể bạn có cố gắng tinh chỉnh đến mức nào. TOAST sẽ tự động nén các thuộc tính quá khổ trong một hàng khi chúng vượt quá ngưỡng 2 KB, nhưng TOAST không hỗ trợ các giá trị nhỏ (bộ dữ liệu), bạn cũng không thể áp dụng các cấu hình nâng cao hơn do người dùng định cấu hình như nén tất cả dữ liệu cũ hơn một tháng trong một bảng cụ thể. Quá trình nén của TOAST hoàn toàn dựa trên kích thước của các giá trị cột riêng lẻ, không dựa trên các đặc điểm của bảng hoặc tập dữ liệu rộng hơn.
TOAST cũng có thể tạo ra chi phí I/O đáng kể, đặc biệt đối với các bảng lớn có các cột quá khổ được truy cập thường xuyên. Trong những trường hợp như vậy, PostgreSQL cần truy xuất dữ liệu ngoại tuyến từ bảng TOAST, đây là một thao tác I/O riêng biệt với việc truy cập bảng chính, vì PostgreSQL phải tuân theo các con trỏ từ bảng chính đến bảng TOAST để đọc dữ liệu đầy đủ. Điều này thường dẫn đến hiệu suất kém hơn.
Cuối cùng, tính năng nén của TOAST không được thiết kế để cung cấp tỷ lệ nén đặc biệt cao vì nó sử dụng một thuật toán tiêu chuẩn cho tất cả các loại dữ liệu.
Việc đề cập nhanh đến TOAST này cũng giúp chúng ta hiểu được những hạn chế của PostgreSQL trong việc nén dữ liệu một cách hiệu quả. Như chúng ta vừa thấy, tính năng nén của TOAST xử lý dữ liệu theo từng hàng, nhưng kiến trúc hướng hàng này làm phân tán tính đồng nhất mà các thuật toán nén phát triển mạnh, dẫn đến giới hạn cơ bản về cách thức hoạt động của tính năng nén. Đây là lý do cơ bản tại sao các cơ sở dữ liệu quan hệ (như Postgres gốc) thường gặp khó khăn khi tối ưu hóa lưu trữ.
Hãy phá vỡ điều này. Theo truyền thống, cơ sở dữ liệu thuộc một trong hai loại:
Cơ sở dữ liệu hướng hàng sắp xếp dữ liệu theo hàng, trong đó mỗi hàng chứa tất cả dữ liệu cho một bản ghi cụ thể. Chúng được tối ưu hóa để xử lý giao dịch trong đó việc chèn, cập nhật và xóa bản ghi diễn ra thường xuyên và chúng hiệu quả đối với các hệ thống OLTP nơi các hoạt động liên quan đến các bản ghi riêng lẻ hoặc tập hợp dữ liệu nhỏ (ví dụ: truy xuất tất cả thông tin về một khách hàng cụ thể).
Mặt khác, cơ sở dữ liệu hướng theo cột (còn gọi là “cột”) sắp xếp dữ liệu theo cột. Mỗi cột lưu trữ tất cả dữ liệu cho một thuộc tính cụ thể trên nhiều bản ghi. Chúng thường được tối ưu hóa cho các hệ thống OLAP (Xử lý phân tích trực tuyến), trong đó các truy vấn thường liên quan đến việc tổng hợp và thao tác trên nhiều bản ghi.
Hãy minh họa điều này bằng một ví dụ. Giả sử chúng ta có một bảng users
có bốn cột: user_id
, name
, logins
và last_login
. Nếu bảng này lưu trữ dữ liệu cho một triệu người dùng, thì thực tế nó sẽ có một triệu hàng và bốn cột, lưu trữ vật lý dữ liệu của từng người dùng (tức là mỗi hàng) liên tục trên đĩa.
Trong thiết lập hướng hàng này, toàn bộ hàng cho user_id = 500.000 được lưu trữ liên tục, giúp việc truy xuất nhanh chóng. Do đó, các truy vấn nông và rộng sẽ nhanh hơn trên một cửa hàng theo hàng (ví dụ: “tìm nạp tất cả dữ liệu cho người dùng X”):
-- Create table CREATE TABLE users ( user_id SERIAL PRIMARY KEY, name VARCHAR(100), logins INT DEFAULT 0, last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Assume we have inserted 1M user records into the 'users' table -- Shallow-and-wide query example (faster in row store) SELECT * FROM users WHERE user_id = 500000;
Ngược lại, kho lưu trữ theo cột sẽ lưu trữ tất cả user_id
cùng nhau, tất cả các tên cùng nhau, tất cả các giá trị đăng nhập cùng nhau, v.v., để dữ liệu của mỗi cột được lưu trữ liên tục trên đĩa. Kiến trúc cơ sở dữ liệu này ưu tiên các truy vấn sâu và hẹp, ví dụ: “tính số lần đăng nhập trung bình cho tất cả người dùng”:
-- Deep-and-narrow query example (faster in column store) SELECT AVG(logins) FROM users;
Cửa hàng cột hoạt động đặc biệt tốt với các truy vấn hẹp trên dữ liệu rộng . Trong cơ sở dữ liệu cột, chỉ cần đọc dữ liệu cột logins
để tính giá trị trung bình, việc này có thể được thực hiện mà không cần phải tải toàn bộ tập dữ liệu cho mỗi người dùng từ đĩa.
Như bạn có thể đoán bây giờ, việc lưu trữ dữ liệu theo hàng và cột cũng có ảnh hưởng đến mức độ nén dữ liệu. Trong cơ sở dữ liệu cột, các cột dữ liệu riêng lẻ thường có cùng loại và thường được lấy từ một miền hoặc phạm vi hạn chế hơn.
Kết quả là, các kho lưu trữ theo cột thường nén tốt hơn cơ sở dữ liệu định hướng theo hàng. Ví dụ: cột logins
của chúng tôi trước đây đều thuộc loại số nguyên và có thể chỉ bao gồm một phạm vi nhỏ các giá trị số (và do đó có entropy thấp, nén tốt). So sánh điều đó với định dạng hướng hàng, trong đó toàn bộ hàng dữ liệu rộng bao gồm nhiều loại và phạm vi dữ liệu khác nhau.
Nhưng ngay cả khi chúng thể hiện những lợi thế về khả năng nén và truy vấn kiểu OLAP, các cửa hàng dạng cột không phải là không có sự đánh đổi:
Các truy vấn truy xuất các hàng riêng lẻ có hiệu suất kém hơn nhiều (đôi khi thậm chí không thể chạy được).
Kiến trúc của chúng không phù hợp lắm với các giao dịch ACID truyền thống.
Thường không thể thực hiện cập nhật trong các cửa hàng dạng cột.
Các cửa hàng theo hàng sẽ dễ dàng tận dụng lợi thế hơn
Với lưu trữ theo hàng, việc chuẩn hóa tập dữ liệu của bạn sẽ dễ dàng hơn, nhờ đó bạn có thể lưu trữ các tập dữ liệu liên quan trong các bảng khác một cách hiệu quả hơn.
Vì vậy, cái gì tốt hơn: định hướng theo hàng hay theo cột?
Theo truyền thống, bạn sẽ đánh giá sự cân bằng giữa cả hai tùy thuộc vào khối lượng công việc của bạn. Nếu bạn đang chạy một trường hợp sử dụng OLTP điển hình, có thể bạn sẽ chọn cơ sở dữ liệu quan hệ hướng hàng như PostgreSQL; nếu trường hợp sử dụng của bạn rõ ràng là OLAP, bạn có thể nghiêng về cửa hàng dạng cột như ClickHouse.
Nhưng điều gì sẽ xảy ra nếu khối lượng công việc của bạn thực sự là sự kết hợp của cả hai?
Các truy vấn ứng dụng của bạn thường có thể nông và rộng, với một truy vấn riêng lẻ truy cập vào nhiều cột dữ liệu cũng như dữ liệu trên nhiều thiết bị/máy chủ/mục khác nhau. Ví dụ: bạn có thể đang cung cấp khả năng hiển thị trực quan cho người dùng yêu cầu hiển thị nhiệt độ và độ ẩm được ghi lại gần đây nhất cho tất cả các cảm biến trong một nhà máy sản xuất cụ thể. Một truy vấn như vậy sẽ cần truy cập vào nhiều cột trên tất cả các hàng phù hợp với tiêu chí xây dựng, có khả năng bao gồm hàng nghìn hoặc hàng triệu bản ghi.
Tuy nhiên, một số truy vấn của bạn cũng có thể sâu và hẹp, với một truy vấn riêng lẻ sẽ chọn số lượng cột nhỏ hơn cho một cảm biến cụ thể trong một khoảng thời gian dài hơn. Ví dụ: bạn cũng có thể cần phân tích xu hướng nhiệt độ của một thiết bị cụ thể trong tháng qua để kiểm tra các điểm bất thường. Loại truy vấn này sẽ tập trung vào một cột duy nhất (nhiệt độ) nhưng sẽ cần truy xuất thông tin này từ một số lượng lớn các hàng tương ứng với từng khoảng thời gian trong khoảng thời gian mục tiêu.
Ứng dụng của bạn cũng có thể sử dụng nhiều dữ liệu và chèn (chắp thêm) nặng. Như chúng ta đã thảo luận trước đây, việc xử lý hàng trăm nghìn lượt ghi mỗi giây là điều bình thường mới. Bộ dữ liệu của bạn có thể cũng rất chi tiết, ví dụ: bạn có thể thu thập dữ liệu mỗi giây. Tiếp tục với ví dụ trước, cơ sở dữ liệu của bạn sẽ cần phải xử lý các thao tác ghi nặng này cùng với các lần đọc liên tục để hỗ trợ khả năng hiển thị trực quan của người dùng trong thời gian thực.
Dữ liệu của bạn là phần lớn phần bổ sung , nhưng không nhất thiết chỉ phần bổ sung . Bạn có thể cần thỉnh thoảng cập nhật các bản ghi cũ hoặc có thể ghi lại dữ liệu đến muộn hoặc không đúng thứ tự.
Khối lượng công việc này không phải là OLTP hay OLAP theo nghĩa truyền thống. Thay vào đó, nó bao gồm các yếu tố của cả hai. Vậy lam gi?
Đi lai!
Để phục vụ khối lượng công việc như ví dụ trước, một cơ sở dữ liệu sẽ phải bao gồm những thành phần sau:
Khả năng duy trì tốc độ chèn cao, dễ dàng với hàng trăm nghìn lượt ghi mỗi giây
Hỗ trợ chèn dữ liệu trễ hoặc không đúng thứ tự cũng như sửa đổi dữ liệu hiện có
Đủ linh hoạt để xử lý hiệu quả cả truy vấn nông và rộng cũng như sâu và hẹp trên một tập dữ liệu lớn
Cơ chế nén có thể giảm đáng kể kích thước cơ sở dữ liệu để cải thiện hiệu quả lưu trữ
Đây là những gì chúng tôi mong muốn đạt được khi thêm tính năng nén cột vào TimescaleDB (và do đó, vào PostgreSQL).
Như chúng tôi đã đề cập trong phần trước, chúng tôi đã xây dựng TimescaleDB để mở rộng PostgreSQL với hiệu suất và khả năng mở rộng cao hơn, giúp nó phù hợp với khối lượng công việc đòi hỏi khắt khe như
Về lý thuyết, điều này có nghĩa là TimescaleDB cũng bị khóa trong định dạng lưu trữ theo hàng của PostgreSQL, với khả năng nén khiêm tốn. Trên thực tế, không có gì mà một chút kỹ thuật không thể giải quyết được.
Hai quan sát. Đầu tiên,
Trên thực tế, việc chuyển đổi từ hàng sang cột này không cần phải áp dụng cho toàn bộ cơ sở dữ liệu của bạn. Là người dùng Timescale, bạn có thể chuyển đổi các bảng PostgreSQL của mình thành các cửa hàng cột hàng kết hợp, chọn chính xác dữ liệu nào sẽ nén ở dạng cột
Hãy minh họa cách thực tế này hoạt động bằng một ví dụ. Hãy tưởng tượng một hệ thống giám sát nhiệt độ thu thập số đọc mỗi giây từ nhiều thiết bị, lưu trữ dữ liệu như dấu thời gian, ID thiết bị, mã trạng thái và nhiệt độ.
Để truy cập dữ liệu nhiệt độ gần đây nhất một cách hiệu quả, đặc biệt đối với các truy vấn vận hành mà bạn có thể muốn phân tích các kết quả đọc mới nhất từ các thiết bị khác nhau, bạn có thể giữ dữ liệu gần đây nhất (ví dụ: tuần trước) ở cấu trúc PostgreSQL hướng hàng, không nén truyền thống . Điều này hỗ trợ tỷ lệ nhập cao và cũng rất tốt cho các truy vấn điểm về dữ liệu gần đây:
-- Find the most recent data from a specific device SELECT * FROM temperature_data WHERE device_id = 'A' ORDER BY timestamp DESC LIMIT 1; -- Find all devices in the past hour that are above a temperature threshold SELECT DISTINCT device_id, MAX(temperature) FROM temperature WHERE timestamp > NOW() - INTERVAL '1 hour' AND temperature > 40.0;
Nhưng sau khi dữ liệu này được vài ngày, các truy vấn nông và rộng như dữ liệu trước đó không còn được chạy nữa: thay vào đó, các truy vấn phân tích sâu và hẹp sẽ phổ biến hơn. Vì vậy, để cải thiện hiệu quả lưu trữ và hiệu suất truy vấn cho loại truy vấn này, bạn có thể tự động chọn chuyển đổi tất cả dữ liệu cũ hơn một tuần thành định dạng cột được nén ở mức độ cao. Để làm như vậy trong Timescale, bạn sẽ xác định chính sách nén như sau:
-- Add a compression policy to compress temperature data older than 1 week SELECT add_compression_policy('temperature_data', INTERVAL '7 days');
Sau khi dữ liệu của bạn được nén, việc chạy các truy vấn phân tích sâu và hẹp trên dữ liệu nhiệt độ (cho dù trên một thiết bị cụ thể hay trên nhiều thiết bị) sẽ cho hiệu suất truy vấn tối ưu.
-- Find daily max temperature for a specific device across past year SELECT time_bucket('1 day', timestamp) AS day, MAX(temperature) FROM temperature_data WHERE timestamp > NOW() - INTERVAL '1 year' AND device_id = 'A' ORDER BY day; -- Find monthly average temperatures across all devices SELECT device_id, time_bucket('1 month', timestamp) AS month, AVG(temperature) FROM temperature_data WHERE timestamp < NOW() - INTERVAL '2 weeks' GROUP BY device_id, month ORDER BY month;
Làm cách nào để thể hiện sự “chuyển đổi” từ định dạng hàng sang cột? Siêu bảng của Timescale dùng để phân vùng dữ liệu thành các “khối” dựa trên khóa phân vùng, chẳng hạn như dấu thời gian hoặc cột ID nối tiếp khác. Sau đó, mỗi đoạn lưu trữ các bản ghi tương ứng với một phạm vi dấu thời gian nhất định hoặc các giá trị khác cho khóa phân vùng đó. Trong ví dụ trên, dữ liệu nhiệt độ sẽ được phân chia theo tuần sao cho phần mới nhất vẫn ở định dạng hàng và tất cả các tuần cũ hơn được chuyển đổi sang định dạng cột.
Công cụ lưu trữ cột-hàng kết hợp này là một công cụ cực kỳ mạnh mẽ để tối ưu hóa hiệu suất truy vấn trong cơ sở dữ liệu PostgreSQL lớn đồng thời giảm đáng kể dung lượng lưu trữ. Như chúng ta sẽ thấy ở phần sau của bài viết này, bằng cách chuyển đổi dữ liệu sang định dạng cột và áp dụng các cơ chế nén chuyên dụng, chúng tôi không chỉ có thể tăng tốc các truy vấn phân tích của bạn mà còn đạt được tỷ lệ nén lên tới 98%. Hãy tưởng tượng điều này sẽ ảnh hưởng như thế nào tới hóa đơn lưu trữ của bạn!
Trước khi đi vào chi tiết về hiệu suất truy vấn và tiết kiệm dung lượng lưu trữ, trước tiên hãy tìm hiểu cách hoạt động cơ bản của cơ chế này: cách thực hiện chuyển đổi từ hàng sang cột và cách áp dụng nén cho dữ liệu cột.
Khi chính sách nén bắt đầu hoạt động, về cơ bản, nó sẽ biến đổi những bản ghi riêng lẻ theo truyền thống trong siêu bảng PostgreSQL ban đầu—hãy tưởng tượng 1.000 hàng dày đặc—thành một cấu trúc hàng đơn lẻ, nhỏ gọn hơn. Trong dạng nén này, mỗi thuộc tính hoặc cột không còn lưu trữ các mục từ mỗi hàng nữa. Thay vào đó, nó gói gọn một chuỗi có thứ tự liên tục của tất cả các giá trị tương ứng từ 1.000 hàng này. Hãy coi 1.000 hàng này là một lô .
Để minh họa, hãy tưởng tượng một bảng như thế này:
| Timestamp | Device ID | Status Code | Temperature | |-----------|-----------|-------------|-------------| | 12:00:01 | A | 0 | 70.11 | | 12:00:01 | B | 0 | 69.70 | | 12:00:02 | A | 0 | 70.12 | | 12:00:02 | B | 0 | 69.69 | | 12:00:03 | A | 0 | 70.14 | | 12:00:03 | B | 4 | 69.70 |
Để chuẩn bị nén dữ liệu này, Timescale trước tiên sẽ chuyển đổi dữ liệu dạng bảng này thành kho lưu trữ theo cột. Với một lô dữ liệu (~1.000 hàng), dữ liệu của mỗi cột được tổng hợp thành một mảng, với mỗi phần tử mảng tương ứng với giá trị từ một trong các hàng ban đầu. Quá trình này tạo ra một hàng duy nhất, trong đó mỗi cột lưu trữ một mảng giá trị từ lô đó.
| Timestamp | Device ID | Status Code | Temperature | |------------------------------|--------------------|--------------------|-------------------------------| | [12:00:01, 12:00:01, 12...] | [A, B, A, B, A, B] | [0, 0, 0, 0, 0, 4] | [70.11, 69.70, 70.12, 69....] |
Ngay cả trước khi áp dụng thuật toán nén, định dạng này ngay lập tức tiết kiệm dung lượng lưu trữ bằng cách giảm đáng kể chi phí nội bộ trên mỗi hàng của Timescale. PostgreSQL thường thêm ~27 byte chi phí trên mỗi hàng (ví dụ: đối với Kiểm soát đồng thời nhiều phiên bản hoặc MVCC). Vì vậy, ngay cả khi không nén, nếu lược đồ của chúng ta ở trên là 32 byte, thì 1.000 hàng dữ liệu từ một lô trước đó chiếm [1.000 * (32 + 27)] ~= 59 kilobyte giờ đây chiếm [1.000 * 32 + 27 ] ~= 32 kilobyte ở định dạng này.
[ Ngoài ra : Khái niệm “nhóm” một bảng lớn hơn thành các lô nhỏ hơn và sau đó lưu trữ các cột của từng lô liên tục (chứ không phải của toàn bộ bảng) thực sự là một cách tiếp cận tương tự với “nhóm hàng” trong định dạng tệp Apache Parquet. Mặc dù chúng ta chỉ nhận ra sự tương đồng đó sau khi sự việc đã xảy ra!]
Nhưng ưu điểm lớn của phép chuyển đổi này là giờ đây, với định dạng có dữ liệu tương tự (dấu thời gian, IDS thiết bị, chỉ số nhiệt độ, v.v.) được lưu trữ liền kề, chúng ta có thể sử dụng thuật toán nén dành riêng cho từng loại để mỗi mảng được nén riêng biệt. . Đây là cách Timescale đạt được tốc độ nén ấn tượng.
Timescale tự động sử dụng các thuật toán nén sau. Tất cả các thuật toán này đều “
Đồng bằng của đồng bằng +
Nén từ điển toàn hàng cho các cột có một vài giá trị lặp lại (+ nén LZ ở trên cùng)
Nén mảng dựa trên LZ cho tất cả các loại khác
Chúng tôi đã mở rộng Gorilla và Simple-8b để xử lý dữ liệu giải nén theo thứ tự ngược lại, cho phép chúng tôi tăng tốc các truy vấn sử dụng tính năng quét ngược.
Chúng tôi nhận thấy tính năng nén dành riêng cho loại này khá mạnh: ngoài khả năng nén cao hơn, một số kỹ thuật như Gorilla và delta-of-delta có thể nhanh hơn tới 40 lần so với nén dựa trên LZ trong quá trình giải mã, dẫn đến hiệu suất truy vấn được cải thiện nhiều .
Khi giải nén dữ liệu, Timescale có thể hoạt động trên các lô nén riêng lẻ này, giải nén chúng theo từng đợt và chỉ trên các cột được yêu cầu. Vì vậy, nếu công cụ truy vấn có thể xác định rằng chỉ cần xử lý 20 lô (tương ứng với 20.000 hàng dữ liệu gốc) từ một đoạn bảng ban đầu bao gồm một triệu hàng dữ liệu thì truy vấn có thể thực thi nhanh hơn nhiều vì nó đọc và giải nén ít dữ liệu hơn rất nhiều. Hãy xem nó làm điều đó như thế nào.
Định dạng dựa trên mảng trước đây đưa ra một thách thức: cụ thể là cơ sở dữ liệu nên tìm nạp và giải nén những hàng nào để giải quyết một truy vấn?
Hãy lấy lại ví dụ về dữ liệu nhiệt độ của chúng ta. Một số loại truy vấn tự nhiên xuất hiện lặp đi lặp lại: chọn và sắp xếp dữ liệu theo khoảng thời gian hoặc chọn dữ liệu dựa trên ID thiết bị của nó (trong mệnh đề WHERE hoặc thông qua GROUP BY). Làm cách nào chúng tôi có thể hỗ trợ hiệu quả các truy vấn như vậy?
Bây giờ, nếu chúng ta cần dữ liệu từ ngày cuối cùng, truy vấn phải điều hướng qua dữ liệu dấu thời gian, hiện là một phần của mảng nén. Vì vậy, cơ sở dữ liệu có nên giải nén toàn bộ khối (hoặc thậm chí toàn bộ siêu bảng) để định vị dữ liệu cho ngày gần đây không?
Hoặc ngay cả khi chúng tôi có thể xác định các “lô” riêng lẻ được nhóm thành một mảng nén (được mô tả ở trên), liệu dữ liệu từ các thiết bị khác nhau có nằm xen kẽ với nhau hay không, vì vậy chúng tôi cần giải nén toàn bộ mảng để tìm xem liệu nó có bao gồm dữ liệu về một thiết bị cụ thể không? Mặc dù cách tiếp cận đơn giản hơn này vẫn có thể mang lại khả năng nén tốt nhưng nó gần như không hiệu quả nếu xét trên quan điểm hiệu suất truy vấn.
Để giải quyết thách thức định vị và giải nén dữ liệu một cách hiệu quả cho các truy vấn cụ thể ở định dạng cột,
segmentby
Hãy nhớ lại rằng dữ liệu trong Timescale ban đầu được chuyển đổi sang dạng cột nén theo từng đoạn. Để nâng cao hiệu quả của các truy vấn lọc dựa trên một cột cụ thể (ví dụ: thường xuyên truy vấn theo device_id
), bạn có tùy chọn xác định cột cụ thể này là “
Các cột segmentby
này được sử dụng để phân vùng dữ liệu một cách hợp lý trong mỗi đoạn được nén. Thay vì xây dựng một mảng nén các giá trị tùy ý như được hiển thị ở trên, công cụ nén trước tiên sẽ nhóm tất cả các giá trị có cùng khóa segmentby
lại với nhau.
Vì vậy, 1.000 hàng dữ liệu về device_id A được sao lưu dày đặc trước khi được lưu trữ trong một hàng nén duy nhất, 1.000 hàng về device_id B, v.v. Vì vậy, nếu device_id
được chọn làm cột segmentby
, mỗi hàng nén sẽ bao gồm các lô dữ liệu dạng cột được nén về một ID thiết bị cụ thể, được lưu trữ ở dạng không nén trong hàng đó. Ngoài ra, Timescale còn xây dựng chỉ mục trên các giá trị phân đoạn này trong đoạn được nén.
| Device ID | Timestamp | Status Code | Temperature | |-----------|--------------------------------|-------------|-----------------------| | A | [12:00:01, 12:00:02, 12:00:03] | [0, 0, 0] | [70.11, 70.12, 70.14] | | B | [12:00:01, 12:00:02, 12:00:03] | [0, 0, 4] | [69.70, 69.69, 69.70] |
Việc lưu trữ dữ liệu liền kề này giúp nâng cao đáng kể hiệu quả của các truy vấn được lọc theo cột segmentby
. Khi chạy truy vấn được lọc theo device_id
trong đó device_id
là cột segmentby
, Timescale có thể nhanh chóng chọn (thông qua chỉ mục) tất cả các hàng được nén trong đoạn có (các) ID thiết bị được chỉ định và nó nhanh chóng bỏ qua dữ liệu (và tránh giải nén ) dữ liệu không liên quan đến thiết bị được yêu cầu.
Ví dụ: trong truy vấn này, Timescale sẽ chỉ định vị và xử lý hiệu quả những hàng được nén chứa dữ liệu cho device_id A:
SELECT AVG(temperature) FROM sensor_data WHERE device_id = 'A' AND time >= '2023-01-01' AND time < '2023-02-01';
Ngoài ra, siêu bảng Timescale lưu trữ siêu dữ liệu được liên kết với từng đoạn chỉ định phạm vi giá trị mà đoạn đó bao gồm. Vì vậy, nếu một siêu bảng được phân vùng dấu thời gian theo tuần thì khi trình lập kế hoạch truy vấn chạy truy vấn trên, nó sẽ biết chỉ xử lý 4-5 đoạn đó trong tháng 1, cải thiện hơn nữa hiệu suất truy vấn.
segmentby
Bạn có thể chỉ định cột nào sẽ được sử dụng cho phân đoạn khi lần đầu tiên bật tính năng nén siêu bảng. Việc lựa chọn sử dụng cột nào phải dựa trên cột hoặc các cột nào thường được sử dụng trong truy vấn của bạn. Trên thực tế, bạn có thể sử dụng nhiều cột để phân đoạn theo: ví dụ: thay vì nhóm các lô lại với nhau theo device_id, bạn có thể (giả sử) nhóm các lô đó có cùng Rent_id và device_id lại với nhau.
Tuy nhiên, hãy cẩn thận để không lạm dụng tính chọn lọc: việc xác định quá nhiều cột theo từng phân đoạn sẽ làm giảm hiệu quả nén vì mỗi cột theo từng phân đoạn bổ sung sẽ chia dữ liệu thành các lô ngày càng nhỏ hơn một cách hiệu quả.
Nếu bạn không còn có thể tạo 1.000 lô dữ liệu bản ghi mà thay vào đó chỉ có 5 bản ghi có khóa phân đoạn được chỉ định trong một đoạn cụ thể thì dữ liệu đó sẽ không được nén tốt chút nào!
Nhưng sau khi bạn đã xác định được cột nào bạn muốn phân đoạn, chúng sẽ dễ dàng được định cấu hình khi bật tính năng nén trong siêu bảng của bạn:
ALTER TABLE temperature_data SET ( timescaledb.compress, timescaledb.compress_segmentby = 'device_id' );
orderby
TimescaleDB nâng cao hiệu suất truy vấn trên dữ liệu nén thông qua thứ tự dữ liệu chiến lược trong mỗi đoạn, được quy định bởi tham số compress_orderby
. Mặc dù cài đặt mặc định sắp xếp thứ tự theo dấu thời gian (khóa phân vùng điển hình trong dữ liệu chuỗi thời gian) phù hợp với hầu hết các trường hợp, nhưng việc hiểu tối ưu hóa này có thể có giá trị. Đọc tiếp để có góc nhìn kỹ thuật sâu sắc hơn.
Hãy xem xét lại ví dụ về các khối hàng tuần và một truy vấn chỉ yêu cầu dữ liệu về một ngày. Trong một bảng thông thường có chỉ mục dấu thời gian, truy vấn có thể đi theo chỉ mục này một cách hiệu quả để tìm dữ liệu trong ngày.
Tuy nhiên, tình huống lại khác với dữ liệu nén: dấu thời gian được nén và không thể truy cập được nếu không giải nén toàn bộ lô. Việc tạo chỉ mục trên mỗi dấu thời gian riêng lẻ sẽ phản tác dụng vì nó có thể phủ nhận lợi ích của việc nén do trở nên quá lớn.
Timescale giải quyết vấn đề này bằng cách về cơ bản là “sắp xếp” dữ liệu được phân nhóm theo dấu thời gian của nó. Sau đó, nó ghi lại siêu dữ liệu về dấu thời gian tối thiểu và tối đa cho mỗi lô. Khi một truy vấn được thực thi, siêu dữ liệu này cho phép công cụ truy vấn nhanh chóng xác định những hàng (lô) nén nào có liên quan đến phạm vi thời gian của truy vấn, do đó giảm nhu cầu giải nén hoàn toàn.
Phương pháp này hoạt động tốt với việc sử dụng các cột phân đoạn. Trong quá trình nén, dữ liệu trước tiên được nhóm theo cột phân đoạn, sau đó được sắp xếp dựa trên tham số orderby và cuối cùng được chia thành các “lô nhỏ” nhỏ hơn, được sắp xếp theo dấu thời gian, mỗi lô chứa tối đa 1.000 hàng.
Sự kết hợp giữa phân đoạn và thứ tự của TimescaleDB giúp nâng cao đáng kể hiệu suất của các truy vấn phân tích và chuỗi thời gian phổ biến. Việc tối ưu hóa này theo cả thời gian (thông qua orderby
) và không gian (thông qua segmentby
) đảm bảo rằng TimescaleDB quản lý và truy vấn hiệu quả dữ liệu chuỗi thời gian quy mô lớn, mang lại sự cân bằng tối ưu giữa khả năng nén và khả năng truy cập.
Phiên bản đầu tiên của thiết kế nén của chúng tôi được phát hành vào năm 2019 với TimescaleDB 1.5 . Nhiều bản phát hành sau đó, việc nén Timescale đã đi được một chặng đường dài.
Một trong những hạn chế chính của bản phát hành đầu tiên của chúng tôi là chúng tôi không cho phép bất kỳ sửa đổi nào nữa đối với dữ liệu—ví dụ: CHÈN, CẬP NHẬT, XÓA—sau khi dữ liệu đã được nén mà không giải nén thủ công toàn bộ đoạn siêu bảng chứa dữ liệu đó.
Do chúng tôi đang tối ưu hóa cho các trường hợp sử dụng nhiều dữ liệu dựa trên dữ liệu phân tích và chuỗi thời gian, chủ yếu nặng về chèn và không nặng về cập nhật, nên điều này ít hạn chế hơn nhiều so với trường hợp sử dụng OLTP truyền thống nơi dữ liệu được cập nhật thường xuyên (ví dụ: bảng thông tin khách hàng). Tuy nhiên,
Một hạn chế khác của bản phát hành nén ban đầu của chúng tôi là chúng tôi không cho phép sửa đổi lược đồ trong các bảng, bao gồm cả dữ liệu nén. Điều này có nghĩa là các nhà phát triển không thể phát triển cấu trúc dữ liệu của họ mà không giải nén toàn bộ bảng,
Ngày nay, tất cả những hạn chế này đã được loại bỏ. Timescale hiện cho phép bạn thực hiện đầy đủ các hoạt động Ngôn ngữ thao tác dữ liệu (DML) và Ngôn ngữ định nghĩa dữ liệu (DDL) trên dữ liệu nén:
Bạn có thể thực hiện CẬP NHẬT, UPSERT và XÓA.
Bạn có thể thêm các cột, bao gồm cả các giá trị mặc định.
Bạn có thể đổi tên và thả cột.
Để tự động hóa việc sửa đổi dữ liệu đối với dữ liệu đã nén (làm cho dữ liệu trở nên liền mạch với người dùng), chúng tôi đã thay đổi phương pháp nén bằng cách giới thiệu một “vùng tổ chức”—về cơ bản là một đoạn chồng chéo vẫn không bị nén và trong đó chúng tôi thực hiện các thao tác “trên dữ liệu không nén” trong mui xe.
Với tư cách là người dùng, bạn không phải làm bất cứ điều gì theo cách thủ công: bạn có thể sửa đổi dữ liệu của mình trực tiếp trong khi công cụ của chúng tôi tự động xử lý mọi thứ. Khả năng thực hiện các thay đổi đối với dữ liệu nén giúp công cụ lưu trữ cột-hàng lai của Timescale linh hoạt hơn nhiều.
Thiết kế này thông qua khu vực tổ chức giúp cho INSERT nhanh như việc chèn vào các đoạn không nén vì đây thực sự là những gì đang xảy ra (khi bạn chèn vào một đoạn đã nén, bây giờ bạn đang ghi vào khu vực tổ chức). Nó cũng cho phép chúng tôi hỗ trợ trực tiếp các CẬP NHẬT, UPSERT và DELETE: khi cần thay đổi một giá trị, công cụ sẽ di chuyển một phần dữ liệu nén có liên quan đến khu vực tổ chức, giải nén nó, thực hiện thay đổi và di chuyển lại (không đồng bộ) vào bảng chính ở dạng nén.
(Vùng dữ liệu này thường hoạt động theo quy mô của các “lô nhỏ” được nén lên tới 1.000 giá trị bao gồm một “hàng” trong bộ lưu trữ PostgreSQL cơ bản để giảm thiểu lượng dữ liệu cần được giải nén để hỗ trợ sửa đổi.)
“Khu vực tổ chức” này vẫn có ngữ nghĩa giao dịch thông thường và các truy vấn của bạn sẽ thấy các giá trị này ngay khi chúng được chèn vào đó. Nói cách khác, trình lập kế hoạch truy vấn đủ thông minh để hiểu cách truy vấn chính xác trên các khối “dàn dựng” dựa trên hàng này và bộ nhớ cột thông thường.
Tại thời điểm này, câu hỏi hợp lý tiếp theo cần đặt ra là: kết quả cuối cùng là gì? Việc nén ảnh hưởng như thế nào đến hiệu suất truy vấn và tôi có thể tiết kiệm được bao nhiêu kích thước ổ đĩa bằng cách sử dụng tính năng nén này?
Như chúng ta đã thảo luận trong bài viết này, các kho lưu trữ theo cột thường không hoạt động tốt đối với các truy vấn truy xuất các hàng riêng lẻ, nhưng chúng có xu hướng hoạt động tốt hơn nhiều đối với các truy vấn phân tích xem xét các giá trị tổng hợp. Đây chính xác là những gì chúng ta thấy trong Timescale: các truy vấn sâu và hẹp liên quan đến mức trung bình sẽ thấy những cải thiện hiệu suất đáng kể khi sử dụng tính năng nén.
Hãy minh họa điều này bằng cách chạy một vài truy vấn trên
Hãy xem xét truy vấn sau, yêu cầu số tiền giá vé cao nhất từ một tập hợp con của tập dữ liệu taxi trong một khung thời gian cụ thể:
SELECT max(fare_amount) FROM demo.yellow_compressed_ht WHERE tpep_pickup_datetime >= '2019-09-01' AND tpep_pickup_datetime <= '2019-12-01';
Khi chạy với tập dữ liệu không nén, thời gian thực hiện truy vấn là 4,7 giây. Chúng tôi đang sử dụng một dịch vụ thử nghiệm nhỏ, chưa được tối ưu hóa và đang truy vấn hàng triệu hàng, vì vậy hiệu suất này không phải là tốt nhất. Nhưng sau khi nén dữ liệu, thời gian phản hồi giảm xuống còn 77,074 mili giây:
Hãy chia sẻ một ví dụ khác. Truy vấn này đếm số chuyến đi với mã giá cụ thể trong một khung thời gian nhất định:
SELECT COUNT(*) FROM demo.yellow_compressed_ht WHERE tpep_pickup_datetime >= '2019-09-01' AND tpep_pickup_datetime <= '2019-12-01' AND "RatecodeID" = 99;
Khi được thực thi đối với dữ liệu không nén, truy vấn này sẽ mất 1,6 giây để hoàn thành. Truy vấn tương tự chạy trên dữ liệu nén sẽ kết thúc chỉ trong 18,953 mili giây. Một lần nữa, chúng tôi thấy sự cải thiện ngay lập tức! Đây chỉ là những ví dụ nhanh nhưng chúng minh họa khả năng nén mạnh mẽ như thế nào để tăng tốc truy vấn của bạn.
Đừng quên điều đã đưa chúng ta đến đây ngay từ đầu: chúng ta cần một chiến thuật cho phép chúng ta giảm kích thước cơ sở dữ liệu PostgreSQL lớn để có thể mở rộng quy mô PostgreSQL hơn nữa. Để cho thấy mức độ hiệu quả của việc nén Timescale đối với tác vụ này, bảng bên dưới bao gồm một số ví dụ thực tế về tốc độ nén được thấy trong số các khách hàng của Timescale .
Những khoản tiết kiệm lưu trữ này trực tiếp chuyển thành tiết kiệm tiền:
Tốc độ nén cuối cùng bạn sẽ đạt được phụ thuộc vào một số yếu tố, bao gồm kiểu dữ liệu và kiểu truy cập của bạn. Nhưng như bạn có thể thấy, nén Timescale có thể cực kỳ hiệu quả—
Nhóm của chúng tôi có thể giúp bạn tinh chỉnh quá trình nén để giúp bạn tiết kiệm nhiều tiền nhất có thể,
“Với tính năng nén, chúng tôi nhận thấy trung bình giảm 97% [kích thước ổ đĩa].”
(Michael Gagliardo, Ndustrial)
“Chúng tôi nhận thấy tỷ lệ nén của Timescale thực sự phi thường! Chúng tôi hiện đang ở tỷ lệ nén trên 26, giảm đáng kể dung lượng ổ đĩa cần thiết để lưu trữ tất cả dữ liệu của chúng tôi.”
(Nicolas Quintin, Quãng tám)
“Khả năng nén của Timescale tốt như quảng cáo, giúp chúng tôi tiết kiệm +90 % dung lượng [đĩa] trong siêu bảng cơ bản của chúng tôi.”
(Paolo Bergantino, Tập đoàn METER)
Cuối cùng, chúng tôi không thể kết thúc bài viết này mà không đề cập đến Bộ nhớ theo cấp của Timescale,
Ngoài khả năng nén, giờ đây bạn còn có một công cụ khác giúp bạn mở rộng quy mô cơ sở dữ liệu PostgreSQL của mình hơn nữa trong nền tảng Timescale: bạn có thể xếp dữ liệu cũ hơn, được truy cập không thường xuyên của mình vào tầng lưu trữ đối tượng chi phí thấp trong khi vẫn có thể truy cập dữ liệu đó qua tiêu chuẩn SQL.
Bậc lưu trữ chi phí thấp này có mức giá cố định là 0,021 USD mỗi GB/tháng cho dữ liệu—rẻ hơn Amazon S3—cho phép bạn giữ nhiều TB trong cơ sở dữ liệu PostgreSQL của mình với một phần chi phí.
Đây là cách chương trình phụ trợ Lưu trữ theo cấp của chúng tôi hoạt động trên nền tảng Timescale và cách cấp lưu trữ thấp hoạt động cùng với nén:
Dữ liệu gần đây nhất của bạn được ghi vào tầng lưu trữ hiệu suất cao được tối ưu hóa cho các truy vấn nhanh và lượng nhập cao. Ở cấp độ này, bạn có thể bật tính năng nén cột Timescale để thu nhỏ kích thước cơ sở dữ liệu và tăng tốc các truy vấn phân tích, như chúng ta đã thảo luận trong bài viết này. Ví dụ: bạn có thể xác định chính sách nén để nén dữ liệu của mình sau 1 tuần.
Sau khi ứng dụng của bạn không còn truy cập thường xuyên vào dữ liệu đó nữa, bạn có thể tự động xếp nó vào tầng lưu trữ đối tượng có chi phí thấp hơn bằng cách thiết lập chính sách xếp tầng. Dữ liệu ở tầng lưu trữ chi phí thấp vẫn có thể truy vấn đầy đủ trong cơ sở dữ liệu của bạn và không có giới hạn về lượng dữ liệu bạn có thể lưu trữ—lên tới hàng trăm TB trở lên. Ví dụ: bạn có thể xác định chính sách phân tầng để di chuyển tất cả dữ liệu cũ hơn sáu tháng của bạn sang tầng lưu trữ chi phí thấp.
Khi bạn không cần phải lưu giữ dữ liệu này trong cơ sở dữ liệu của mình nữa, bạn có thể loại bỏ dữ liệu đó thông qua chính sách lưu giữ. Ví dụ: bạn có thể xóa tất cả dữ liệu sau năm năm.
Chúng tôi đã cung cấp cho Postgres cơ chế nén cơ sở dữ liệu hiệu quả bằng cách thêm khả năng nén cột. Đây là một tính năng thiết yếu để mở rộng cơ sở dữ liệu PostgreSQL trong thế giới sử dụng nhiều dữ liệu ngày nay: tính năng nén cho phép tiết kiệm đáng kể mức sử dụng ổ đĩa (lưu trữ nhiều dữ liệu hơn với chi phí rẻ hơn) và cải thiện hiệu suất (chạy các truy vấn phân tích trên khối lượng lớn tính bằng mili giây).
Thiết kế nén của Timescale đạt được tốc độ nén ấn tượng bằng cách kết hợp các thuật toán nén tốt nhất cùng với một phương pháp mới để tạo bộ lưu trữ hàng/cột kết hợp trong PostgreSQL. Khả năng này làm cho dung lượng lưu trữ của Timescale (và do đó là PostgreSQL) ngang bằng với các cơ sở dữ liệu cột được xây dựng tùy chỉnh, hạn chế hơn.
Nhưng không giống như nhiều công cụ cột, Timescale hỗ trợ ngữ nghĩa giao dịch ACID và hỗ trợ trực tiếp các sửa đổi (INSERT, UPDATE, UPSERT, DELETE) trên dữ liệu cột được nén. Do mô hình cũ “một cơ sở dữ liệu cho khối lượng công việc giao dịch, một cơ sở dữ liệu khác cho khối lượng công việc phân tích” đã lỗi thời nên nhiều ứng dụng hiện đại chạy khối lượng công việc phù hợp với cả hai mô hình. Vậy tại sao phải duy trì hai cơ sở dữ liệu riêng biệt khi bạn có thể thực hiện tất cả trong PostgreSQL?
Timescale cho phép bạn bắt đầu trên PostgreSQL, mở rộng quy mô với PostgreSQL, duy trì với PostgreSQL.
- Viết bởi Carlota Soto và Mike Freedman .
Cũng được xuất bản ở đây.