cube-js / cube-js/cube

Pre-Aggregations

Open
#8,191 2 comments 0 reactions 0 assignees View on GitHub
question
Dominant language
Rust
Stars
20.8k
Forks
2.1k
Avg merge
1d 2h
Merged PRs (30d)
181

Description

Hello, I'm having some slow querys with dynamic tables so I wanted to add some generic pre-aggs, I'm a little lost on finding good examples and also those I created are not working, and I think is because of the cube.js we have, since we are adding an account_uuid filter to have each database to just have data related to them (we use metabase cloud).

Tools:
- Cube Cloud Latest (0.35.22)
- Metabase Cloud v1.49.6
- Google Cloud (have the prod_pre_aggregations dataset already saving this aggregations I did)

**Related Cube.js schema**
```javascript
const {memoizedFetchAccountPassword} = require("./sql-auth")

function decodeBase64(data) {
let buff = Buffer.from(data, 'base64');
return buff.toString('ascii');
}

const deconstructGlobalId = (globalId) => {
const decoded = decodeBase64(globalId)
const globalIdParts = decoded.split(":")

return {
node: globalIdParts[0],
uuid: globalIdParts[1]
}
}

const contextToAppId = (context) => `CUBEJS_APP_${context.cacct}`

const extendContext = (req) => {
// For SQL API (context is then handled by checkSqlAuth)
if (req.headers === undefined || req.headers.cacct === undefined) {
return;
}

try {
const deconstructed = deconstructGlobalId(req.headers.cacct);
return {cacct: deconstructed["uuid"]};
} catch (err) {
console.log("Error extending context: " + err)
}
}

const queryRewrite = (query, request) => {
console.log("Rewriting for", JSON.stringify(request))
const accountId = request.cacct !== undefined ? request.cacct : request.securityContext.cacct;

if (query["dimensions"].length > 0) {
try {
var table_name = query["dimensions"][0].substring(0, query["dimensions"][0].indexOf("."));
} catch {
var table_name = query["dimensions"][0]["cubeName"]
}
}
// measures in query
else if (query["measures"].length > 0) {
try {
var table_name = query["measures"][0].substring(0, query["measures"][0].indexOf("."))
} catch {
var table_name = query["measures"][0]["cubeName"]
}
}

else if (query["timeDimensions"].length > 0) {
try {
var table_name = query["timeDimensions"][0]["dimension"].substring(0, query["timeDimensions"][0]["dimension"].indexOf("."))
} catch {
var table_name = query["measures"][0]["cubeName"]
}
}

else {
console.log("Using account id:", accountId)
console.log("The query:", query)
}

console.log("Using account id:", accountId);

query.filters.push({
member: `${table_name}.accountUuid`,
operator: 'equals',
values: [accountId],
});

return query;
}

const checkSqlAuth = async (req, username) => {
const deconstructed = deconstructGlobalId(username);
// const password = await memoizedFetchAccountPassword(deconstructed['uuid']);
const password = "stagingaccess";

if (password !== null) {
return {
password,
securityContext: {
cacct: deconstructed['uuid']
}
}
}
}

// To test sql auth locally, uncomment the following line.
// checkSqlAuth({}, "QWNjb3VudDo4NDUyZjg4MS0wYWM1LTRmMjYtOWFhYi0xNmUzN2IyZWM0ZjI=").then((ctx) => console.log("X>", ctx));

const queueOptions = {
concurrency: 2,
executionTimeout: 600,
orphanedTimeout: 120,
heartBeatInterval: 120,
};

module.exports = {
http: {
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
allowedHeaders: ['Content-Type', 'Authorization', 'cacct'],
optionsSuccessStatus: 204,
},
},
scheduledRefreshTimer: 120,
checkSqlAuth,
contextToAppId,
extendContext,
queryRewrite,
orchestratorOptions: {
queryCacheOptions: {
refreshKeyRenewalThreshold: 120,
backgroundRenew: true,
queueOptions,
},
preAggregationsOptions: {queueOptions},
},
// Figure out ScheduledRefreshContexts later! (probably needed for QueryRewrite)
// Placeholder to prevent the error message:
scheduledRefreshContexts: () => [
{
securityContext: {
cacct: '00000000-0000-0000-0000-000000000000'
}
}
],
};

```
So this is one of the dynamic files we have:

