Skip to content

Commit

Permalink
Fix issue where query get interrupted during prepare. (backport #7592)…
Browse files Browse the repository at this point in the history
… [release/4.10.x] (#7654)

Co-authored-by: affank <khanaffan@gmail.com>
Co-authored-by: Affan Khan <khanaffan@users.noreply.github.com>
Co-authored-by: imodeljs-admin <38288322+imodeljs-admin@users.noreply.github.com>
Co-authored-by: Arun George <11051042+aruniverse@users.noreply.github.com>
  • Loading branch information
5 people authored Feb 12, 2025
1 parent dabe07e commit 33d451b
Show file tree
Hide file tree
Showing 13 changed files with 341 additions and 26 deletions.
39 changes: 30 additions & 9 deletions common/api/core-common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,8 @@ export interface BaseReaderOptions {
priority?: number;
quota?: QueryQuota;
restartToken?: string;
// @internal (undocumented)
testingArgs?: TestingArgs;
usePrimaryConn?: boolean;
}

Expand Down Expand Up @@ -1998,15 +2000,19 @@ export interface DbBlobResponse extends DbResponse {

// @internal (undocumented)
export interface DbQueryConfig {
allowTestingArgs?: boolean;
autoShutdowWhenIdlelForSeconds?: number;
// (undocumented)
globalQuota?: QueryQuota;
doNotUsePrimaryConnToPrepare?: boolean;
// (undocumented)
globalQuota?: QueryQuota;
ignoreDelay?: boolean;
// (undocumented)
ignorePriority?: boolean;
memoryMapFileSize?: number;
// (undocumented)
monitorPollInterval?: number;
requestQueueSize?: number;
// (undocumented)
statementCacheSizePerWorker?: number;
workerThreads?: number;
}

Expand Down Expand Up @@ -2045,6 +2051,8 @@ export interface DbQueryResponse extends DbResponse {
export interface DbRequest extends BaseReaderOptions {
// (undocumented)
kind?: DbRequestKind;
// (undocumented)
testingArgs?: TestingArgs;
}

// @internal (undocumented)
Expand Down Expand Up @@ -2096,17 +2104,19 @@ export enum DbResponseStatus {
// (undocumented)
Error_BlobIO_OutOfRange = 106,/* could not submit the query as queue was full.*/
// (undocumented)
Error_ECSql_BindingFailed = 104,/* generic error*/
Error_ECSql_BindingFailed = 104,/* Shutdown is in progress. */
// (undocumented)
Error_ECSql_PreparedFailed = 101,/* generic error*/
// (undocumented)
Error_ECSql_PreparedFailed = 101,/* ecsql prepared failed*/
Error_ECSql_RowToJsonFailed = 103,/* ecsql prepared failed*/
// (undocumented)
Error_ECSql_RowToJsonFailed = 103,/* ecsql step failed*/
Error_ECSql_StepFailed = 102,/* ecsql step failed*/
// (undocumented)
Error_ECSql_StepFailed = 102,/* ecsql failed to serialized row to json.*/
Partial = 3,/* ecsql failed to serialized row to json.*/
// (undocumented)
Partial = 3,/* ecsql binding failed.*/
QueueFull = 5,/* ecsql binding failed.*/
// (undocumented)
QueueFull = 5,/* class or property or instance specified was not found or property as not of type blob.*/
ShuttingDown = 6,/* class or property or instance specified was not found or property as not of type blob.*/
// (undocumented)
Timeout = 4
}
Expand All @@ -2122,6 +2132,8 @@ export interface DbRuntimeStats {
// (undocumented)
memUsed: number;
// (undocumented)
prepareTime: number;
// (undocumented)
timeLimit: number;
// (undocumented)
totalTime: number;
Expand Down Expand Up @@ -7477,6 +7489,8 @@ export class QueryOptionsBuilder {
setRestartToken(val: string): this;
setRowFormat(val: QueryRowFormat): this;
setSuppressLogErrors(val: boolean): this;
// @internal
setTestingArgs(val: TestingArgs): this;
setUsePrimaryConnection(val: boolean): this;
}

Expand Down Expand Up @@ -7548,6 +7562,7 @@ export interface QueryStats {
backendMemUsed: number;
backendRowsReturned: number;
backendTotalTime: number;
prepareTime: number;
retryCount: number;
totalTime: number;
}
Expand Down Expand Up @@ -9711,6 +9726,12 @@ export class TerrainSettings {
toJSON(): TerrainProps;
}

// @internal (undocumented)
export interface TestingArgs {
// (undocumented)
interrupt?: boolean;
}

// @internal
export class TestRpcManager {
// (undocumented)
Expand Down
1 change: 1 addition & 0 deletions common/api/summary/core-common.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ public;interface;TerrainProps
public;type;TerrainProviderName
deprecated;type;TerrainProviderName
public;class;TerrainSettings
internal;interface;TestingArgs
internal;class;TestRpcManager
beta;class;TextAnnotation
public;interface;TextAnnotation2dProps
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-backend",
"comment": "Fix issue with concurrent query where it interrupt statement during prepare",
"type": "none"
}
],
"packageName": "@itwin/core-backend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-common",
"comment": "Update ECSql reader api to support no stat and error code.",
"type": "none"
}
],
"packageName": "@itwin/core-common"
}
16 changes: 5 additions & 11 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"webpack": "^5.76.0"
},
"dependencies": {
"@bentley/imodeljs-native": "4.10.33",
"@bentley/imodeljs-native": "4.10.34",
"@itwin/cloud-agnostic-core": "^2.2.4",
"@itwin/core-telemetry": "workspace:*",
"@itwin/object-storage-azure": "^2.2.5",
Expand Down
186 changes: 186 additions & 0 deletions core/backend/src/test/ecdb/ConcurrentQueryLoad.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Logger, LogLevel, StopWatch } from "@itwin/core-bentley";
import { DbQueryConfig, ECSqlReader, QueryStats } from "@itwin/core-common";
import { expect } from "chai";
import { ConcurrentQuery } from "../../ConcurrentQuery";
import { ECDb } from "../../ECDb";
import { IModelDb, SnapshotDb } from "../../IModelDb";
import { _nativeDb } from "../../core-backend";
import { IModelTestUtils } from "../IModelTestUtils";

