Add some performance improvements (#7481)

* Add some performance improvements

* fix tests
This commit is contained in:
Daniel Espino García 2023-08-07 09:43:00 +02:00 committed by GitHub
parent 8bc1ec2dff
commit ff601982b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 82 additions and 9 deletions

View file

@ -20,11 +20,14 @@ export async function setCurrentUserStatus(serverUrl: string, status: string) {
throw new Error(`No current user for ${serverUrl}`);
}
user.prepareStatus(status);
await operator.batchRecords([user], 'setCurrentUserStatusOffline');
if (user.status !== status) {
user.prepareStatus(status);
await operator.batchRecords([user], 'setCurrentUserStatus');
}
return null;
} catch (error) {
logError('Failed setCurrentUserStatusOffline', error);
logError('Failed setCurrentUserStatus', error);
return {error};
}
}

View file

@ -378,7 +378,10 @@ export async function fetchPosts(serverUrl: string, channelId: string, page = 0,
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'fetchPosts');
if (models.length) {
await operator.batchRecords(models, 'fetchPosts');
}
}
return result;
} catch (error) {

View file

@ -344,12 +344,19 @@ export async function fetchStatusByIds(serverUrl: string, userIds: string[], fet
return result;
}, {});
const usersToBatch = [];
for (const user of users) {
const status = userStatuses[user.id];
user.prepareStatus(status?.status || General.OFFLINE);
const receivedStatus = userStatuses[user.id];
const statusToSet = receivedStatus?.status || General.OFFLINE;
if (statusToSet !== user.status) {
user.prepareStatus(statusToSet);
usersToBatch.push(user);
}
}
await operator.batchRecords(users, 'fetchStatusByIds');
if (usersToBatch.length) {
await operator.batchRecords(usersToBatch, 'fetchStatusByIds');
}
}
return {statuses};

View file

