From 223ea41d62df7454761ede6a8b51f442e4735088 Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Fri, 14 Nov 2025 08:05:36 -0600 Subject: [PATCH] Improve speed of getUnreadByConversationAndMarkRead query Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com> --- ts/sql/Server.node.ts | 1 + .../1530-update-expiring-index.std.ts | 15 ++++++------- .../1540-partial-expiring-index.std.ts | 16 ++++++++++++++ ts/sql/migrations/index.node.ts | 2 ++ ...st.node.ts => migration_1540_test.node.ts} | 21 ++++++++++--------- 5 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 ts/sql/migrations/1540-partial-expiring-index.std.ts rename ts/test-node/sql/{migration_1530_test.node.ts => migration_1540_test.node.ts} (70%) diff --git a/ts/sql/Server.node.ts b/ts/sql/Server.node.ts index 0da8238b62..e182776a64 100644 --- a/ts/sql/Server.node.ts +++ b/ts/sql/Server.node.ts @@ -3351,6 +3351,7 @@ function getUnreadByConversationAndMarkRead( const updateExpirationFragment = sqlFragment` UPDATE messages + INDEXED BY messages_conversationId_expirationStartTimestamp SET expirationStartTimestamp = ${expirationStartTimestamp} WHERE diff --git a/ts/sql/migrations/1530-update-expiring-index.std.ts b/ts/sql/migrations/1530-update-expiring-index.std.ts index e2e7c9aa1e..46553dcfbd 100644 --- a/ts/sql/migrations/1530-update-expiring-index.std.ts +++ b/ts/sql/migrations/1530-update-expiring-index.std.ts @@ -3,18 +3,15 @@ import type { Database } from '@signalapp/sqlcipher'; -export default function updateToSchemaVersion1520(db: Database): void { - db.exec( - 'DROP INDEX IF EXISTS expiring_message_by_conversation_and_received_at;' - ); - +export default function updateToSchemaVersion1530(db: Database): void { db.exec(` ALTER TABLE messages ADD COLUMN hasExpireTimer INTEGER NOT NULL GENERATED ALWAYS AS (COALESCE(expireTimer, 0) > 0) VIRTUAL; `); - db.exec(` - CREATE INDEX messages_conversationId_hasExpireTimer_expirationStartTimestamp - ON messages (conversationId, hasExpireTimer, expirationStartTimestamp); - `); + // Deprecated by migration 1540 + // db.exec(` + // CREATE INDEX messages_conversationId_hasExpireTimer_expirationStartTimestamp + // ON messages (conversationId, hasExpireTimer, expirationStartTimestamp); + // `); } diff --git a/ts/sql/migrations/1540-partial-expiring-index.std.ts b/ts/sql/migrations/1540-partial-expiring-index.std.ts new file mode 100644 index 0000000000..d3f34bd200 --- /dev/null +++ b/ts/sql/migrations/1540-partial-expiring-index.std.ts @@ -0,0 +1,16 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { Database } from '@signalapp/sqlcipher'; + +export default function updateToSchemaVersion1540(db: Database): void { + db.exec( + 'DROP INDEX IF EXISTS messages_conversationId_hasExpireTimer_expirationStartTimestamp;' + ); + + db.exec(` + CREATE INDEX messages_conversationId_expirationStartTimestamp + ON messages (conversationId, expirationStartTimestamp) + WHERE hasExpireTimer IS 1; + `); +} diff --git a/ts/sql/migrations/index.node.ts b/ts/sql/migrations/index.node.ts index c1c74b3804..b781a3e089 100644 --- a/ts/sql/migrations/index.node.ts +++ b/ts/sql/migrations/index.node.ts @@ -129,6 +129,7 @@ import updateToSchemaVersion1500 from './1500-search-polls.std.js'; import updateToSchemaVersion1510 from './1510-chat-folders-normalize-all-chats.std.js'; import updateToSchemaVersion1520 from './1520-poll-votes-unread.std.js'; import updateToSchemaVersion1530 from './1530-update-expiring-index.std.js'; +import updateToSchemaVersion1540 from './1540-partial-expiring-index.std.js'; import { DataWriter } from '../Server.node.js'; @@ -1616,6 +1617,7 @@ export const SCHEMA_VERSIONS: ReadonlyArray = [ { version: 1510, update: updateToSchemaVersion1510 }, { version: 1520, update: updateToSchemaVersion1520 }, { version: 1530, update: updateToSchemaVersion1530 }, + { version: 1540, update: updateToSchemaVersion1540 }, ]; export class DBVersionFromFutureError extends Error { diff --git a/ts/test-node/sql/migration_1530_test.node.ts b/ts/test-node/sql/migration_1540_test.node.ts similarity index 70% rename from ts/test-node/sql/migration_1530_test.node.ts rename to ts/test-node/sql/migration_1540_test.node.ts index 684d9edb5a..e7067d13a6 100644 --- a/ts/test-node/sql/migration_1530_test.node.ts +++ b/ts/test-node/sql/migration_1540_test.node.ts @@ -6,12 +6,12 @@ import { type WritableDB } from '../../sql/Interface.std.js'; import { sql, sqlFragment } from '../../sql/util.std.js'; import { createDB, explain, updateToVersion } from './helpers.node.js'; -describe('SQL/updateToSchemaVersion1530', () => { +describe('SQL/updateToSchemaVersion1540', () => { let db: WritableDB; beforeEach(() => { db = createDB(); - updateToVersion(db, 1530); + updateToVersion(db, 1540); }); afterEach(() => { db.close(); @@ -19,6 +19,7 @@ describe('SQL/updateToSchemaVersion1530', () => { const CORE_UPDATE_QUERY = sqlFragment` UPDATE messages + INDEXED BY messages_conversationId_expirationStartTimestamp SET expirationStartTimestamp = 342342 WHERE @@ -48,8 +49,8 @@ describe('SQL/updateToSchemaVersion1530', () => { assert.strictEqual( detail, - 'SEARCH messages USING INDEX messages_conversationId_hasExpireTimer_expirationStartTimestamp' + - ' (conversationId=? AND hasExpireTimer=? AND expirationStartTimestamp=?)' + 'SEARCH messages USING INDEX messages_conversationId_expirationStartTimestamp' + + ' (conversationId=? AND expirationStartTimestamp=?)' ); }); it('uses index efficiently with null start + no storyId condition', () => { @@ -62,8 +63,8 @@ describe('SQL/updateToSchemaVersion1530', () => { assert.strictEqual( detail, - 'SEARCH messages USING INDEX messages_conversationId_hasExpireTimer_expirationStartTimestamp' + - ' (conversationId=? AND hasExpireTimer=? AND expirationStartTimestamp=?)' + 'SEARCH messages USING INDEX messages_conversationId_expirationStartTimestamp' + + ' (conversationId=? AND expirationStartTimestamp=?)' ); }); @@ -72,8 +73,8 @@ describe('SQL/updateToSchemaVersion1530', () => { assert.strictEqual( detail, - 'SEARCH messages USING INDEX messages_conversationId_hasExpireTimer_expirationStartTimestamp' + - ' (conversationId=? AND hasExpireTimer=? AND expirationStartTimestamp>?)' + 'SEARCH messages USING INDEX messages_conversationId_expirationStartTimestamp' + + ' (conversationId=? AND expirationStartTimestamp>?)' ); }); @@ -86,8 +87,8 @@ describe('SQL/updateToSchemaVersion1530', () => { assert.strictEqual( detail, - 'SEARCH messages USING INDEX messages_conversationId_hasExpireTimer_expirationStartTimestamp' + - ' (conversationId=? AND hasExpireTimer=? AND expirationStartTimestamp>?)' + 'SEARCH messages USING INDEX messages_conversationId_expirationStartTimestamp' + + ' (conversationId=? AND expirationStartTimestamp>?)' ); }); });