interface ITaskResult {
stats: QueryStats;
error?: any;
}

interface ISenario {
name: string;
config?: DbQueryConfig;
totalBatches: number;
taskPerBatch: number;
createReader: (db: ECDb | IModelDb) => ECSqlReader;
}


class LoadSimulator {
constructor(public db: ECDb | IModelDb, public senario: ISenario) { }
private async runQueryTask(reader: ECSqlReader): Promise<ITaskResult> {
try {
while (await reader.step()) { }
return { stats: reader.stats };
} catch (err) {
return { stats: reader.stats, error: err };
}
}

public async run() {
ConcurrentQuery.shutdown(this.db[_nativeDb]);
if (this.senario.config) {
const config = ConcurrentQuery.resetConfig(this.db[_nativeDb], this.senario.config);
// eslint-disable-next-line no-console
console.log(config);
}
const overalTime = new StopWatch();
overalTime.start();
const results: ITaskResult[] = [];
for (let i = 0; i < this.senario.totalBatches; ++i) {
const promises: Promise<ITaskResult>[] = [];
const readerTasks = Array(this.senario.taskPerBatch).fill(undefined).map(() => this.senario.createReader(this.db));
readerTasks.forEach((reader) => {
promises.push(this.runQueryTask(reader));
});
results.push(... await Promise.all(promises));

}
overalTime.stop();
const errors = results.filter((x) => x.error !== undefined);
const errorsMap = new Map<string, number>();
errors.forEach((x) => {
if (x.error instanceof Error) {
if (!errorsMap.has(x.error.message)) {
errorsMap.set(x.error.message, 1);
} else {
errorsMap.set(x.error.message, errorsMap.get(x.error.message)! + 1);
}
} else {
if (!errorsMap.has("error")) {
errorsMap.set("error", 1);
} else {
errorsMap.set("error", errorsMap.get("error")! + 1);
}
}
});
const errorCount = errors.length;
let backendCpuTime: bigint = BigInt(0);
let backendTotalTime: bigint = BigInt(0);
let backendMemUsed: bigint = BigInt(0);
let backendRowsReturned: bigint = BigInt(0);
let totalTime: bigint = BigInt(0);
let retryCount: bigint = BigInt(0);
let prepareTime: bigint = BigInt(0);

// Calculate average
results.forEach((r: ITaskResult) => {
backendCpuTime += BigInt(r.stats.backendCpuTime);
backendTotalTime += BigInt(r.stats.backendTotalTime);
backendMemUsed += BigInt(r.stats.backendMemUsed);
backendRowsReturned += BigInt(r.stats.backendRowsReturned);
totalTime += BigInt(r.stats.totalTime);
retryCount += BigInt(r.stats.retryCount);
prepareTime += BigInt(r.stats.prepareTime);
});

backendCpuTime /= BigInt(results.length);
backendTotalTime /= BigInt(results.length);
backendMemUsed /= BigInt(results.length);
backendRowsReturned /= BigInt(results.length);
totalTime /= BigInt(results.length);
retryCount /= BigInt(results.length);
// prepareTime /= BigInt(results.length);

return {
result: {
backendCpuTime,
backendTotalTime,
backendMemUsed,
backendRowsReturned,
totalTime,
retryCount,
prepareTime,
},
overalTimeInSec: overalTime.currentSeconds,
errorCount,
totalQueries: results.length,
errorMap: errorsMap
};

}
}