@ -45,7 +45,7 @@ export default class BaseDataOperator {
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.buildKeyRecordBy
* @returns {Promise<{ProcessRecordResults}>}
*/
processRecords = async <T extends Model>({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults<T>> => {
processRecords = async <T extends Model>({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName, shouldUpdate}: ProcessRecordsArgs): Promise<ProcessRecordResults<T>> => {
const getRecords = async (rawValues: RawValue[]) => {
// We will query a table where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
const columnValues: string[] = getRangeOfValues({fieldName, raws: rawValues});
@ -92,6 +92,10 @@ export default class BaseDataOperator {
// We found a record in the database that matches this element; hence, we'll proceed for an UPDATE operation
if (existingRecord) {
if (shouldUpdate && !shouldUpdate(existingRecord, newElement)) {
continue;
}
// Some raw value has an update_at field. We'll proceed to update only if the update_at value is different from the record's value in database
const updateRecords = getValidRecordsForUpdate({
tableName,
@ -205,7 +209,7 @@ export default class BaseDataOperator {
* @param {string} handleRecordsArgs.tableName
* @returns {Promise<Model[]>}
*/
async handleRecords<T extends Model>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true}: HandleRecordsArgs<T>, description: string): Promise<T[]> {
async handleRecords<T extends Model>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true, shouldUpdate}: HandleRecordsArgs<T>, description: string): Promise<T[]> {
if (!createOrUpdateRawValues.length) {
logWarning(
`An empty "rawValues" array has been passed to the handleRecords method for tableName ${tableName}`,
@ -219,6 +223,7 @@ export default class BaseDataOperator {
tableName,
buildKeyRecordBy,
fieldName,
shouldUpdate,
});
let models: T[] = [];

View file

@ -0,0 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type FileModel from '@typings/database/models/servers/file';
export const shouldUpdateFileRecord = (e: FileModel, n: FileInfo): boolean => {
return Boolean(
(n.post_id !== e.postId) ||
(n.name !== e.name) ||
(n.extension !== e.extension) ||
(n.size !== e.size) ||
((n.mime_type || '') !== e.mimeType) ||
(n.width && n.width !== e.width) ||
(n.height && n.height !== e.height) ||
(n.mini_preview && n.mini_preview !== e.imageThumbnail) ||
(n.localPath && n.localPath !== e.localPath),
);
};

View file

@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type ThreadModel from '@typings/database/models/servers/thread';
export const shouldUpdateThreadRecord = (e: ThreadModel, n: ThreadWithLastFetchedAt): boolean => {
return (
((n.last_reply_at != null) && n.last_reply_at !== e.lastReplyAt) ||
((n.lastFetchedAt || 0) > e.lastFetchedAt) ||
((n.last_viewed_at != null) && e.lastViewedAt !== n.last_viewed_at) ||
(e.replyCount !== n.reply_count) ||
((n.is_following != null) && e.isFollowing !== n.is_following) ||
((n.unread_replies != null) && e.unreadReplies !== n.unread_replies) ||
((n.unread_mentions != null) && e.unreadMentions !== n.unread_mentions)
);
};

View file

@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type UserModel from '@typings/database/models/servers/user';
export function shouldUpdateUserRecord(e: UserModel, n: UserProfile) {
return Boolean(n.update_at > e.updateAt || (n.status && n.status !== e.status));
}

View file

@ -6,6 +6,7 @@ import {Q} from '@nozbe/watermelondb';
import {ActionType} from '@constants';
import {MM_TABLES} from '@constants/database';
import {buildDraftKey} from '@database/operator/server_data_operator/comparators';
import {shouldUpdateFileRecord} from '@database/operator/server_data_operator/comparators/files';
import {
transformDraftRecord,
transformFileRecord,
@ -234,6 +235,7 @@ const PostHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercla
deleteRawValues: pendingPostsToDelete,
tableName,
fieldName: 'id',
shouldUpdate: (e: PostModel, n: Post) => n.update_at > e.updateAt,
}));
const preparedPosts = (await this.prepareRecords({
@ -309,6 +311,7 @@ const PostHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercla
tableName: FILE,
fieldName: 'id',
deleteRawValues: [],
shouldUpdate: shouldUpdateFileRecord,
}));
const postFiles = await this.prepareRecords({

View file

@ -2,6 +2,7 @@
// See LICENSE.txt for license information.
import DatabaseManager from '@database/manager';
import {shouldUpdateThreadRecord} from '@database/operator/server_data_operator/comparators/thread';
import {transformThreadRecord, transformThreadParticipantRecord, transformThreadInTeamRecord, transformTeamThreadsSyncRecord} from '@database/operator/server_data_operator/transformers/thread';
import type ServerDataOperator from '..';
@ -59,6 +60,7 @@ describe('*** Operator: Thread Handlers tests ***', () => {
createOrUpdateRawValues: threads,
tableName: 'Thread',
prepareRecordsOnly: true,
shouldUpdate: shouldUpdateThreadRecord,
}, 'handleThreads(NEVER)');
// Should handle participants

View file

@ -4,6 +4,7 @@
import {Q} from '@nozbe/watermelondb';
import {MM_TABLES} from '@constants/database';
import {shouldUpdateThreadRecord} from '@database/operator/server_data_operator/comparators/thread';
import {
transformThreadRecord,
transformThreadParticipantRecord,
@ -110,6 +111,7 @@ const ThreadHandler = <TBase extends Constructor<ServerDataOperatorBase>>(superc
prepareRecordsOnly: true,
createOrUpdateRawValues: createOrUpdateThreads,
tableName: THREAD,
shouldUpdate: shouldUpdateThreadRecord,
}, 'handleThreads(NEVER)');
// Add the models to be batched here

View file

@ -3,6 +3,7 @@
import DatabaseManager from '@database/manager';
import {buildPreferenceKey} from '@database/operator/server_data_operator/comparators';
import {shouldUpdateUserRecord} from '@database/operator/server_data_operator/comparators/user';
import {
transformPreferenceRecord,
transformUserRecord,
@ -102,6 +103,7 @@ describe('*** Operator: User Handlers tests ***', () => {
tableName: 'User',
prepareRecordsOnly: false,
transformer: transformUserRecord,
shouldUpdate: shouldUpdateUserRecord,
}, 'handleUsers');
});

View file

@ -3,6 +3,7 @@
import {MM_TABLES} from '@constants/database';
import {buildPreferenceKey} from '@database/operator/server_data_operator/comparators';
import {shouldUpdateUserRecord} from '@database/operator/server_data_operator/comparators/user';
import {
transformPreferenceRecord,
transformUserRecord,
@ -126,6 +127,7 @@ const UserHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercla
createOrUpdateRawValues,
tableName: USER,
prepareRecordsOnly,
shouldUpdate: shouldUpdateUserRecord,
}, 'handleUsers');
};
};

View file

@ -155,6 +155,7 @@ export type ProcessRecordsArgs = {
tableName: string;
fieldName: string;
buildKeyRecordBy?: (obj: Record<string, any>) => string;
shouldUpdate?: (existing: Record<string, any>, newRaw: Record<string, any>) => boolean;
};
export type HandleRecordsArgs<T extends Model> = {
@ -165,6 +166,7 @@ export type HandleRecordsArgs<T extends Model> = {
deleteRawValues?: RawValue[];
tableName: string;
prepareRecordsOnly: boolean;
shouldUpdate?: (existingRecord: T, newRaw: RawValue) => boolean;
};
export type RangeOfValueArgs = {