# Melhorar performance de sync ## Solução Checar em quais pontos da sync esta sendo ocupado muito tempo e memória. Notou-se que o método `getOldEntites` da classe `AbstractUpdate` leva em média **10 segundos** (em cada 500 registros), para ser executado na segunda sincronização. ### AbstractList Fazendo uso do método `getEagers` da classe `AbstractList`, este método tem o objetivo de percorrer **recursivamente** todas as entidades relacionadas com a entidade pai e ir montando uma árvore de relacionamentos ex: (OneToMany, ManyToOne, ManyToMany ...). Para melhorar o tempo foi incluido em `getEagers` um argumento que disponibiliza uma saída a **recursividade**, além do atributo `relationNameToIgnore` que já disponibilizaria uma saída. ```javascript= getEagers( model, item, shouldConvert, removeSyncFields = false, relationNameToIgnore = null, skipEager = false, ) { if (skipEager) { return this.getFormattedEagerResult({ model, item, shouldConvert, removeSyncFields, }); } ... } ``` O método `getFormattedEagerResult` retorna a entidade formatada para uso da sync em download ou upload de dados. ```javascript= getFormattedEagerResult({ model, item, shouldConvert, removeSyncFields }) { if (shouldConvert) { const entityDAO = model.entityDAO; item = new entityDAO().convertRelationalToModel(item); } if (removeSyncFields) { delete item.insertInServer; delete item.updateInServer; delete item.transmitted; } return item; } ``` Incluir o atributo `ignoreEager` em relacionamenots em que não há necessidade de retornar toda a árvore de relacionamentos para a entidade pai. ```javascript= // Entidade Customer.js static get relationMappings() { const fields = this.fields; return { professional: { type: SynchronizableModel.ManyToOne, relationClass: User, relationKey: fields.professionalId, ignoreEager: true, }, } ``` Ao passar o parametro ele só ira retornar os dados do profissional sem pegar todos os relacionamentos de User. ### SyncQuery Alteração da classe `SyncQuery` em que nem todas as entidades precisam de uma promise para converter os dados para model ```javascript= if (syncModel?.isPromise) { [...mobileModelItems] = await Promise.all(items.map(syncModel.convertToMobileModel)); } else { mobileModelItems = items.map(syncModel.convertToMobileModel); } ``` O get `isPromisse` pode ser inserido nas classes sync da entidade. ```javascript= // /calendar/calendar/logic/sync/CalendarSync.js static get isPromise() { return true; } ``` ### GDAMetadataDAO Foi notado também que a classe `GDAMetadataDAO` tomava certo tempo pois fazia o uso do `batchDelete` que usa **recursividade**. Este estava executando o método `batchDelete` para items inexistentes no banco do mobile, tomando **tempo desnecessário**. Média de **5 segundos** para itens que nem existem. Solução para este caso foi refatorar a classe e incluir verificações para executar o batchDelete apenas para itens existentes ```javascript= async deleteGdaItems(gdaItems) { if (Array.isArray(gdaItems) && gdaItems.length > 0) { // separe rows by entity const rowsByEntity = gdaItems.reduce((obj, item) => { const array = obj[item.objectId] || []; obj[item.objectId] = [...array, item.rowId]; return obj; }, {}); // get entities as array const entities = Object.keys(rowsByEntity); const mapper = DaoMapper.map; for (const entity of entities) { const dao = mapper[entity]; if (typeof dao?.batchDelete === 'function' && dao?.entityName != null) { await this.batchDeleteEntityItems({ entity, dao, rowsByEntity }); } else { logger.warn(`Unmapped entity id (${entity?.toString()}) check DaoMapper`); } } } return Promise.resolve(); } async batchDeleteEntityItems({ entity, dao, rowsByEntity }) { const ids = await this.listEntityIds(dao.entityName, rowsByEntity[entity]); if (ids.length > 0) { return dao.batchDelete(ids, true, false); } return Promise.resolve(); } async listEntityIds(model, ids) { const keyValue = 'id'; const query = this.knex .select(keyValue) .from(model) .whereIn(keyValue, ids); return query.then((results) => results.map((item) => item[keyValue])); } ``` ### Proposta resolve o problema? Comparando com uma base pequena de leads (1000 registros) em uma sync que alteramos as políticas de GDA. Segue o resultado dos ajustes: #### Apple IOS ![](https://i.imgur.com/1PHzocU.png) #### Android (Emulador) ![](https://i.imgur.com/9xr34IR.png) ### POC MR para a poc https://gitlab.wssim.com.br/cloud/mjolnir/-/merge_requests/3590 ## Solução alteranativa Neste caso utilizariamos um delete na entidade a ser syncada que faria o delete em cascata de todas as entidades relacionadas a ela, mas para isto todas as entidades que fazem relação com a entidade pai teriam que ser alteradas incluindo `ON DELETE CASCADE`. ```mysql CONSTRAINT fk_chave FOREIGN KEY (chave_estrangeira_id) REFERENCES Customer(id) ON DELETE CASCADE ``` Exemplo https://dbfiddle.uk/?rdbms=sqlite_3.27&fiddle=8dd4e17026db2df8908003f6db9ee737 Após exclusão em cascata, a sync faria uma insersão limpa nos dados que estiverem vindo. https://www.techonthenet.com/sqlite/foreign_keys/foreign_delete.php