# 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

#### Android (Emulador)

### 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