Reduce number of SQL queries during conversation update

This commit is contained in:
Fedor Indutny 2021-08-16 09:56:27 -07:00 committed by GitHub
parent 765b3eddc4
commit 962515031d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 91 deletions

View file

@ -713,9 +713,7 @@ export async function startApp(): Promise<void> {
const conversation = window.ConversationController.get(selectedId); const conversation = window.ConversationController.get(selectedId);
assert(conversation, "Conversation wasn't found"); assert(conversation, "Conversation wasn't found");
conversation.queueJob('maybeSetPendingUniversalTimer', () => await conversation.updateLastMessage();
conversation.maybeSetPendingUniversalTimer()
);
} }
}, },

View file

@ -1061,11 +1061,6 @@ export class ConversationModel extends window.Backbone
// On successful fetch - mark contact as registered. // On successful fetch - mark contact as registered.
this.setRegistered(); this.setRegistered();
// If we couldn't apply universal timer before - try it again.
this.queueJob('maybeSetPendingUniversalTimer', async () =>
this.maybeSetPendingUniversalTimer()
);
} }
isValid(): boolean { isValid(): boolean {
@ -2759,7 +2754,9 @@ export class ConversationModel extends window.Backbone
return id; return id;
} }
async maybeSetPendingUniversalTimer(): Promise<void> { async maybeSetPendingUniversalTimer(
hasUserInitiatedMessages: boolean
): Promise<void> {
if (!isDirectConversation(this.attributes)) { if (!isDirectConversation(this.attributes)) {
return; return;
} }
@ -2768,7 +2765,8 @@ export class ConversationModel extends window.Backbone
return; return;
} }
if (await window.Signal.Data.hasUserInitiatedMessages(this.get('id'))) { if (hasUserInitiatedMessages) {
await this.maybeApplyUniversalTimer(true);
return; return;
} }
@ -2787,7 +2785,7 @@ export class ConversationModel extends window.Backbone
this.set('pendingUniversalTimer', notificationId); this.set('pendingUniversalTimer', notificationId);
} }
async maybeApplyUniversalTimer(): Promise<void> { async maybeApplyUniversalTimer(forceRemove: boolean): Promise<void> {
const notificationId = this.get('pendingUniversalTimer'); const notificationId = this.get('pendingUniversalTimer');
if (!notificationId) { if (!notificationId) {
return; return;
@ -2801,7 +2799,7 @@ export class ConversationModel extends window.Backbone
}); });
} }
if (this.get('expireTimer')) { if (this.get('expireTimer') || forceRemove) {
this.set('pendingUniversalTimer', undefined); this.set('pendingUniversalTimer', undefined);
return; return;
} }
@ -3405,7 +3403,7 @@ export class ConversationModel extends window.Backbone
timestamp timestamp
); );
await this.maybeApplyUniversalTimer(); await this.maybeApplyUniversalTimer(false);
const expireTimer = this.get('expireTimer'); const expireTimer = this.get('expireTimer');
@ -3604,7 +3602,7 @@ export class ConversationModel extends window.Backbone
this.queueJob('sendMessage', async () => { this.queueJob('sendMessage', async () => {
const now = timestamp || Date.now(); const now = timestamp || Date.now();
await this.maybeApplyUniversalTimer(); await this.maybeApplyUniversalTimer(false);
const expireTimer = this.get('expireTimer'); const expireTimer = this.get('expireTimer');
@ -3822,28 +3820,25 @@ export class ConversationModel extends window.Backbone
return; return;
} }
this.queueJob('maybeSetPendingUniversalTimer', async () =>
this.maybeSetPendingUniversalTimer()
);
const ourConversationId = window.ConversationController.getOurConversationId(); const ourConversationId = window.ConversationController.getOurConversationId();
if (!ourConversationId) { if (!ourConversationId) {
throw new Error('updateLastMessage: Failed to fetch ourConversationId'); throw new Error('updateLastMessage: Failed to fetch ourConversationId');
} }
const conversationId = this.id; const conversationId = this.id;
let [previewMessage, activityMessage] = await Promise.all([
window.Signal.Data.getLastConversationPreview({ const lastMessages = await window.Signal.Data.getLastConversationMessages({
conversationId, conversationId,
ourConversationId, ourConversationId,
Message: window.Whisper.Message, Message: window.Whisper.Message,
}), });
window.Signal.Data.getLastConversationActivity({
conversationId, // This runs as a job to avoid race conditions
ourConversationId, this.queueJob('maybeSetPendingUniversalTimer', async () =>
Message: window.Whisper.Message, this.maybeSetPendingUniversalTimer(lastMessages.hasUserInitiatedMessages)
}), );
]);
let { preview: previewMessage, activity: activityMessage } = lastMessages;
// Register the message with MessageController so that if it already exists // Register the message with MessageController so that if it already exists
// in memory we use that data instead of the data from the db which may // in memory we use that data instead of the data from the db which may
@ -4157,7 +4152,7 @@ export class ConversationModel extends window.Backbone
// This call actually removes universal timer notification and clears // This call actually removes universal timer notification and clears
// the pending flags. // the pending flags.
await this.maybeApplyUniversalTimer(); await this.maybeApplyUniversalTimer(true);
window.Signal.Data.updateConversation(this.attributes); window.Signal.Data.updateConversation(this.attributes);

View file

@ -48,6 +48,7 @@ import {
IdentityKeyType, IdentityKeyType,
ItemKeyType, ItemKeyType,
ItemType, ItemType,
LastConversationMessagesType,
MessageType, MessageType,
MessageTypeUnhydrated, MessageTypeUnhydrated,
PreKeyType, PreKeyType,
@ -190,7 +191,6 @@ const dataInterface: ClientInterface = {
searchMessagesInConversation, searchMessagesInConversation,
getMessageCount, getMessageCount,
hasUserInitiatedMessages,
saveMessage, saveMessage,
saveMessages, saveMessages,
removeMessage, removeMessage,
@ -213,8 +213,7 @@ const dataInterface: ClientInterface = {
getTapToViewMessagesNeedingErase, getTapToViewMessagesNeedingErase,
getOlderMessagesByConversation, getOlderMessagesByConversation,
getNewerMessagesByConversation, getNewerMessagesByConversation,
getLastConversationActivity, getLastConversationMessages,
getLastConversationPreview,
getMessageMetricsForConversation, getMessageMetricsForConversation,
hasGroupCallHistoryMessage, hasGroupCallHistoryMessage,
migrateConversationMessages, migrateConversationMessages,
@ -1063,10 +1062,6 @@ async function getMessageCount(conversationId?: string) {
return channels.getMessageCount(conversationId); return channels.getMessageCount(conversationId);
} }
async function hasUserInitiatedMessages(conversationId: string) {
return channels.hasUserInitiatedMessages(conversationId);
}
async function saveMessage( async function saveMessage(
data: MessageType, data: MessageType,
options?: { forceSave?: boolean } options?: { forceSave?: boolean }
@ -1267,7 +1262,7 @@ async function getNewerMessagesByConversation(
return new MessageCollection(handleMessageJSON(messages)); return new MessageCollection(handleMessageJSON(messages));
} }
async function getLastConversationActivity({ async function getLastConversationMessages({
conversationId, conversationId,
ourConversationId, ourConversationId,
Message, Message,
@ -1275,33 +1270,21 @@ async function getLastConversationActivity({
conversationId: string; conversationId: string;
ourConversationId: string; ourConversationId: string;
Message: typeof MessageModel; Message: typeof MessageModel;
}): Promise<MessageModel | undefined> { }): Promise<LastConversationMessagesType> {
const result = await channels.getLastConversationActivity({ const {
preview,
activity,
hasUserInitiatedMessages,
} = await channels.getLastConversationMessages({
conversationId, conversationId,
ourConversationId, ourConversationId,
}); });
if (result) {
return new Message(result); return {
} preview: preview ? new Message(preview) : undefined,
return undefined; activity: activity ? new Message(activity) : undefined,
} hasUserInitiatedMessages,
async function getLastConversationPreview({ };
conversationId,
ourConversationId,
Message,
}: {
conversationId: string;
ourConversationId: string;
Message: typeof MessageModel;
}): Promise<MessageModel | undefined> {
const result = await channels.getLastConversationPreview({
conversationId,
ourConversationId,
});
if (result) {
return new Message(result);
}
return undefined;
} }
async function getMessageMetricsForConversation(conversationId: string) { async function getMessageMetricsForConversation(conversationId: string) {
const result = await channels.getMessageMetricsForConversation( const result = await channels.getMessageMetricsForConversation(

View file

@ -201,6 +201,18 @@ export type UnprocessedUpdateType = {
decrypted?: string; decrypted?: string;
}; };
export type LastConversationMessagesServerType = {
activity?: MessageType;
preview?: MessageType;
hasUserInitiatedMessages: boolean;
};
export type LastConversationMessagesType = {
activity?: MessageModel;
preview?: MessageModel;
hasUserInitiatedMessages: boolean;
};
export type DataInterface = { export type DataInterface = {
close: () => Promise<void>; close: () => Promise<void>;
removeDB: () => Promise<void>; removeDB: () => Promise<void>;
@ -302,7 +314,6 @@ export type DataInterface = {
options?: { forceSave?: boolean } options?: { forceSave?: boolean }
) => Promise<void>; ) => Promise<void>;
getMessageCount: (conversationId?: string) => Promise<number>; getMessageCount: (conversationId?: string) => Promise<number>;
hasUserInitiatedMessages: (conversationId: string) => Promise<boolean>;
getAllMessageIds: () => Promise<Array<string>>; getAllMessageIds: () => Promise<Array<string>>;
getMessageMetricsForConversation: ( getMessageMetricsForConversation: (
conversationId: string conversationId: string
@ -476,14 +487,10 @@ export type ServerInterface = DataInterface & {
conversationId: string, conversationId: string,
options?: { limit?: number; receivedAt?: number; sentAt?: number } options?: { limit?: number; receivedAt?: number; sentAt?: number }
) => Promise<Array<MessageTypeUnhydrated>>; ) => Promise<Array<MessageTypeUnhydrated>>;
getLastConversationActivity: (options: { getLastConversationMessages: (options: {
conversationId: string; conversationId: string;
ourConversationId: string; ourConversationId: string;
}) => Promise<MessageType | undefined>; }) => Promise<LastConversationMessagesServerType>;
getLastConversationPreview: (options: {
conversationId: string;
ourConversationId: string;
}) => Promise<MessageType | undefined>;
getTapToViewMessagesNeedingErase: () => Promise<Array<MessageType>>; getTapToViewMessagesNeedingErase: () => Promise<Array<MessageType>>;
removeConversation: (id: Array<string> | string) => Promise<void>; removeConversation: (id: Array<string> | string) => Promise<void>;
removeMessage: (id: string) => Promise<void>; removeMessage: (id: string) => Promise<void>;
@ -576,16 +583,11 @@ export type ClientInterface = DataInterface & {
MessageCollection: typeof MessageModelCollectionType; MessageCollection: typeof MessageModelCollectionType;
} }
) => Promise<MessageModelCollectionType>; ) => Promise<MessageModelCollectionType>;
getLastConversationActivity: (options: { getLastConversationMessages: (options: {
conversationId: string; conversationId: string;
ourConversationId: string; ourConversationId: string;
Message: typeof MessageModel; Message: typeof MessageModel;
}) => Promise<MessageModel | undefined>; }) => Promise<LastConversationMessagesType>;
getLastConversationPreview: (options: {
conversationId: string;
ourConversationId: string;
Message: typeof MessageModel;
}) => Promise<MessageModel | undefined>;
getTapToViewMessagesNeedingErase: (options: { getTapToViewMessagesNeedingErase: (options: {
MessageCollection: typeof MessageModelCollectionType; MessageCollection: typeof MessageModelCollectionType;
}) => Promise<MessageModelCollectionType>; }) => Promise<MessageModelCollectionType>;

View file

@ -52,6 +52,7 @@ import {
IdentityKeyType, IdentityKeyType,
ItemKeyType, ItemKeyType,
ItemType, ItemType,
LastConversationMessagesServerType,
MessageMetricsType, MessageMetricsType,
MessageType, MessageType,
MessageTypeUnhydrated, MessageTypeUnhydrated,
@ -179,7 +180,6 @@ const dataInterface: ServerInterface = {
searchMessagesInConversation, searchMessagesInConversation,
getMessageCount, getMessageCount,
hasUserInitiatedMessages,
saveMessage, saveMessage,
saveMessages, saveMessages,
removeMessage, removeMessage,
@ -203,8 +203,7 @@ const dataInterface: ServerInterface = {
getOlderMessagesByConversation, getOlderMessagesByConversation,
getNewerMessagesByConversation, getNewerMessagesByConversation,
getMessageMetricsForConversation, getMessageMetricsForConversation,
getLastConversationActivity, getLastConversationMessages,
getLastConversationPreview,
hasGroupCallHistoryMessage, hasGroupCallHistoryMessage,
migrateConversationMessages, migrateConversationMessages,
@ -3513,10 +3512,7 @@ async function getMessageCount(conversationId?: string): Promise<number> {
return row['count(*)']; return row['count(*)'];
} }
// Called only for private conversations function hasUserInitiatedMessages(conversationId: string): boolean {
async function hasUserInitiatedMessages(
conversationId: string
): Promise<boolean> {
const db = getInstance(); const db = getInstance();
// We apply the limit in the sub-query so that `json_extract` wouldn't run // We apply the limit in the sub-query so that `json_extract` wouldn't run
@ -3538,10 +3534,10 @@ async function hasUserInitiatedMessages(
'keychange', 'keychange',
'group-v1-migration', 'group-v1-migration',
'universal-timer-notification', 'universal-timer-notification',
'change-number-notification' 'change-number-notification',
'group-v2-change'
) )
) AND )
json_extract(json, '$.expirationTimerUpdate') IS NULL
LIMIT 1 LIMIT 1
); );
` `
@ -4218,13 +4214,13 @@ function getNewestMessageForConversation(
return row; return row;
} }
async function getLastConversationActivity({ function getLastConversationActivity({
conversationId, conversationId,
ourConversationId, ourConversationId,
}: { }: {
conversationId: string; conversationId: string;
ourConversationId: string; ourConversationId: string;
}): Promise<MessageType | undefined> { }): MessageType | undefined {
const db = getInstance(); const db = getInstance();
const row = prepare( const row = prepare(
db, db,
@ -4270,13 +4266,13 @@ async function getLastConversationActivity({
return jsonToObject(row.json); return jsonToObject(row.json);
} }
async function getLastConversationPreview({ function getLastConversationPreview({
conversationId, conversationId,
ourConversationId, ourConversationId,
}: { }: {
conversationId: string; conversationId: string;
ourConversationId: string; ourConversationId: string;
}): Promise<MessageType | undefined> { }): MessageType | undefined {
const db = getInstance(); const db = getInstance();
const row = prepare( const row = prepare(
db, db,
@ -4317,6 +4313,31 @@ async function getLastConversationPreview({
return jsonToObject(row.json); return jsonToObject(row.json);
} }
async function getLastConversationMessages({
conversationId,
ourConversationId,
}: {
conversationId: string;
ourConversationId: string;
}): Promise<LastConversationMessagesServerType> {
const db = getInstance();
return db.transaction(() => {
return {
activity: getLastConversationActivity({
conversationId,
ourConversationId,
}),
preview: getLastConversationPreview({
conversationId,
ourConversationId,
}),
hasUserInitiatedMessages: hasUserInitiatedMessages(conversationId),
};
})();
}
function getOldestUnreadMessageForConversation( function getOldestUnreadMessageForConversation(
conversationId: string conversationId: string
): MessageMetricsType | undefined { ): MessageMetricsType | undefined {