Qlik engine lưu trữ dữ liệu như thế nào?

Hẳn bạn đã từng để ý thấy kích thước các file Qlik application (qvw với QlikView và qvf với Qlik Sense) thường nhỏ hơn đáng kể so với kích thước nguồn dữ liệu. Chẳng hạn, khi load một file CSV nặng 4GB vào Qlik Sense, file qvf tương ứng có thể chỉ chiếm vài trăm MB trên đĩa cứng.

Tuy nhiên, không phải lúc nào Qlik cũng đạt được tỷ lệ nén dữ liệu tốt như vậy. Trong nhiều trường hợp, với kích thước dữ liệu nguồn tương đương, khối dữ liệu trong Qlik vẫn chiếm dụng một không gian lớn, gây quá tải bộ nhớ và làm cho ứng dụng của bạn hoạt động chậm.

Vậy yếu tố nào quyết định khối dữ liệu trong Qlik application của bạn sẽ chiếm bao nhiêu dung lượng trong bộ nhớ và trên đĩa cứng? Để trả lời câu hỏi này, ta cần hiểu về cơ chế Qlik tổ chức và lưu trữ dữ liệu.

Symbol table

Dữ liệu được load vào QlikView hoặc Qlik Sense thường có dạng bảng dữ liệu phẳng, gồm các hàng và các cột. Sau mỗi câu lệnh load, Qlik engine chuyển đổi mỗi cột trong bảng dữ liệu ban đầu thành một symbol table.

Mỗi dòng trong symbol table lưu trữ một giá trị không trùng lặp (distinct value) từ cột dữ liệu ban đầu, cùng với một con trỏ (pointer) tương ứng với giá trị đó. Như vậy, symbol table căn bản là một bảng tham chiếu (lookup table) giữa các giá trị trong một cột dữ liệu với pointer tương ứng của nó.

Sau khi xây dựng xong các symbol table, bảng dữ liệu thực tế được lưu trữ trong bộ nhớ lúc này vẫn có cùng số hàng, số cột với bảng dữ liệu nguồn ban đầu. Tuy nhiên, giá trị thực tế của các cột dữ liệu được thay thế bằng pointer tương ứng.

Điểm đặc biệt của data pointer trong Qlik là pointer chỉ dùng đúng số bit tối thiểu tương ứng với số giá trị distinct value trong một cột dữ liệu. Chẳng hạn cột ProductName trong hình trên có 3 giá trị có thể có: Samsung Galaxy Tab S4 BlackiPhone 7 và iPad Air. Khi xây dựng symbol table choProductName , pointer của ProductName chỉ dùng 2 bit, là số bit tối thiểu để mã hóa được 3 giá trị dữ liệu.

Bây giờ hãy thử làm một phép tính đơn giản để tính tỷ lệ nén dữ liệu trong Qlik. Giả sử trong bảng dữ liệu nguồn của bạn, giá trị “Samsung Galaxy Tab S4 Black” (dài 27 byte theo bảng mã ASCII) xuất hiện ở 1,000 dòng. Như vậy, trong dữ liệu nguồn giá trị này chiếm 27,000 bytes.

Sau khi load vào QlikView hoặc Qlik Sense, dung lương bộ nhớ mà giá trị này chiếm dụng là như sau:

  • Trong symbol table của ProductName:
    • Pointer: 3 bit
    • Value: Samsung Galaxy Tab S4 Black – 27 byte
  • Trong bảng dữ liệu với giá trị thực tế được thay bằng pointer: 3 bit * 1,000 = 3,000 bit

Tổng dung lượng mà Qlik Engine cần dùng để lưu trữ riêng giá trị này là 3 bit + 27 byte + 3,000 bit ~ 403 bytes. Như vậy, với riêng giá trị này, Qlik engine đã đạt độ nén lên tới (1 – 403/27,000) ~ 98%

Symbol table và kích thước ứng dụng

Hiểu đươc cơ chế lưu trữ dữ liệu với symbol table và pointer của Qlik engine sẽ giúp ích khá nhiều trong việc tối ưu hóa dung lượng dashboard của bạn. Trong hầu hết mọi trường hợp, dễ thấy số lượng hàng và cột trong dữ liệu nguồn ảnh hưởng trực tiếp đến kích thước bộ nhớ cần sử dụng. Tuy nhiên, với cơ chế symbol table và pointer như trên, việc sử dụng bộ nhớ còn phụ thuộc vào các yếu tố sau:

  • Độ dài của các distinct values ảnh hưởng đến kích thước của symbol table
  • Số lượng distinct values quyết định số dòng của symbol table, đồng thời cũng quyết định số bit mà pointer cần sử dụng.

Tối ưu hóa kích thước ứng dụng

Khi xây dựng reload script trong QlikView hoặc Qlik Sense, việc tìm cách giảm độ dài và số lương distinct values là chìa khóa đảm bảo ứng dụng của bạn sử dụng ít bộ nhớ nhất có thể. Một số trường hợp cụ thể như sau:

  • Khi 2 bảng trong data model của bạn được liên kết bởi một khóa phức hợp (composite key – khóa tạo thành bằng cách ghép nhiều cột). Sử dụng hàm AutoNumber( ) để thay thế giá trị khóa phức hợp bằng các số nguyên theo thứ tự sẽ làm giảm đáng kể dung lượng symbol table của khóa.
  • Khi bảng dữ liệu của bạn có cột ngày giờ chính xác đến giờ phút hoặc phần ngàn giây (ví dụ 2019-03-15 07:05:44.534), nhưng bạn lại không có nhu cầu theo dõi ngày tháng quá chi tiết. Chẳng hạn, nếu chỉ cần theo dõi giao dịch bán hàng chính xác đến giờ phút, bạn có thể cắt bỏ phần giây và %% giây thừa như sau:Time(Floor(Frac(SalesTime),1/24/60)) as SalesTime

Với cách làm này, bạn chỉ cần tối đa 11 bit để làm pointer cho 24*60=1440 giá trị có thể có của cột SalesTime chính xác đến giờ phút, thay vì tối đa 27 bit cho 24*60*60*1000 = 86,400,000 giá trị có thể có nếu SalesTime chính xác đến %% giây. Việc này làm giảm khoảng 60% (1 – 11/27) kích thước của cả symbol table lẫn bảng dữ liệu thực tế.