mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
Merge pull request #15643 from strapi/fix/performance-degradation-in-deeply-nested-coimpos
This commit is contained in:
commit
5645d2ad0a
@ -366,7 +366,16 @@ describe('Entity service', () => {
|
||||
|
||||
const fakeStrapi = {
|
||||
getModel: jest.fn((modelName) => fakeModels[modelName]),
|
||||
query: jest.fn(() => fakeQuery),
|
||||
db: {
|
||||
dialect: {
|
||||
client: 'sqlite',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
global.strapi = fakeStrapi;
|
||||
|
||||
instance = createEntityService({
|
||||
strapi: fakeStrapi,
|
||||
db: fakeDB,
|
||||
|
||||
@ -3,10 +3,12 @@
|
||||
const _ = require('lodash');
|
||||
const { has, prop, omit, toString, pipe, assign } = require('lodash/fp');
|
||||
|
||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
||||
const { contentTypes: contentTypesUtils, mapAsync } = require('@strapi/utils');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const { getComponentAttributes } = require('@strapi/utils').contentTypes;
|
||||
|
||||
const isDialectMySQL = () => strapi.db.dialect.client === 'mysql';
|
||||
|
||||
const omitComponentData = (contentType, data) => {
|
||||
const { attributes } = contentType;
|
||||
const componentAttributes = Object.keys(attributes).filter((attributeName) =>
|
||||
@ -43,10 +45,12 @@ const createComponents = async (uid, data) => {
|
||||
throw new Error('Expected an array to create repeatable component');
|
||||
}
|
||||
|
||||
const components = [];
|
||||
for (const value of componentValue) {
|
||||
components.push(await createComponent(componentUID, value));
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
const components = await mapAsync(
|
||||
componentValue,
|
||||
(value) => createComponent(componentUID, value),
|
||||
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
||||
);
|
||||
|
||||
componentBody[attributeName] = components.map(({ id }) => {
|
||||
return {
|
||||
@ -78,19 +82,23 @@ const createComponents = async (uid, data) => {
|
||||
throw new Error('Expected an array to create repeatable component');
|
||||
}
|
||||
|
||||
const dynamicZoneData = [];
|
||||
for (const value of dynamiczoneValues) {
|
||||
const createDynamicZoneComponents = async (value) => {
|
||||
const { id } = await createComponent(value.__component, value);
|
||||
dynamicZoneData.push({
|
||||
return {
|
||||
id,
|
||||
__component: value.__component,
|
||||
__pivot: {
|
||||
field: attributeName,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
componentBody[attributeName] = dynamicZoneData;
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
componentBody[attributeName] = await mapAsync(
|
||||
dynamiczoneValues,
|
||||
createDynamicZoneComponents,
|
||||
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -139,10 +147,12 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
||||
throw new Error('Expected an array to create repeatable component');
|
||||
}
|
||||
|
||||
const components = [];
|
||||
for (const value of componentValue) {
|
||||
components.push(await updateOrCreateComponent(componentUID, value));
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
const components = await mapAsync(
|
||||
componentValue,
|
||||
(value) => updateOrCreateComponent(componentUID, value),
|
||||
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
||||
);
|
||||
|
||||
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
|
||||
return {
|
||||
@ -176,19 +186,22 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
||||
throw new Error('Expected an array to create repeatable component');
|
||||
}
|
||||
|
||||
const dynamicZoneData = [];
|
||||
for (const value of dynamiczoneValues) {
|
||||
const { id } = await updateOrCreateComponent(value.__component, value);
|
||||
dynamicZoneData.push({
|
||||
id,
|
||||
__component: value.__component,
|
||||
__pivot: {
|
||||
field: attributeName,
|
||||
},
|
||||
});
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
componentBody[attributeName] = await mapAsync(
|
||||
dynamiczoneValues,
|
||||
async (value) => {
|
||||
const { id } = await updateOrCreateComponent(value.__component, value);
|
||||
|
||||
componentBody[attributeName] = dynamicZoneData;
|
||||
return {
|
||||
id,
|
||||
__component: value.__component,
|
||||
__pivot: {
|
||||
field: attributeName,
|
||||
},
|
||||
};
|
||||
},
|
||||
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -290,14 +303,18 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } =
|
||||
|
||||
if (attribute.type === 'component') {
|
||||
const { component: componentUID } = attribute;
|
||||
for (const subValue of _.castArray(value)) {
|
||||
await deleteComponent(componentUID, subValue);
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
await mapAsync(_.castArray(value), (subValue) => deleteComponent(componentUID, subValue), {
|
||||
concurrency: isDialectMySQL() ? 1 : Infinity,
|
||||
});
|
||||
} else {
|
||||
// delete dynamic zone components
|
||||
for (const subValue of _.castArray(value)) {
|
||||
await deleteComponent(subValue.__component, subValue);
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
await mapAsync(
|
||||
_.castArray(value),
|
||||
(subValue) => deleteComponent(subValue.__component, subValue),
|
||||
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
6
packages/core/utils/lib/async.d.ts
vendored
6
packages/core/utils/lib/async.d.ts
vendored
@ -4,3 +4,9 @@ export type MapAsync<T = any, R = any> = lodash.CurriedFunction3<
|
||||
{ concurrency?: number },
|
||||
Promise<R[]>
|
||||
>;
|
||||
|
||||
export type ForEachAsync<T = any, R = any> = (
|
||||
array: T[],
|
||||
func: (element: T, index: number) => R | Promise<R>,
|
||||
options?: { concurrency?: number }
|
||||
) => Promise<R[]>;
|
||||
|
||||
@ -20,7 +20,15 @@ function pipeAsync(...methods) {
|
||||
*/
|
||||
const mapAsync = curry(pMap);
|
||||
|
||||
/**
|
||||
* @type { import('./async').ForEachAsync }
|
||||
*/
|
||||
const forEachAsync = curry(async (array, func, options) => {
|
||||
await mapAsync(array, func, options);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
mapAsync,
|
||||
forEachAsync,
|
||||
pipeAsync,
|
||||
};
|
||||
|
||||
@ -37,7 +37,7 @@ const providerFactory = require('./provider-factory');
|
||||
const pagination = require('./pagination');
|
||||
const sanitize = require('./sanitize');
|
||||
const traverseEntity = require('./traverse-entity');
|
||||
const { pipeAsync, mapAsync } = require('./async');
|
||||
const { pipeAsync, mapAsync, forEachAsync } = require('./async');
|
||||
const convertQueryParams = require('./convert-query-params');
|
||||
const importDefault = require('./import-default');
|
||||
const template = require('./template');
|
||||
@ -82,6 +82,7 @@ module.exports = {
|
||||
pagination,
|
||||
pipeAsync,
|
||||
mapAsync,
|
||||
forEachAsync,
|
||||
errors,
|
||||
validateYupSchema,
|
||||
validateYupSchemaSync,
|
||||
|
||||
@ -65,6 +65,11 @@ const setGlobalStrapi = () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
dialect: {
|
||||
client: 'sqlite',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -2,8 +2,11 @@
|
||||
|
||||
const { prop, isNil, isEmpty } = require('lodash/fp');
|
||||
|
||||
const { forEachAsync } = require('@strapi/utils');
|
||||
const { getService } = require('../utils');
|
||||
|
||||
const isDialectMySQL = () => strapi.db.dialect.client === 'mysql';
|
||||
|
||||
/**
|
||||
* Adds the default locale to an object if it isn't defined yet
|
||||
* @param {Object} data a data object before being persisted into db
|
||||
@ -32,9 +35,10 @@ const syncLocalizations = async (entry, { model }) => {
|
||||
return strapi.query(model.uid).update({ where: { id }, data: { localizations } });
|
||||
};
|
||||
|
||||
for (const localization of entry.localizations) {
|
||||
await updateLocalization(localization.id);
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
await forEachAsync(entry.localizations, (localization) => updateLocalization(localization.id), {
|
||||
concurrency: isDialectMySQL() ? 1 : Infinity,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -58,9 +62,10 @@ const syncNonLocalizedAttributes = async (entry, { model }) => {
|
||||
return strapi.entityService.update(model.uid, id, { data: nonLocalizedAttributes });
|
||||
};
|
||||
|
||||
for (const localization of entry.localizations) {
|
||||
await updateLocalization(localization.id);
|
||||
}
|
||||
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
||||
await forEachAsync(entry.localizations, (localization) => updateLocalization(localization.id), {
|
||||
concurrency: isDialectMySQL() ? 1 : Infinity,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user