Skip to content

Instantly share code, notes, and snippets.

@VigilioYonatan
Last active July 27, 2025 14:11
Show Gist options
  • Save VigilioYonatan/2c116be9801622528d58d8bbbcf29402 to your computer and use it in GitHub Desktop.
Save VigilioYonatan/2c116be9801622528d58d8bbbcf29402 to your computer and use it in GitHub Desktop.
optimizacion good practices.
// Se agrega un campo mas en los DELETEAT, y a sus childs isDelete
// DELETE CONSUME MUCHA MEMORIA, usar DELETE una vez cada 24 horas
const { User, Post, Comment, Like } = require('../models');
class UserService {
async softDeleteUser(userId) {
const transaction = await sequelize.transaction();
try {
// 1. Marcar usuario como eliminado
await User.update(
{ isDeleted: true, deletedAt: new Date() },
{ where: { id: userId }, transaction }
);
// 2. Marcar posts del usuario como eliminados
await Post.update(
{ isDeleted: true },
{ where: { userId }, transaction }
);
// 3. Marcar comentarios del usuario como eliminados
await Comment.update(
{ isDeleted: true },
{ where: { userId }, transaction }
);
// 4. Eliminar likes del usuario (relación directa sin soft delete), los likes son campos con pocas tablas, no llegan a tener imagenes, etc pesados
await Like.destroy({
where: { userId },
transaction
});
await transaction.commit();
// Programar limpieza física para después
setTimeout(() => this.physicalCleanup(userId), 24 * 60 * 60 * 1000); // 24 horas después o poner la fecha que deseas -. menos de 1 mes recomendable.
return { success: true, message: 'Usuario marcado para eliminación' };
} catch (error) {
await transaction.rollback();
throw error;
}
}
async physicalCleanup(userId) {
const transaction = await sequelize.transaction();
try {
// Eliminar en lotes para evitar bloqueos
let hasMoreComments = true;
while (hasMoreComments) {
const result = await Comment.destroy({
where: { userId, isDeleted: true },
limit: 100,
transaction
});
hasMoreComments = result === 100;
}
let hasMorePosts = true;
while (hasMorePosts) {
const result = await Post.destroy({
where: { userId, isDeleted: true },
limit: 100,
transaction
});
hasMorePosts = result === 100;
}
// Finalmente eliminar el usuario
await User.destroy({
where: { id: userId, isDeleted: true },
transaction
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
// Reintentar más tarde
setTimeout(() => this.physicalCleanup(userId), 60 * 60 * 1000); // Reintentar en 1 hora
}
}
}
module.exports = new UserService();
// Claro CUANDO VAS A MOSTRAR usa User.finAdll({where:{isDelete:null}) al igual que a sus hijos
/**
*
Esto sirve para no consumir muchos recursos a la hora de insertar muchos datos
*/
export async function bulkCreateWithNestedRelations<
T extends object,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
R extends RelationConfig<any, any>[] = RelationConfig<any, any>[]
>(
mainConfig: BulkCreateConfig<T>,
relations: R,
options: { transaction?: Transaction } = {}
): Promise<Array<T & { id: number }>> {
const transaction = options.transaction;
const chunkSize = mainConfig.chunkSize || 1000;
const processInChunks = async <D extends object>(
model: typeof Model<D, D>,
data: D[],
config?: Omit<BulkCreateConfig<D>, "data" | "model" | "transaction">
): Promise<Array<D & { id: number }>> => {
const results: Array<D & { id: number }> = [];
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const created = await (model as any).bulkCreate(chunk, {
transaction,
returning: true,
...config,
});
results.push(...created);
}
return results;
};
const createLevel = async <P extends object, C extends object>(
parentData: P[],
parentResults: Array<P & { id: number }>,
relationConfig: RelationConfig<P, C>,
childConfig: BulkCreateConfig<C>
): Promise<Array<C & { id: number }>> => {
const childrenToCreate: C[] = [];
for (const [i, parentItem] of parentData.entries()) {
const parentResult = parentResults[i];
const children =
(parentItem[relationConfig.childrenField] as unknown as C[]) ||
[];
for (const child of children) {
let childData = { ...child };
if (childConfig.excludeFields) {
for (const field of childConfig.excludeFields) {
delete childData[field as keyof C];
}
}
childData[relationConfig.relationField] =
parentResult.id as never;
if (childConfig.beforeCreate) {
childData = childConfig.beforeCreate(
childData as Omit<C, "id">
) as C;
}
childrenToCreate.push(childData);
}
}
if (childrenToCreate.length > 0) {
return processInChunks(
childConfig.model,
childrenToCreate,
childConfig
);
}
return [];
};
// Process main data
const mainData = mainConfig.data.map((item) => {
const itemData = { ...item };
if (mainConfig.excludeFields) {
for (const field of mainConfig.excludeFields) {
delete itemData[field as keyof Omit<T, "id">];
}
}
return mainConfig.beforeCreate
? mainConfig.beforeCreate(itemData)
: itemData;
});
const mainResults = await processInChunks(
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(mainConfig as any).model,
mainData
);
// Process nested relations
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
let currentParents: any[] = mainConfig.data;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
let currentResults: any[] = mainResults;
for (const relation of relations) {
const childResults = await createLevel(
currentParents,
currentResults,
relation,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
relation.config as BulkCreateConfig<any>
);
// Update for next level
currentParents = currentParents.flatMap(
(parent) =>
(parent[relation.childrenField as keyof T] as object[]) || []
) as T[];
currentResults = childResults;
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
return mainResults as any;
}
// USO, ADIOS promise.all()
body.pages = [{
id:1,
...rest,
gridbanners:[
{
id:1,
...rest,
gridBanners_items:[{...rest}]
}
]
}]
const createdPages = await bulkCreateWithNestedRelations(
{
model: Page,
data: body.pages,
excludeFields: ['id', 'createdAt', 'updatedAt'], // quito id, etc por que eso no es necesario
beforeCreate: (page) => ({
...page,
empresa_id: empresa!.id // si quiero persalizar el inserto
}),
chunkSize: 1000 // 500 - 5000 para que no enviar milloiens en solo bulk para no consumir mucha memoria
},
[ // si tienes mas hijos etc
{
relationField: 'page_id',
childrenField: 'grid_banners',
config: {
model: GridBanner,
excludeFields: ['id', 'createdAt', 'updatedAt'],
beforeCreate: (banner) => ({
...banner,
empresa_id: empresa!.id
})
}
},
{
relationField: 'grid_banner_id',
childrenField: 'banner_items',
config: {
model: BannerItem,
excludeFields: ['id', 'createdAt', 'updatedAt']
}
}
]
);

Comments are disabled for this gist.