perf: improve parallelization in SQLES

It turns out SQLES was doing some things very sequentially when this was
not necessary. This should make SQLES a lot faster in general.
This commit is contained in:
KernelDeimos
2025-12-17 17:12:33 -05:00
committed by Eric Dubé
parent 3b646328f7
commit 6edd7debb8
2 changed files with 35 additions and 33 deletions
+19 -33
View File
@@ -24,6 +24,9 @@ const { Entity } = require('./Entity');
const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature');
const { And, Or, Eq, Like, Null, Predicate, PredicateUtil, IsNotNull, StartsWith } = require('../query/query');
const { DB_WRITE } = require('../../services/database/consts');
const { safeHasOwnProperty } = require('../../util/safety');
const { ParallelTasks } = require('../../util/otelutil');
const opentelemetry = require('@opentelemetry/api');
class RawCondition extends AdvancedBase {
// properties: sql:string, values:any[]
@@ -113,12 +116,9 @@ class SQLES extends BaseES {
const rows = await this.db.read(stmt, values);
const entities = [];
for ( const data of rows ) {
const entity = await this.sql_row_to_entity_(data);
entities.push(entity);
}
const entities = await Promise.all(rows.map(async (data) => {
return await this.sql_row_to_entity_(data);
}));
return entities;
},
@@ -187,6 +187,7 @@ class SQLES extends BaseES {
async sql_row_to_entity_ (data) {
const entity_data = {};
const tasks = new ParallelTasks({ tracer: opentelemetry.trace.getTracer('sqles') });
for ( const prop of Object.values(this.om.properties) ) {
const options = prop.descriptor.sql ?? {};
@@ -196,38 +197,23 @@ class SQLES extends BaseES {
const col_name = options.column_name ?? prop.name;
if ( ! data.hasOwnProperty(col_name) ) {
if ( ! safeHasOwnProperty(data, col_name) ) {
continue;
}
let value = data[col_name];
value = await prop.sql_dereference(value);
// TODO: This is not an ideal implementation,
// but this is only 6 lines of code so doing this
// "properly" is not sensible at this time.
//
// This is here because:
// - SQLES has access to the "db" object
//
// Writing this in `json`'s `sql_reference` method
// is also not ideal because that places the concern
// of supporting different database backends to
// property types.
//
// Best solution: SQLES has a SQLRefinements by
// composition. This SQLRefinements is applied
// to property types for the duration of this
// function.
if ( prop.typ.name === 'json' ) {
value = this.db.case({
mysql: () => value,
otherwise: () => JSON.parse(value ?? '{}'),
})();
}
entity_data[prop.name] = value;
tasks.add(`sql_row_to_entity_::${prop.name}`, async () => {
value = await prop.sql_dereference(value);
if ( prop.typ.name === 'json' ) {
value = this.db.case({
mysql: () => value,
otherwise: () => JSON.parse(value ?? '{}'),
})();
}
entity_data[prop.name] = value;
});
}
await tasks.awaitAll();
const entity = await Entity.create({ om: this.om }, entity_data);
entity.private_meta.mysql_id = data.id;
return entity;
+16
View File
@@ -0,0 +1,16 @@
/**
* Instead of `myObject.hasOwnProperty(k)`, always write:
* `safeHasOwnProperty(myObject, k)`.
*
* This is a less verbose way to call `Object.prototype.hasOwnProperty.call`.
* This prevents unexpected behavior when `hasOwnProperty` is overridden,
* which is especially possible for objects parsed from user-sent JSON.
*
* explanation: https://eslint.org/docs/latest/rules/no-prototype-builtins
* @param {*} o
* @param {...any} a
* @returns
*/
export const safeHasOwnProperty = (o, ...a) => {
return Object.prototype.hasOwnProperty.call(o, ...a);
};