Skip to content

Commit aa82256

Browse files
committed
fix: Broken query and pre-aggregation cancel
1 parent 950ba84 commit aa82256

File tree

6 files changed

+113
-48
lines changed

6 files changed

+113
-48
lines changed

packages/cubejs-mysql-driver/driver/MySqlDriver.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,12 @@ class MySqlDriver extends BaseDriver {
6363

6464
let cancelled = false;
6565
const cancelObj = {};
66-
const promise = connectionPromise.then(conn => {
66+
const promise = connectionPromise.then(async conn => {
67+
const [{ connectionId }] = await conn.execute('select connection_id() as connectionId');
6768
cancelObj.cancel = async () => {
6869
cancelled = true;
6970
await self.withConnection(async processConnection => {
70-
const processRows = await processConnection.execute('SHOW PROCESSLIST');
71-
await Promise.all(processRows.filter(row => row.Time >= 599)
72-
.map(row => processConnection.execute(`KILL ${row.Id}`)));
71+
await processConnection.execute(`KILL ${connectionId}`);
7372
});
7473
};
7574
return fn(conn)
@@ -128,10 +127,12 @@ class MySqlDriver extends BaseDriver {
128127
return GenericTypeToMySql[columnType] || super.fromGenericType(columnType);
129128
}
130129

131-
async loadPreAggregationIntoTable(preAggregationTableName, loadSql, params, tx) {
130+
loadPreAggregationIntoTable(preAggregationTableName, loadSql, params, tx) {
132131
if (this.config.loadPreAggregationWithoutMetaLock) {
133-
await this.query(`${loadSql} LIMIT 0`, params);
134-
return this.query(loadSql.replace(/^CREATE TABLE (\S+) AS/i, 'INSERT INTO $1'), params);
132+
return this.cancelCombinator(async saveCancelFn => {
133+
await saveCancelFn(this.query(`${loadSql} LIMIT 0`, params));
134+
await saveCancelFn(this.query(loadSql.replace(/^CREATE TABLE (\S+) AS/i, 'INSERT INTO $1'), params));
135+
});
135136
}
136137
return super.loadPreAggregationIntoTable(preAggregationTableName, loadSql, params, tx);
137138
}

packages/cubejs-query-orchestrator/driver/BaseDriver.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { reduce } = require('ramda');
2+
const { cancelCombinator } = require('./utils');
23

34
const sortByKeys = (unordered) => {
45
const ordered = {};
@@ -169,6 +170,10 @@ class BaseDriver {
169170
quoteIdentifier(identifier) {
170171
return `"${identifier}"`;
171172
}
173+
174+
cancelCombinator(fn) {
175+
return cancelCombinator(fn);
176+
}
172177
}
173178

174179
module.exports = BaseDriver;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
exports.cancelCombinator = (fn) => {
2+
const cancelFnArray = [];
3+
const saveCancelFn = promise => {
4+
if (promise.cancel) {
5+
cancelFnArray.push(promise.cancel);
6+
}
7+
return promise;
8+
};
9+
const promise = fn(saveCancelFn);
10+
promise.cancel = () => Promise.all(cancelFnArray.map(cancel => cancel()));
11+
return promise;
12+
};

packages/cubejs-query-orchestrator/orchestrator/PreAggregations.js

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const crypto = require('crypto');
22
const R = require('ramda');
3+
const { cancelCombinator } = require('../driver/utils');
34
const RedisCacheDriver = require('./RedisCacheDriver');
45
const LocalCacheDriver = require('./LocalCacheDriver');
56

@@ -380,13 +381,13 @@ class PreAggregationLoader {
380381
refreshStrategy = readOnly ?
381382
this.refreshImplStreamExternalStrategy : this.refreshImplTempTableExternalStrategy;
382383
}
383-
const resultPromise = refreshStrategy.bind(this)(client, newVersionEntry);
384-
resultPromise.cancel = () => {}; // TODO implement cancel (loading rollup into table and external upload)
385-
return resultPromise;
384+
return cancelCombinator(
385+
saveCancelFn => refreshStrategy.bind(this)(client, newVersionEntry, saveCancelFn)
386+
);
386387
};
387388
}
388389

389-
async refreshImplStoreInSourceStrategy(client, newVersionEntry) {
390+
async refreshImplStoreInSourceStrategy(client, newVersionEntry, saveCancelFn) {
390391
const [loadSql, params] =
391392
Array.isArray(this.preAggregation.loadSql) ? this.preAggregation.loadSql : [this.preAggregation.loadSql, []];
392393
const targetTableName = this.targetTableName(newVersionEntry);
@@ -402,18 +403,18 @@ class PreAggregationLoader {
402403
targetTableName,
403404
requestId: this.requestId
404405
});
405-
await client.loadPreAggregationIntoTable(
406+
await saveCancelFn(client.loadPreAggregationIntoTable(
406407
targetTableName,
407408
query,
408409
params
409-
);
410-
await this.createIndexes(client, newVersionEntry);
410+
));
411+
await this.createIndexes(client, newVersionEntry, saveCancelFn);
411412
await this.loadCache.reset(this.preAggregation);
412-
await this.dropOrphanedTables(client, targetTableName);
413+
await this.dropOrphanedTables(client, targetTableName, saveCancelFn);
413414
await this.loadCache.reset(this.preAggregation);
414415
}
415416

416-
async refreshImplTempTableExternalStrategy(client, newVersionEntry) {
417+
async refreshImplTempTableExternalStrategy(client, newVersionEntry, saveCancelFn) {
417418
const [loadSql, params] =
418419
Array.isArray(this.preAggregation.loadSql) ? this.preAggregation.loadSql : [this.preAggregation.loadSql, []];
419420
await client.createSchemaIfNotExists(this.preAggregation.preAggregationsSchema);
@@ -430,18 +431,18 @@ class PreAggregationLoader {
430431
targetTableName,
431432
requestId: this.requestId
432433
});
433-
await client.loadPreAggregationIntoTable(
434+
await saveCancelFn(client.loadPreAggregationIntoTable(
434435
targetTableName,
435436
query,
436437
params
437-
);
438-
const tableData = await this.downloadTempExternalPreAggregation(client, newVersionEntry);
439-
await this.uploadExternalPreAggregation(tableData, newVersionEntry);
438+
));
439+
const tableData = await this.downloadTempExternalPreAggregation(client, newVersionEntry, saveCancelFn);
440+
await this.uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn);
440441
await this.loadCache.reset(this.preAggregation);
441-
await this.dropOrphanedTables(client, targetTableName);
442+
await this.dropOrphanedTables(client, targetTableName, saveCancelFn);
442443
}
443444

444-
async refreshImplStreamExternalStrategy(client, newVersionEntry) {
445+
async refreshImplStreamExternalStrategy(client, newVersionEntry, saveCancelFn) {
445446
const [sql, params] =
446447
Array.isArray(this.preAggregation.sql) ? this.preAggregation.sql : [this.preAggregation.sql, []];
447448
if (!client.downloadQueryResults) {
@@ -452,12 +453,12 @@ class PreAggregationLoader {
452453
preAggregation: this.preAggregation,
453454
requestId: this.requestId
454455
});
455-
const tableData = await client.downloadQueryResults(sql, params);
456-
await this.uploadExternalPreAggregation(tableData, newVersionEntry);
456+
const tableData = await saveCancelFn(client.downloadQueryResults(sql, params));
457+
await this.uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn);
457458
await this.loadCache.reset(this.preAggregation);
458459
}
459460

460-
async downloadTempExternalPreAggregation(client, newVersionEntry) {
461+
async downloadTempExternalPreAggregation(client, newVersionEntry, saveCancelFn) {
461462
if (!client.downloadTable) {
462463
throw new Error(`Can't load external pre-aggregation: source driver doesn't support downloadTable()`);
463464
}
@@ -466,12 +467,12 @@ class PreAggregationLoader {
466467
preAggregation: this.preAggregation,
467468
requestId: this.requestId
468469
});
469-
const tableData = await client.downloadTable(table);
470-
tableData.types = await client.tableColumnTypes(table);
470+
const tableData = await saveCancelFn(client.downloadTable(table));
471+
tableData.types = await saveCancelFn(client.tableColumnTypes(table));
471472
return tableData;
472473
}
473474

474-
async uploadExternalPreAggregation(tableData, newVersionEntry) {
475+
async uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn) {
475476
const table = this.targetTableName(newVersionEntry);
476477
const externalDriver = await this.externalDriverFactory();
477478
if (!externalDriver.uploadTable) {
@@ -481,13 +482,13 @@ class PreAggregationLoader {
481482
preAggregation: this.preAggregation,
482483
requestId: this.requestId
483484
});
484-
await externalDriver.uploadTable(table, tableData.types, tableData);
485-
await this.createIndexes(externalDriver, newVersionEntry);
485+
await saveCancelFn(externalDriver.uploadTable(table, tableData.types, tableData));
486+
await this.createIndexes(externalDriver, newVersionEntry, saveCancelFn);
486487
await this.loadCache.reset(this.preAggregation);
487-
await this.dropOrphanedTables(externalDriver, table);
488+
await this.dropOrphanedTables(externalDriver, table, saveCancelFn);
488489
}
489490

490-
async createIndexes(driver, newVersionEntry) {
491+
async createIndexes(driver, newVersionEntry, saveCancelFn) {
491492
if (!this.preAggregation.indexesSql || !this.preAggregation.indexesSql.length) {
492493
return;
493494
}
@@ -503,7 +504,7 @@ class PreAggregationLoader {
503504
requestId: this.requestId,
504505
sql
505506
});
506-
await driver.query(
507+
await saveCancelFn(driver.query(
507508
QueryCache.replacePreAggregationTableNames(
508509
query,
509510
this.preAggregationsTablesToTempTables.concat([
@@ -512,11 +513,11 @@ class PreAggregationLoader {
512513
])
513514
),
514515
params
515-
);
516+
));
516517
}
517518
}
518519

519-
async dropOrphanedTables(client, justCreatedTable) {
520+
async dropOrphanedTables(client, justCreatedTable, saveCancelFn) {
520521
await this.preAggregations.addTableUsed(justCreatedTable);
521522
const actualTables = await client.getTablesQuery(this.preAggregation.preAggregationsSchema);
522523
const versionEntries = tablesToVersionEntries(this.preAggregation.preAggregationsSchema, actualTables);
@@ -536,7 +537,7 @@ class PreAggregationLoader {
536537
tablesToDrop: JSON.stringify(toDrop),
537538
requestId: this.requestId
538539
});
539-
await Promise.all(toDrop.map(table => client.dropTable(table)));
540+
await Promise.all(toDrop.map(table => saveCancelFn(client.dropTable(table))));
540541
}
541542
}
542543

packages/cubejs-query-orchestrator/orchestrator/QueryQueue.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,16 @@ class QueryQueue {
274274
error: (e.stack || e).toString()
275275
});
276276
if (e instanceof TimeoutError) {
277-
this.logger('Cancelling query due to timeout', {
278-
processingId,
279-
queryKey: query.queryKey,
280-
queuePrefix: this.redisQueuePrefix,
281-
requestId: query.requestId
282-
});
283-
await this.sendCancelMessageFn(query);
277+
const queryWithCancelHandle = await redisClient.getQueryDef(queryKey);
278+
if (queryWithCancelHandle) {
279+
this.logger('Cancelling query due to timeout', {
280+
processingId,
281+
queryKey: queryWithCancelHandle.queryKey,
282+
queuePrefix: this.redisQueuePrefix,
283+
requestId: queryWithCancelHandle.requestId
284+
});
285+
await this.sendCancelMessageFn(queryWithCancelHandle);
286+
}
284287
}
285288
}
286289

packages/cubejs-query-orchestrator/test/QueryOrchestrator.test.js

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
/* globals describe, it, should, before */
1+
/* globals describe, beforeAll, afterAll, test, expect */
22
const QueryOrchestrator = require('../orchestrator/QueryOrchestrator');
33

44
class MockDriver {
55
constructor() {
66
this.tables = [];
77
this.executedQueries = [];
8+
this.cancelledQueries = [];
89
}
910

10-
async query(query) {
11+
query(query) {
1112
this.executedQueries.push(query);
12-
return [query];
13+
let promise = Promise.resolve([query]);
14+
if (query.match(`orders_too_big`)) {
15+
promise = promise.then((res) => new Promise(resolve => setTimeout(() => resolve(res), 3000)));
16+
}
17+
promise.cancel = async () => {
18+
this.cancelledQueries.push(query);
19+
};
20+
return promise;
1321
}
1422

1523
async getTablesQuery(schema) {
@@ -21,19 +29,29 @@ class MockDriver {
2129
return null;
2230
}
2331

24-
async loadPreAggregationIntoTable(preAggregationTableName) {
32+
loadPreAggregationIntoTable(preAggregationTableName, loadSql) {
2533
this.tables.push(preAggregationTableName.substring(0, 100));
34+
return this.query(loadSql);
2635
}
2736

2837
async dropTable(tableName) {
2938
this.tables = this.tables.filter(t => t !== tableName.split('.')[1]);
39+
return this.query(`DROP TABLE ${tableName}`);
3040
}
3141
}
3242

3343
describe('QueryOrchestrator', () => {
3444
let mockDriver = null;
3545
const queryOrchestrator = new QueryOrchestrator(
36-
'TEST', async () => mockDriver, (msg, params) => console.log(msg, params)
46+
'TEST',
47+
async () => mockDriver,
48+
(msg, params) => console.log(msg, params), {
49+
preAggregationsOptions: {
50+
queueOptions: {
51+
executionTimeout: 1
52+
}
53+
}
54+
}
3755
);
3856

3957
beforeAll(() => {
@@ -119,4 +137,29 @@ describe('QueryOrchestrator', () => {
119137
}
120138
expect(thrown).toBe(true);
121139
});
140+
141+
test('cancel pre-aggregation', async () => {
142+
const query = {
143+
query: "SELECT \"orders__created_at_week\" \"orders__created_at_week\", sum(\"orders__count\") \"orders__count\" FROM (SELECT * FROM stb_pre_aggregations.orders_number_and_count20181101) as partition_union WHERE (\"orders__created_at_week\" >= ($1::timestamptz::timestamptz AT TIME ZONE 'UTC') AND \"orders__created_at_week\" <= ($2::timestamptz::timestamptz AT TIME ZONE 'UTC')) GROUP BY 1 ORDER BY 1 ASC LIMIT 10000",
144+
values: ["2018-11-01T00:00:00Z", "2018-11-30T23:59:59Z"],
145+
cacheKeyQueries: {
146+
renewalThreshold: 21600,
147+
queries: [["SELECT date_trunc('hour', (NOW()::timestamptz AT TIME ZONE 'UTC')) as current_hour", []]]
148+
},
149+
preAggregations: [{
150+
preAggregationsSchema: "stb_pre_aggregations",
151+
tableName: "stb_pre_aggregations.orders_number_and_count20181101",
152+
loadSql: ["CREATE TABLE stb_pre_aggregations.orders_number_and_count20181101 AS SELECT\n date_trunc('week', (\"orders\".created_at::timestamptz AT TIME ZONE 'UTC')) \"orders__created_at_week\", count(\"orders\".id) \"orders__count\", sum(\"orders\".number) \"orders__number\"\n FROM\n public.orders_too_big AS \"orders\"\n WHERE (\"orders\".created_at >= $1::timestamptz AND \"orders\".created_at <= $2::timestamptz) GROUP BY 1", ["2018-11-01T00:00:00Z", "2018-11-30T23:59:59Z"]],
153+
invalidateKeyQueries: [["SELECT date_trunc('hour', (NOW()::timestamptz AT TIME ZONE 'UTC')) as current_hour", []]]
154+
}],
155+
renewQuery: true,
156+
requestId: 'cancel pre-aggregation'
157+
};
158+
try {
159+
await queryOrchestrator.fetchQuery(query);
160+
} catch (e) {
161+
expect(e.toString()).toMatch(/timeout/);
162+
}
163+
expect(mockDriver.cancelledQueries[0]).toMatch(/orders_too_big/);
164+
});
122165
});

0 commit comments

Comments
 (0)