Applied Case Studies: một bài toán đầy đủ

    Áp dụng nội dung các chương trước vào một hệ thống hoàn chỉnh: data model, kiến trúc, cấu hình, kết quả đo, đánh đổi và quy trình tái hiện.

    Trần Ngọc Đăng KhôiChương 07 / 0716 phút đọc
    Mục lục chương
      Ý chính của chươngMột data model phục vụ ba bề mặt tiêu thụ. Pre-aggregations đảm nhận tốc độ và chi phí, queryRewrite đảm nhận cách ly dữ liệu, còn view giúp AI truy vấn đúng nguồn.
      Tình huống minh họaCase study trong handbook là một tình huống giả định, không phải dự án thực tế đã công bố. Mọi con số trong chương (chi phí data warehouse, độ trễ, mức lệch chỉ số, độ chính xác của AI) thuộc về case study, chỉ nhằm minh họa cách lập luận, không phải benchmark chung.
      07.1

      Bối cảnh case study

      Một sàn E-commerce đa kênh với ba nhóm người dùng cùng truy vấn một data model.

      Tình huống được xét là một tập đoàn E-commerce đa kênh (omnichannel) với hàng triệu giao dịch mỗi ngày. Hệ thống phân tích phải phục vụ cùng lúc ba bề mặt tiêu thụ. Mỗi bề mặt kết nối với Cube qua một cổng riêng nhưng cùng sử dụng một định nghĩa chỉ số. Chọn từng bề mặt để xem đối tượng sử dụng và nhu cầu tương ứng.

      Hình 1Ba bề mặt tiêu thụ trong case study.
      Ghi nhớBa cổng kết nối khác nhau (SQL API, REST API, MCP) nhưng chỉ có một data model phía sau.
      07.2

      Bài toán và yêu cầu

      Bốn yêu cầu bắt buộc, mỗi yêu cầu ứng với một tính năng của Cube.

      Handbook đặt ra bốn yêu cầu: một định nghĩa duy nhất cho Weekly Active Users (WAU) và Net Revenue trên cả ba bề mặt; P95 dưới 0.25 giây trên các bảng dữ liệu từ hàng chục triệu dòng trở lên; multi-tenancy cho hơn 200 merchant; và AI Agent không được tự sinh SQL thô. Chọn một yêu cầu để xem tính năng Cube tương ứng.

      Hình 2Yêu cầu, cách đo, và phần Cube đảm nhận.
      Đọc sâuP95 nghĩa là gì?

      P95 là phân vị thứ 95 của độ trễ: 95% truy vấn hoàn tất nhanh hơn ngưỡng này, 5% còn lại có thể chậm hơn. Mục tiêu được đặt theo P95 thay vì giá trị trung bình, vì giá trị trung bình che khuất các truy vấn chậm mà người dùng vẫn gặp phải.

      07.3

      Dữ liệu và data model

      Bốn bảng thô, ba file cube, và một view chung cho BI và AI.

      Data warehouse nguồn có bốn bảng: orders, customers, regions, events. Data model viết bằng YAML theo quy chuẩn v0.33: tên snake_case, cube đặt trong model/cubes/, view đặt trong model/views/. Measure net_revenue nằm trong orders, còn WAU được tính từ active_users của events theo tuần. Chọn từng dòng để xem nội dung khai báo.

      Hình 3Bốn file của data model. Dòng có nền nhạt là dòng có giải thích.

      Join từ orders sang customers được khai báo với relationship many_to_one. Nhờ khai báo này, Cube biết nhiều đơn hàng thuộc về một khách hàng, nên không tính trùng khi nối bảng (tránh fanout). View sales_data_mart chỉ công khai các trường cần cho báo cáo doanh thu, do đó AI Agent không phải tự chọn bảng.

      Đọc sâuNhững điểm cần rà soát khi áp dụng thực tế

      Model trong handbook đủ cho mục đích minh họa nhưng có một số điểm không nhất quán. Khi dùng model này làm điểm xuất phát cho dự án thực tế, cần kiểm tra:

      1. Measure net_revenue lấy sql: profit. Lợi nhuận và doanh thu thuần là hai chỉ số khác nhau; cần xác nhận cột nguồn đúng với định nghĩa kinh doanh.

      2. Dimension tenant_id lấy sql: customer_id. Trong case study, tenant là merchant (nhà bán hàng), không phải khách mua hàng. Nếu bảng orders có cột merchant, tenant nên trỏ vào cột đó.

      3. View đi qua orders.customers.regions và lấy region_name, country_code, nhưng handbook không có file regions.yml. View chỉ compile được khi bổ sung cube regions.

      4. Pre-aggregation tên merchant_monthly_sales nhưng granularity là day, partition_granularity là month. Tên gọi dễ gây hiểu nhầm là rollup theo tháng.

      5. Yêu cầu nói về WAU, nhưng model chỉ có active_users đếm user_id không trùng. WAU thu được khi truy vấn active_users với time dimension event_time ở granularity week; handbook không khai báo measure WAU riêng.

      07.4

      Kiến trúc đề xuất

      Ba bề mặt truy vấn qua Cube. Truy vấn khớp rollup được trả lời từ Cube Store; truy vấn không khớp được chuyển xuống data warehouse.

      Mọi truy vấn đi qua view sales_data_mart và filter RLS theo tenant_id lấy từ JWT. Sau đó aggregate awareness xác định nguồn trả kết quả: nếu truy vấn khớp một pre-aggregation, Cube đọc bảng rollup trong Cube Store; nếu không khớp, Cube sinh SQL gửi xuống data warehouse (Postgres hoặc Snowflake). Dùng công tắc để xem đường đi của truy vấn, hoặc chọn từng tầng để xem chi tiết.

      Đường đi của một truy vấn
      Hình 4Sơ đồ kiến trúc, viết lại từ sơ đồ ASCII trong handbook.
      Ghi nhớClient không chọn bảng rollup. Cube tự định tuyến dựa trên measures, dimensions và granularity của truy vấn.
      Đọc sâuNhánh “miss” và chế độ rollup-only

      Mục 07.7 nêu rằng hệ thống bật CUBEJS_ROLLUP_ONLY=true. Ở chế độ rollup-only, truy vấn chỉ được phục vụ từ pre-aggregation, không được đẩy trực tiếp xuống data warehouse nguồn. Như vậy nhánh “Pre-agg miss → data warehouse” trong sơ đồ chỉ đúng khi chế độ này tắt. Khi bật, truy vấn không khớp rollup sẽ không đi xuống data warehouse; handbook không nói rõ case study chọn cách nào cho từng môi trường.

      07.5

      Triển khai

      Ba cấu hình chính: pre-aggregation, RLS theo tenant từ JWT, và kết nối cho AI.

      Thứ nhất, pre-aggregation merchant_monthly_sales ở 07.3 được chia partition theo tháng (partition_granularity) và làm mới mỗi 5 phút qua refresh_key. Theo handbook, cấu hình này giảm lượng dữ liệu cần quét từ hàng triệu dòng xuống vài nghìn dòng trong Cube Store.

      Thứ hai, multi-tenancy dùng queryRewrite trong cube.js: hàm đọc tenant_id trong securityContext (giải mã từ JWT), sau đó thêm một filter vào mọi truy vấn. Chọn một JWT bên dưới để xem truy vấn sau khi được viết lại.

      JWT gửi kèm request
      Hình 5Mô phỏng queryRewrite theo code của handbook. Mã tenant là số minh họa.

      Thứ ba, AI Agent kết nối qua MCP. Agent dùng Meta API để khám phá view sales_data_mart trước khi gửi Semantic SQL hoặc GraphQL. Ba đoạn cấu hình liên quan được trình bày bên dưới.

      Hình 6Cấu hình RLS và kết nối AI, lấy từ handbook.
      Đọc sâuTăng độ an toàn cho queryRewrite

      Code của handbook chỉ thêm filter khi có tenant_id. Nếu một request mang JWT hợp lệ nhưng thiếu claim này, truy vấn chạy không có filter và trả về dữ liệu của mọi merchant. Với yêu cầu cách ly giữa các merchant, cách an toàn hơn là từ chối truy vấn khi thiếu tenant_id:

      queryRewrite: (query, { securityContext }) => {
        if (!securityContext || !securityContext.tenant_id) {
          throw new Error('Missing tenant_id in security context');
        }
        query.filters.push({
          member: 'orders.tenant_id',
          operator: 'equals',
          values: [securityContext.tenant_id]
        });
        return query;
      }
      07.6

      Thực nghiệm và đánh giá

      Bốn chỉ số trước và sau khi dùng Cube; mọi con số đều lấy từ case study.

      Handbook đưa ra bảng so sánh bốn chỉ số. Dùng công tắc để chuyển giữa trạng thái trước và sau. Thanh ngang so sánh mỗi giá trị với giá trị “trước” của cùng chỉ số.

      Số liệu từ case study trong handbook
      Hình 7Chi phí, độ trễ, mức lệch chỉ số và độ chính xác của AI Agent.

      Handbook không mô tả cách đo: kích thước tập dữ liệu, thời gian quan sát, bộ câu hỏi dùng để chấm AI, hay ai chấm. Vì vậy, bảng này nên được xem là giả thuyết cần kiểm chứng, không phải kết quả có thể trích dẫn.

      Ghi nhớTheo case study, phần lớn chi phí giảm nhờ pre-aggregations: truy vấn lặp lại được trả lời từ Cube Store thay vì quét lại data warehouse.
      07.7

      Kết quả, đánh đổi và bài học

      Chấp nhận chi phí cho Cube Store để giảm chi phí quét trên data warehouse.

      Cube Store phát sinh chi phí riêng: dung lượng ổ đĩa cho các file Parquet và RAM cho các worker node. Đổi lại, khi bật rollup-only, các truy vấn lặp lại từ người dùng và AI Agent được trả lời tại Cube Store, nhờ đó data warehouse chính không phải chịu concurrency lớn. Chọn từng mục để xem chi tiết đánh đổi.

      Hình 8Chi phí bỏ ra, lợi ích nhận được và điểm cần lưu ý.
      Ghi nhớBài học handbook rút ra: thiết kế data model đúng từ đầu (snake_case, tách cubes và views, khai báo relationship đúng) giúp giảm nhiều công sức bảo trì về sau.
      07.8

      Hướng dẫn tái hiện

      Bốn bước chạy Cube Core và Postgres trên máy local bằng Docker Compose.

      Lưu ý về repoHandbook dùng repo nickmancol/cube_bubble.io. Đây là repo mẫu tích hợp Cube với Bubble.io, không phải mã nguồn của case study này. Handbook không nêu rằng repo có sẵn data model ở 07.3; để thử model đó, cần tự đặt các file vào thư mục model/ và có dữ liệu tương ứng.

      Đánh dấu từng bước sau khi hoàn thành. Tên container và file SQL được giữ nguyên từ handbook; các tên này phụ thuộc vào repo mẫu và phiên bản Docker Compose, vì vậy cần kiểm tra lại bằng docker ps nếu lệnh báo lỗi.

        Hình 9Các bước tái hiện. Trạng thái được lưu trong trình duyệt của người đọc.
        Kết thúc chương cuốiVề trang tổng→

        Case study trong chương là tình huống giả định; các con số đều là số minh họa của handbook.