---
# System prepended metadata

title: Melhorar performance de sync

---

# 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