```
{% set account = "accountUuid" %}
{% set interaction = "interactionUuid" %}
{% set time = "time" %}
cubes:
{%- for cube in load_interaction_tags()["cubes"] %}

- name: {{ cube.name }}
sql: {{ cube.sql }}
dataSource: default

{%- if cube.dimensions is not none and cube.dimensions|length > 0 %}
pre_aggregations:

- name: {{cube.name + "_table" }}
dimensions:
{%- for dimension in cube.dimensions %}
- {{ dimension.name }}
{%- endfor %}
refresh_key:
every: 12 hour

- name: {{cube.name + "_table2" }}
dimensions:
{%- for dimension in cube.dimensions %}
{%- if dimension.name != account and dimension.name != interaction %}
- {{ dimension.name }}
{%- endif %}
{%- endfor %}
refresh_key:
every: 12 hour

{%- for dimension in cube.dimensions %}

- name: {{ dimension.name + "_filter" }}
dimensions:
- {{ dimension.name }}
refresh_key:
every: 12 hour

- name: {{ dimension.name + "_by_distinctUuid" }}
dimensions:
- {{ dimension.name }}
measures:
- distinctUuid
refresh_key:
every: 12 hour

{%- if dimension.type == time %}
- name: {{ dimension.name + "uuid_by_month" }}
measures:
- distinctUuid
time_dimension: {{ dimension.name }}
granularity: month

- name: {{ dimension.name + "uuid_by_day" }}
measures:
- distinctUuid
time_dimension: {{ dimension.name }}
granularity: day

- name: {{ dimension.name + "uuid_by_year" }}
measures:
- distinctUuid
time_dimension: {{ dimension.name }}
granularity: year

{%- endif %}
{%- endfor %}
{%- endif %}

{%- if cube.joins is not none and cube.joins|length > 0 %}
joins:
{%- for join in cube.joins %}
- name: {{ join.name }}
relationship: {{ join.relationship }}
sql: {{ join.sql }}
{%- endfor %}
{%- endif %}

{%- if cube.measures is not none and cube.measures|length > 0 %}
measures:
{%- for measure in cube.measures %}
- name: {{ measure.name }}
type: {{ measure.type }}
{%- if measure.sql %}
sql: {{ measure.sql }}
{%- endif %}
{%- endfor %}
{%- endif %}

{%- if cube.dimensions is not none and cube.dimensions|length > 0 %}
dimensions:
{%- for dimension in cube.dimensions %}
- name: {{ dimension.name }}
sql: {{ dimension.sql }}
type: {{ dimension.type }}
{% if dimension.primaryKey == True -%}
primaryKey: true
public: true
{% endif -%}
{%- endfor %}
{%- endif %}
{%- endfor %}
```

As you see I already did some pre-aggs for every table, column and some testing ones for date columns, but my problem is first, this is how the table gets to metabase:

![image](https://github.com/cube-js/cube/assets/125912854/ab6166e7-ee12-4af2-a301-cff6e99db2ae)

And this is the query generated in query history:

```sql
-- Metabase:: userID: 1 queryType: MBQL queryHash: de78bb40680846bcd73c5909057caa9eda003637ecc1a97df41551608e5e1b9e
SELECT
"public"."IT_Daily_IPS_CheckIn"."interactionUuid" AS "interactionUuid",
"public"."IT_Daily_IPS_CheckIn"."accountUuid" AS "accountUuid",
"public"."IT_Daily_IPS_CheckIn"."Denied" AS "Denied",
"public"."IT_Daily_IPS_CheckIn"."Duplicates" AS "Duplicates",
"public"."IT_Daily_IPS_CheckIn"."Emailed_for_Approval" AS "Emailed_for_Approval",
"public"."IT_Daily_IPS_CheckIn"."Failed_Posts" AS "Failed_Posts",
"public"."IT_Daily_IPS_CheckIn"."Pending_Approval" AS "Pending_Approval",
"public"."IT_Daily_IPS_CheckIn"."Questions" AS "Questions",
"public"."IT_Daily_IPS_CheckIn"."Second_Email_Approval" AS "Second_Email_Approval",
"public"."IT_Daily_IPS_CheckIn"."__user" AS "__user",
"public"."IT_Daily_IPS_CheckIn"."__cubeJoinField" AS "__cubeJoinField"
FROM
"public"."IT_Daily_IPS_CheckIn"
LIMIT
2000
```
![image](https://github.com/cube-js/cube/assets/125912854/003ab009-05a0-44bf-b1b2-4e1711d7d6e8)
And as you see it doesn't detects the pre-aggregation, I don't know if its because the query has user and cubejoin columns in them, or maybe we have too much pre-aggs?

The thing is I don't find how to do a pre-agg with joins and filters, all this dynamic tables are primarily used joined with two other tables. I want to do something like the two first pre-aggs but with the joins to the tables and filtering not null in a different column for each pre-agg (this is likely the most general useful pre-agg I can create).

Always videos and documentation are welcome, I did all this with what I could find but I cannot say I read them deeply, though I tried.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.