describe.skip("ConcurrentQueryLoad", () => {
it("should run", async () => {
Logger.initializeToConsole();
Logger.setLevel("ECDb.ConcurrentQuery", LogLevel.Trace);
// {
// workerThreads: 4,
// requestQueueSize: 2000,
// ignorePriority: false,
// ignoreDelay: true,
// doNotUsePrimaryConnToPrepare: false,
// autoShutdowWhenIdlelForSeconds: 300,
// statementCacheSizePerWorker: 40,
// monitorPollInterval: 1000,
// memoryMapFileSize: 0,
// allowTestingArgs: false,
// globalQuota: { time: 60, memory: 8388608 }
// }

const senario: ISenario = {
name: "ConcurrentQueryLoad",
config: {

},
totalBatches: 1,
taskPerBatch: 1,
createReader: (dbs: ECDb | IModelDb) => {
const quries = [
{
sql: `
WITH sequence(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM sequence WHERE n < 10000
)
SELECT COUNT(*)
FROM bis.SpatialIndex i, sequence s
WHERE i.ECInstanceId MATCH iModel_spatial_overlap_aabb(
iModel_bbox(random(), random(), random(), random(),random(), random()))`
},
{
sql: `
WITH sequence(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM sequence WHERE n < 1000000
)
SELECT COUNT(*) FROM sequence`
},
{
sql: "SELECT $ FROM bis.Element LIMIT 10000"
}
];
const idx = Math.floor(Math.random() * quries.length);
return dbs.createQueryReader(quries[idx].sql);
}
};

const verySmallFile = IModelTestUtils.resolveAssetFile("test.bim");
const db = SnapshotDb.openFile(verySmallFile);
const simulator = new LoadSimulator(db, senario);
const result = await simulator.run();
// eslint-disable-next-line no-console
console.log(result);
db.close();
expect(result.errorCount).to.be.equal(0);
});

});
Loading

0 comments on commit 33d451b

Please sign in to comment.