Batch deleteSentProtoRecipient queries
This commit is contained in:
parent
b71e4875e6
commit
6f3191117f
5 changed files with 151 additions and 81 deletions
|
@ -13,11 +13,13 @@ import { isOutgoing } from '../state/selectors/message';
|
||||||
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
||||||
import { getOwn } from '../util/getOwn';
|
import { getOwn } from '../util/getOwn';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import { createWaitBatcher } from '../util/waitBatcher';
|
||||||
import {
|
import {
|
||||||
SendActionType,
|
SendActionType,
|
||||||
SendStatus,
|
SendStatus,
|
||||||
sendStateReducer,
|
sendStateReducer,
|
||||||
} from '../messages/MessageSendState';
|
} from '../messages/MessageSendState';
|
||||||
|
import type { DeleteSentProtoRecipientOptionsType } from '../sql/Interface';
|
||||||
import dataInterface from '../sql/Client';
|
import dataInterface from '../sql/Client';
|
||||||
|
|
||||||
const { deleteSentProtoRecipient } = dataInterface;
|
const { deleteSentProtoRecipient } = dataInterface;
|
||||||
|
@ -40,6 +42,18 @@ class MessageReceiptModel extends Model<MessageReceiptAttributesType> {}
|
||||||
|
|
||||||
let singleton: MessageReceipts | undefined;
|
let singleton: MessageReceipts | undefined;
|
||||||
|
|
||||||
|
const deleteSentProtoBatcher = createWaitBatcher({
|
||||||
|
name: 'deleteSentProtoBatcher',
|
||||||
|
wait: 250,
|
||||||
|
maxSize: 30,
|
||||||
|
async processBatch(items: Array<DeleteSentProtoRecipientOptionsType>) {
|
||||||
|
window.log.info(
|
||||||
|
`MessageReceipts: Batching ${items.length} sent proto recipients deletes`
|
||||||
|
);
|
||||||
|
await deleteSentProtoRecipient(items);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async function getTargetMessage(
|
async function getTargetMessage(
|
||||||
sourceId: string,
|
sourceId: string,
|
||||||
messages: MessageModelCollectionType
|
messages: MessageModelCollectionType
|
||||||
|
@ -202,7 +216,7 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
|
||||||
const deviceId = receipt.get('sourceDevice');
|
const deviceId = receipt.get('sourceDevice');
|
||||||
|
|
||||||
if (recipientUuid && deviceId) {
|
if (recipientUuid && deviceId) {
|
||||||
await deleteSentProtoRecipient({
|
await deleteSentProtoBatcher.add({
|
||||||
timestamp: messageSentAt,
|
timestamp: messageSentAt,
|
||||||
recipientUuid,
|
recipientUuid,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
|
|
@ -48,6 +48,7 @@ import {
|
||||||
ClientJobType,
|
ClientJobType,
|
||||||
ClientSearchResultMessageType,
|
ClientSearchResultMessageType,
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
DeleteSentProtoRecipientOptionsType,
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
ItemKeyType,
|
ItemKeyType,
|
||||||
ItemType,
|
ItemType,
|
||||||
|
@ -825,11 +826,11 @@ async function insertProtoRecipients(options: {
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await channels.insertProtoRecipients(options);
|
await channels.insertProtoRecipients(options);
|
||||||
}
|
}
|
||||||
async function deleteSentProtoRecipient(options: {
|
async function deleteSentProtoRecipient(
|
||||||
timestamp: number;
|
options:
|
||||||
recipientUuid: string;
|
| DeleteSentProtoRecipientOptionsType
|
||||||
deviceId: number;
|
| ReadonlyArray<DeleteSentProtoRecipientOptionsType>
|
||||||
}): Promise<void> {
|
): Promise<void> {
|
||||||
await channels.deleteSentProtoRecipient(options);
|
await channels.deleteSentProtoRecipient(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -215,6 +215,12 @@ export type LastConversationMessagesType = {
|
||||||
hasUserInitiatedMessages: boolean;
|
hasUserInitiatedMessages: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DeleteSentProtoRecipientOptionsType = Readonly<{
|
||||||
|
timestamp: number;
|
||||||
|
recipientUuid: string;
|
||||||
|
deviceId: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type DataInterface = {
|
export type DataInterface = {
|
||||||
close: () => Promise<void>;
|
close: () => Promise<void>;
|
||||||
removeDB: () => Promise<void>;
|
removeDB: () => Promise<void>;
|
||||||
|
@ -267,11 +273,11 @@ export type DataInterface = {
|
||||||
recipientUuid: string;
|
recipientUuid: string;
|
||||||
deviceIds: Array<number>;
|
deviceIds: Array<number>;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
deleteSentProtoRecipient: (options: {
|
deleteSentProtoRecipient: (
|
||||||
timestamp: number;
|
options:
|
||||||
recipientUuid: string;
|
| DeleteSentProtoRecipientOptionsType
|
||||||
deviceId: number;
|
| ReadonlyArray<DeleteSentProtoRecipientOptionsType>
|
||||||
}) => Promise<void>;
|
) => Promise<void>;
|
||||||
getSentProtoByRecipient: (options: {
|
getSentProtoByRecipient: (options: {
|
||||||
now: number;
|
now: number;
|
||||||
recipientUuid: string;
|
recipientUuid: string;
|
||||||
|
|
146
ts/sql/Server.ts
146
ts/sql/Server.ts
|
@ -52,6 +52,7 @@ import {
|
||||||
AttachmentDownloadJobType,
|
AttachmentDownloadJobType,
|
||||||
ConversationMetricsType,
|
ConversationMetricsType,
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
DeleteSentProtoRecipientOptionsType,
|
||||||
EmojiType,
|
EmojiType,
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
ItemKeyType,
|
ItemKeyType,
|
||||||
|
@ -2708,82 +2709,87 @@ async function insertProtoRecipients({
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSentProtoRecipient({
|
async function deleteSentProtoRecipient(
|
||||||
timestamp,
|
options:
|
||||||
recipientUuid,
|
| DeleteSentProtoRecipientOptionsType
|
||||||
deviceId,
|
| ReadonlyArray<DeleteSentProtoRecipientOptionsType>
|
||||||
}: {
|
): Promise<void> {
|
||||||
timestamp: number;
|
|
||||||
recipientUuid: string;
|
|
||||||
deviceId: number;
|
|
||||||
}): Promise<void> {
|
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
|
|
||||||
// Note: we use `pluck` in this function to fetch only the first column of returned row.
|
const items = Array.isArray(options) ? options : [options];
|
||||||
|
|
||||||
|
// Note: we use `pluck` in this function to fetch only the first column of
|
||||||
|
// returned row.
|
||||||
|
|
||||||
db.transaction(() => {
|
db.transaction(() => {
|
||||||
// 1. Figure out what payload we're talking about.
|
for (const item of items) {
|
||||||
const rows = prepare(
|
const { timestamp, recipientUuid, deviceId } = item;
|
||||||
db,
|
|
||||||
`
|
// 1. Figure out what payload we're talking about.
|
||||||
SELECT sendLogPayloads.id FROM sendLogPayloads
|
const rows = prepare(
|
||||||
INNER JOIN sendLogRecipients
|
db,
|
||||||
ON sendLogRecipients.payloadId = sendLogPayloads.id
|
`
|
||||||
WHERE
|
SELECT sendLogPayloads.id FROM sendLogPayloads
|
||||||
sendLogPayloads.timestamp = $timestamp AND
|
INNER JOIN sendLogRecipients
|
||||||
sendLogRecipients.recipientUuid = $recipientUuid AND
|
ON sendLogRecipients.payloadId = sendLogPayloads.id
|
||||||
sendLogRecipients.deviceId = $deviceId;
|
WHERE
|
||||||
`
|
sendLogPayloads.timestamp = $timestamp AND
|
||||||
).all({ timestamp, recipientUuid, deviceId });
|
sendLogRecipients.recipientUuid = $recipientUuid AND
|
||||||
if (!rows.length) {
|
sendLogRecipients.deviceId = $deviceId;
|
||||||
return;
|
`
|
||||||
}
|
).all({ timestamp, recipientUuid, deviceId });
|
||||||
if (rows.length > 1) {
|
if (!rows.length) {
|
||||||
console.warn(
|
continue;
|
||||||
`deleteSentProtoRecipient: More than one payload matches recipient and timestamp ${timestamp}. Using the first.`
|
}
|
||||||
|
if (rows.length > 1) {
|
||||||
|
console.warn(
|
||||||
|
'deleteSentProtoRecipient: More than one payload matches ' +
|
||||||
|
`recipient and timestamp ${timestamp}. Using the first.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = rows[0];
|
||||||
|
|
||||||
|
// 2. Delete the recipient/device combination in question.
|
||||||
|
prepare(
|
||||||
|
db,
|
||||||
|
`
|
||||||
|
DELETE FROM sendLogRecipients
|
||||||
|
WHERE
|
||||||
|
payloadId = $id AND
|
||||||
|
recipientUuid = $recipientUuid AND
|
||||||
|
deviceId = $deviceId;
|
||||||
|
`
|
||||||
|
).run({ id, recipientUuid, deviceId });
|
||||||
|
|
||||||
|
// 3. See how many more recipient devices there were for this payload.
|
||||||
|
const remaining = prepare(
|
||||||
|
db,
|
||||||
|
'SELECT count(*) FROM sendLogRecipients WHERE payloadId = $id;'
|
||||||
|
)
|
||||||
|
.pluck(true)
|
||||||
|
.get({ id });
|
||||||
|
|
||||||
|
if (!isNumber(remaining)) {
|
||||||
|
throw new Error(
|
||||||
|
'deleteSentProtoRecipient: select count() returned non-number!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining > 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Delete the entire payload if there are no more recipients left.
|
||||||
|
console.info(
|
||||||
|
'deleteSentProtoRecipient: ' +
|
||||||
|
`Deleting proto payload for timestamp ${timestamp}`
|
||||||
);
|
);
|
||||||
return;
|
prepare(db, 'DELETE FROM sendLogPayloads WHERE id = $id;').run({
|
||||||
|
id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = rows[0];
|
|
||||||
|
|
||||||
// 2. Delete the recipient/device combination in question.
|
|
||||||
prepare(
|
|
||||||
db,
|
|
||||||
`
|
|
||||||
DELETE FROM sendLogRecipients
|
|
||||||
WHERE
|
|
||||||
payloadId = $id AND
|
|
||||||
recipientUuid = $recipientUuid AND
|
|
||||||
deviceId = $deviceId;
|
|
||||||
`
|
|
||||||
).run({ id, recipientUuid, deviceId });
|
|
||||||
|
|
||||||
// 3. See how many more recipient devices there were for this payload.
|
|
||||||
const remaining = prepare(
|
|
||||||
db,
|
|
||||||
'SELECT count(*) FROM sendLogRecipients WHERE payloadId = $id;'
|
|
||||||
)
|
|
||||||
.pluck(true)
|
|
||||||
.get({ id });
|
|
||||||
|
|
||||||
if (!isNumber(remaining)) {
|
|
||||||
throw new Error(
|
|
||||||
'deleteSentProtoRecipient: select count() returned non-number!'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remaining > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Delete the entire payload if there are no more recipients left.
|
|
||||||
console.info(
|
|
||||||
`deleteSentProtoRecipient: Deleting proto payload for timestamp ${timestamp}`
|
|
||||||
);
|
|
||||||
prepare(db, 'DELETE FROM sendLogPayloads WHERE id = $id;').run({
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -416,6 +416,49 @@ describe('sendLog', () => {
|
||||||
assert.lengthOf(await getAllSentProtos(), 0);
|
assert.lengthOf(await getAllSentProtos(), 0);
|
||||||
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
|
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('deletes multiple recipients in a single transaction', async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
const recipientUuid1 = getGuid();
|
||||||
|
const recipientUuid2 = getGuid();
|
||||||
|
const proto = {
|
||||||
|
contentHint: 1,
|
||||||
|
proto: Buffer.from(getRandomBytes(128)),
|
||||||
|
timestamp,
|
||||||
|
};
|
||||||
|
await insertSentProto(proto, {
|
||||||
|
messageIds: [getGuid()],
|
||||||
|
recipients: {
|
||||||
|
[recipientUuid1]: [1, 2],
|
||||||
|
[recipientUuid2]: [1],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.lengthOf(await getAllSentProtos(), 1);
|
||||||
|
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
|
||||||
|
|
||||||
|
await deleteSentProtoRecipient([
|
||||||
|
{
|
||||||
|
timestamp,
|
||||||
|
recipientUuid: recipientUuid1,
|
||||||
|
deviceId: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamp,
|
||||||
|
recipientUuid: recipientUuid1,
|
||||||
|
deviceId: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamp,
|
||||||
|
recipientUuid: recipientUuid2,
|
||||||
|
deviceId: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.lengthOf(await getAllSentProtos(), 0);
|
||||||
|
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getSentProtoByRecipient', () => {
|
describe('#getSentProtoByRecipient', () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue