Engineering Deep Dives
Mười chủ đề kỹ thuật bên trong Cube: đặt tên model, chọn relationship cho join, context variables, cache, phân quyền, multi-tenancy và một benchmark về AI Agent.
Mục lục chương
Data Modeling
Ba quy ước từ v0.33 giúp model gọn, dễ đọc với cả người lẫn AI Agent.
- Tách thư mục:
model/cubeschứa cube thô,model/viewschứa data mart cho các ứng dụng tiêu thụ. - Mọi tên cube, view, measure, dimension dùng snake_case.
- Kiểm soát hiển thị bằng tham số public, đặt được trên cube, view, measure và dimension. Mặc định là
public: true.
Khi đặt public: false cho một cube thô, cube đó không còn xuất hiện trong các API công khai nhưng vẫn hiển thị trong Developer Playground với biểu tượng khóa. Chọn từng giá trị bên dưới để so sánh.
Đọc sâuVí dụ ẩn cube thô
cubes:
- name: internal_orders
sql_table: raw.orders
public: false # ẩn cube thô khỏi API công khai
measures:
- name: raw_count
type: countCách dùng phổ biến: ẩn cube thô và chỉ công khai view đã chọn lọc trường. Người dùng và AI Agent chỉ thấy những trường mà view cho phép.
Measures, Dimensions & Granularity
Một measure count_distinct trên user_id đủ để tính DAU, WAU và MAU.
Không cần khai báo ba measure riêng. Khi truy vấn truyền timeDimensions với granularity là day, week hay month, cùng một measure active_users trả về DAU, WAU hoặc MAU tương ứng. Có thể đổi granularity bên dưới; hình tính trực tiếp các giá trị trên 14 ngày dữ liệu minh họa.
Đọc sâuCác kiểu measure và khai báo active_users
| Kiểu | Chức năng |
|---|---|
count | Đếm số dòng. |
sum | Tính tổng. |
count_distinct | Đếm số giá trị khác nhau. |
count_distinct_approx | Đếm xấp xỉ bằng thuật toán HyperLogLog, nhanh hơn nhiều trên tập dữ liệu lớn. Handbook ghi chú hỗ trợ tốt trên Snowflake từ v0.28. |
measures:
- name: active_users
type: count_distinct
sql: user_idJoins, Fanout & Double Counting
Khai báo relationship sai làm tổng bị nhân lên.
Cube nối cube bằng LEFT JOIN. Khi nối line_items với orders, mỗi đơn hàng lặp lại theo số dòng hàng của nó. Khi relationship được khai báo là many_to_one, Cube điều chỉnh SQL để mỗi đơn chỉ được cộng một lần. Nếu khai báo sai, fanout xảy ra và tổng tiền bị double counting.
Đọc sâuBa loại relationship và Entities Graph
many_to_one: nhiều đơn hàng thuộc một khách hàng. one_to_many: một khách hàng có nhiều đơn hàng. one_to_one: mỗi bản ghi bên này ứng với đúng một bản ghi bên kia. Hướng khai báo tính từ cube chứa khối joins.
cubes:
- name: line_items
sql_table: public.line_items
joins:
- name: orders
sql: "{CUBE}.order_id = {orders.id}"
relationship: many_to_oneKhi sinh SQL, Cube dựng một Entities Graph từ các joins. Theo handbook, nếu phát hiện phép JOIN có nguy cơ fanout với phép aggregate, Cube tự điều chỉnh cấu trúc SQL hoặc cảnh báo nhà phát triển. Cơ chế này chỉ hoạt động đúng khi relationship được khai báo đúng.
SQL Generation and Inspection
Semantic SQL cho phép gọi measure bằng hàm MEASURE() trong câu SQL theo cú pháp Postgres.
Client viết SQL gần như thông thường; điểm khác biệt duy nhất là gọi MEASURE("orders".total_sales) thay vì tự viết sum(...). Schema Compiler tra định nghĩa measure trong model và biên dịch thành SQL gốc cho data warehouse. Ba bước bên dưới minh họa quá trình này.
Context Variables
Context variables cho phép SQL trong model thay đổi theo truy vấn và theo người dùng.
Sáu biến thường gặp gồm: CUBE, FILTER_PARAMS, FILTER_GROUP, SQL_UTILS.convertTz(), COMPILE_CONTEXT và SECURITY_CONTEXT. Biến cuối đã deprecated, nên thay bằng query_rewrite. Chọn từng biến để xem chức năng và các lưu ý khi sử dụng.
FILTER_GROUP cần thiết khi SQL nguồn dùng nhiều FILTER_PARAMS và truy vấn có toán tử khác AND. Trong ví dụ bên dưới, client lọc a > 1 OR b > 1 và kết quả đúng phải có 3 dòng.
Đọc sâuFILTER_PARAMS dạng chuỗi và dạng hàm
Dạng chuỗi: chuyển điều kiện ngày từ truy vấn vào SQL nguồn để data warehouse không phải quét toàn bộ bảng.
cubes:
- name: order_facts
sql: |
SELECT * FROM orders
WHERE {FILTER_PARAMS.order_facts.date.filter('date')}Dạng hàm: tự xây dựng điều kiện, ví dụ chỉ đọc các shard BigQuery nằm trong khoảng ngày.
cube(`events`, {
sql: `
SELECT * FROM schema.\`events*\`
WHERE ${FILTER_PARAMS.events.date.filter(
(x, y) => `
_TABLE_SUFFIX >= FORMAT_TIMESTAMP('%Y%m%d', TIMESTAMP(${x})) AND
_TABLE_SUFFIX <= FORMAT_TIMESTAMP('%Y%m%d', TIMESTAMP(${y}))
`
)}
`
})Handbook gọi việc lạm dụng FILTER_PARAMS là anti-pattern: SQL nguồn trở thành một hàm phụ thuộc tham số từ giao diện, model khó bảo trì, khó tối ưu pre-aggregation và khó tái sử dụng.
Cách viết đúng với FILTER_GROUP:
cube(`filter_group`, {
sql: `
SELECT * FROM data
WHERE ${FILTER_GROUP(
FILTER_PARAMS.filter_group.a.filter('a'),
FILTER_PARAMS.filter_group.b.filter('b')
)}
`
})Caching Mechanisms
Hai tầng cache: Query Result Cache và pre-aggregations.
Tầng 1 là Query Result Cache: lưu kết quả JSON của các truy vấn gần đây trong bộ nhớ (development) hoặc Cube Store (production). Cube Store thay Redis làm cache driver mặc định từ v0.32; v0.36 bỏ hẳn driver Redis. Tầng 2 là pre-aggregations: các bảng rollup vật lý build sẵn trong Cube Store, làm mới theo refresh_key. Chọn một tiêu chí để so sánh với truy vấn trực tiếp vào data warehouse. Các con số là ví dụ trong handbook; giá trị trên hệ thống thực tế sẽ khác.
Pre-aggregations
Bảng rollup tính sẵn, lưu trong Cube Store, để truy vấn không phải chạy lại trên data warehouse.
Từ v0.28, kiểu pre-aggregation được khuyến nghị là rollup, và nơi lưu mặc định cho external pre-aggregations là Cube Store. Chọn từng dòng YAML để xem vai trò của dòng đó.
sales_daily cho cube orders.Đọc sâuTăng tốc build và bên trong Cube Store
Batching (từ v0.28): tăng tốc build pre-aggregation cho các driver BigQuery, ClickHouse, MongoDB và MySQL.
Export bucket: data warehouse xuất trực tiếp ra cloud bucket trước khi nạp vào Cube Store. Handbook liệt kê Redshift (qua AWS S3) và Snowflake (qua S3 và Google Cloud Storage).
Cube Store là một hệ CSDL phân tán chuyên cho pre-aggregation. Bên trong dùng Apache Arrow DataFusion (query engine viết bằng Rust, thực thi dạng cột), RocksDB cho metadata, và file Parquet nén. Truy vấn khớp với rollup chỉ quét các cột cần dùng thay vì đọc lại dòng thô từ data warehouse. Handbook nêu độ trễ P95 dưới 250ms; đây là số liệu từ handbook, không phải cam kết chung.
Authentication, Authorization & Multi-tenancy
JWT mang danh tính, securityContext chứa danh tính đó, query_rewrite chuyển danh tính thành bộ lọc.
Ứng dụng client tạo JWT chứa các claim như tenant_id, role, user_id. Cube giải mã JWT và đưa claim vào securityContext. Hook queryRewrite trong cube.js chèn bộ lọc RLS vào mọi truy vấn trước khi SQL tới data warehouse. Chọn một bên gọi để xem luồng xử lý.
tenant_id, queryRewrite không thêm bộ lọc nào và truy vấn truy cập được dữ liệu của mọi tenant. Khi triển khai thực tế, nên từ chối truy vấn trong trường hợp này.Đọc sâuqueryRewrite trong cube.js
module.exports = {
queryRewrite: (query, { securityContext }) => {
if (securityContext && securityContext.tenant_id) {
query.filters.push({
member: 'orders.tenant_id',
operator: 'equals',
values: [securityContext.tenant_id]
});
}
return query;
}
};Source Code Investigation
Meta API trả về model đang chạy; cube.js hoặc cube.py chứa các hook cấu hình.
Để xác định model thực sự công khai những thành phần nào, gọi Meta API. Để thay đổi hành vi của Cube, xem trong file cấu hình.
Experiments & Performance Analysis
Một benchmark trong handbook: AI Agent trả lời chính xác hơn khi truy vấn qua semantic layer.
Handbook mô tả một paired benchmark với các AI Agent như Claude và ChatGPT. Đây là số liệu từ case study trong handbook, không phải kết quả chung cho mọi hệ thống. Chọn từng thanh để xem chi tiết.
Đọc sâuLưu ý khi diễn giải số liệu
Handbook ghi mức tăng 17 đến 23 điểm phần trăm, từ mức 55–60% lên 80–85%. Nếu cộng trực tiếp, 55–60% thêm 17–23 điểm chỉ cho khoảng 72–83%, nên hai cách diễn đạt không khớp hoàn toàn. Handbook cũng không nêu số câu hỏi, bộ dữ liệu hay cách chấm. Do đó, nên xem đây là xu hướng, không dùng làm số liệu so sánh chính xác.