diff --git a/modules/module-postgres-storage/src/migrations/scripts/1734384000000-bucket-data-compact-index.ts b/modules/module-postgres-storage/src/migrations/scripts/1734384000000-bucket-data-compact-index.ts new file mode 100644 index 00000000..1a695ea4 --- /dev/null +++ b/modules/module-postgres-storage/src/migrations/scripts/1734384000000-bucket-data-compact-index.ts @@ -0,0 +1,41 @@ +import { migrations } from '@powersync/service-core'; + +import { openMigrationDB } from '../migration-utils.js'; + +/** + * Migration: Add specialized index for bucket_data compaction query optimization + * + * This migration creates an index optimized for the compaction query in PostgresCompactor.ts + * which uses COLLATE "C" (binary collation) and DESC ordering. + * The index enables PostgreSQL to push COLLATE "C" conditions into Index Cond + * instead of applying them as filters, significantly reducing rows scanned. + */ +export const up: migrations.PowerSyncMigrationFunction = async (context) => { + const { + service_context: { configuration } + } = context; + await using client = openMigrationDB(configuration.storage); + + await client.transaction(async (db) => { + await db.sql` + CREATE INDEX IF NOT EXISTS idx_bucket_data_compact ON bucket_data ( + group_id, + bucket_name COLLATE "C" DESC, + op_id DESC + ) + `.execute(); + + await db.sql`ANALYZE bucket_data`.execute(); + }); +}; + +export const down: migrations.PowerSyncMigrationFunction = async (context) => { + const { + service_context: { configuration } + } = context; + await using client = openMigrationDB(configuration.storage); + + await client.transaction(async (db) => { + await db.sql`DROP INDEX IF EXISTS idx_bucket_data_compact`.execute(); + }); +}; diff --git a/modules/module-postgres-storage/src/storage/PostgresCompactor.ts b/modules/module-postgres-storage/src/storage/PostgresCompactor.ts index b19d866e..3d5f8918 100644 --- a/modules/module-postgres-storage/src/storage/PostgresCompactor.ts +++ b/modules/module-postgres-storage/src/storage/PostgresCompactor.ts @@ -123,16 +123,16 @@ export class PostgresCompactor { bucket_data WHERE group_id = ${{ type: 'int4', value: this.group_id }} - AND bucket_name >= ${{ type: 'varchar', value: bucketLower }} + AND bucket_name >= ${{ type: 'varchar', value: bucketLower }} COLLATE "C" AND ( ( - bucket_name = ${{ type: 'varchar', value: bucketUpper }} + bucket_name = ${{ type: 'varchar', value: bucketUpper }} COLLATE "C" AND op_id < ${{ type: 'int8', value: upperOpIdLimit }} ) OR bucket_name < ${{ type: 'varchar', value: bucketUpper }} COLLATE "C" -- Use binary comparison ) ORDER BY - bucket_name DESC, + bucket_name COLLATE "C" DESC, op_id DESC LIMIT ${{ type: 'int4', value: this.moveBatchQueryLimit }} diff --git a/modules/module-postgres-storage/test/src/__snapshots__/storage.test.ts.snap b/modules/module-postgres-storage/test/src/__snapshots__/storage.test.ts.snap index d1b24f45..79e1e624 100644 --- a/modules/module-postgres-storage/test/src/__snapshots__/storage.test.ts.snap +++ b/modules/module-postgres-storage/test/src/__snapshots__/storage.test.ts.snap @@ -2,7 +2,7 @@ exports[`Postgres Sync Bucket Storage - Data > empty storage metrics 1`] = ` { - "operations_size_bytes": 16384, + "operations_size_bytes": 24576, "parameters_size_bytes": 32768, "replication_size_bytes": 16384, }