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.

    Trần Ngọc Đăng KhôiChương 04 / 0724 phút đọc
    Mục lục chương
      Ý chính của chươngPhần lớn lỗi số liệu bắt nguồn từ model khai báo sai, không phải từ data warehouse. Các mục dưới đây phân tích những điểm dễ sai nhất và cơ chế Cube cung cấp để tránh chúng.
      04.1

      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/cubes chứa cube thô, model/views chứ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.

      cubes: - name: internal_orders
      Hình 1Cùng một cube, phạm vi hiển thị thay đổi theo tham số public.
      Đọ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: count

      Cá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.

      04.2

      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.

      Hình 2Một measure, ba granularity. Dữ liệu minh họa: 8 người dùng trong 14 ngày.
      Ghi nhớcount_distinct không cộng dồn được. Để có WAU, cần truy vấn với granularity week thay vì cộng bảy giá trị DAU.
      Đọc sâuCác kiểu measure và khai báo active_users
      KiểuChức năng
      countĐếm số dòng.
      sumTí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_id
      04.3

      Joins, 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.

      Tổng orders.total_amount trên phép join line_items → orders
      Hình 3Hai đơn hàng, bốn dòng hàng. Đơn #1 có ba dòng hàng nên có nguy cơ bị cộng ba lần. Số minh họa.
      Đọ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_one

      Khi 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.

      04.4

      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.

      Hình 4Từ truy vấn của client tới câu lệnh thực thi trên data warehouse. SQL ở bước 3 là minh họa.
      04.5

      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.

      Hình 5Sáu context variables và điểm dễ sai của từng biến.

      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.

      Bộ lọc của client: a > 1 OR b > 1
      Hình 6Inner query lọc bằng AND trong khi outer query yêu cầu OR, nên inner query đã loại các dòng hợp lệ. Số minh họa.
      Ghi nhớChỉ dùng FILTER_PARAMS cho predicate pushdown, như bảng partition hay shard của BigQuery. Logic nghiệp vụ phức tạp nên chuyển về tầng upstream (dbt hoặc transformation trong data warehouse).
      Đọ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')
          )}
        `
      })
      04.6

      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.

      Hình 7Hai tầng cache và truy vấn trực tiếp, theo từng tiêu chí.
      04.7

      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 đó.

      Hình 8Khai báo rollup 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.

      04.8

      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ý.

      Truy vấn orders.total_amount
      Hình 9Từ JWT tới SQL. Bảng orders minh họa có 3 dòng thuộc 2 tenant.
      Ghi nhớTrong ví dụ của handbook, khi JWT thiếu 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;
        }
      };
      04.9

      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.

      Hình 10Hai vị trí nên kiểm tra trước khi đọc mã nguồn Cube.
      04.10

      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.

      Schema thô
      Semantic layer
      0%25%50%75%100%
      Hình 11Độ chính xác của AI Agent theo handbook. Số liệu từ case study trong handbook.
      Đọ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.

      Chương tiếp theo05 · Production Engineering→