chore(server)!: drop pgvecto.rs support (#28159)

drop pgvecto.rs
This commit is contained in:
Mert
2026-04-30 09:40:38 -04:00
committed by GitHub
parent c1051c7ed2
commit 97c62136b7
14 changed files with 34 additions and 174 deletions
+18 -80
View File
@@ -2,7 +2,7 @@ import { EXTENSION_NAMES } from 'src/constants';
import { DatabaseExtension, VectorIndex } from 'src/enum';
import { DatabaseService } from 'src/services/database.service';
import { VectorExtension } from 'src/types';
import { mockEnvData } from 'test/repositories/config.repository.mock';
import { envData, mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService, ServiceMocks } from 'test/utils';
describe(DatabaseService.name, () => {
@@ -55,7 +55,6 @@ describe(DatabaseService.name, () => {
describe.each(<Array<{ extension: VectorExtension; extensionName: string }>>[
{ extension: DatabaseExtension.Vector, extensionName: EXTENSION_NAMES[DatabaseExtension.Vector] },
{ extension: DatabaseExtension.Vectors, extensionName: EXTENSION_NAMES[DatabaseExtension.Vectors] },
{ extension: DatabaseExtension.VectorChord, extensionName: EXTENSION_NAMES[DatabaseExtension.VectorChord] },
])('should work with $extensionName', ({ extension, extensionName }) => {
beforeEach(() => {
@@ -68,20 +67,7 @@ describe(DatabaseService.name, () => {
]);
mocks.database.getVectorExtension.mockResolvedValue(extension);
mocks.config.getEnv.mockReturnValue(
mockEnvData({
database: {
config: {
connectionType: 'parts',
host: 'database',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'immich',
},
skipMigrations: false,
vectorExtension: extension,
},
}),
mockEnvData({ database: { ...envData.database, vectorExtension: extension } }),
);
});
@@ -157,7 +143,6 @@ describe(DatabaseService.name, () => {
installedVersion: minVersionInRange,
},
]);
mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: false });
await expect(sut.onBootstrap()).resolves.toBeUndefined();
@@ -278,27 +263,6 @@ describe(DatabaseService.name, () => {
expect(mocks.database.runMigrations).not.toHaveBeenCalled();
});
it(`should warn if ${extension} extension update requires restart`, async () => {
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: extension,
availableVersion: updateInRange,
installedVersion: minVersionInRange,
},
]);
mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: true });
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.logger.warn.mock.calls).toEqual(
expect.arrayContaining([expect.arrayContaining([expect.stringContaining(extensionName)])]),
);
expect(mocks.database.updateVectorExtension).toHaveBeenCalledWith(extension, updateInRange);
expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1);
expect(mocks.logger.fatal).not.toHaveBeenCalled();
});
it(`should reindex ${extension} indices if needed`, async () => {
await expect(sut.onBootstrap()).resolves.toBeUndefined();
@@ -329,22 +293,7 @@ describe(DatabaseService.name, () => {
});
it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => {
mocks.config.getEnv.mockReturnValue(
mockEnvData({
database: {
config: {
connectionType: 'parts',
host: 'database',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'immich',
},
skipMigrations: true,
vectorExtension: DatabaseExtension.Vectors,
},
}),
);
mocks.config.getEnv.mockReturnValue(mockEnvData({ database: { ...envData.database, skipMigrations: true } }));
await expect(sut.onBootstrap()).resolves.toBeUndefined();
@@ -352,7 +301,6 @@ describe(DatabaseService.name, () => {
});
it(`should throw error if extension could not be created`, async () => {
mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: false });
mocks.database.createExtension.mockRejectedValue(new Error('Failed to create extension'));
await expect(sut.onBootstrap()).rejects.toThrow('Failed to create extension');
@@ -365,35 +313,42 @@ describe(DatabaseService.name, () => {
});
it(`should drop unused extension`, async () => {
mocks.config.getEnv.mockReturnValue(
mockEnvData({ database: { ...envData.database, vectorExtension: DatabaseExtension.Vector } }),
);
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vector);
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.Vectors,
name: DatabaseExtension.Vector,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
{
name: DatabaseExtension.VectorChord,
installedVersion: null,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
]);
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
});
it(`should warn if unused extension could not be dropped`, async () => {
mocks.config.getEnv.mockReturnValue(
mockEnvData({ database: { ...envData.database, vectorExtension: DatabaseExtension.Vector } }),
);
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vector);
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.Vectors,
name: DatabaseExtension.Vector,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
{
name: DatabaseExtension.VectorChord,
installedVersion: null,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
]);
@@ -401,10 +356,9 @@ describe(DatabaseService.name, () => {
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
expect(mocks.logger.warn).toHaveBeenCalledTimes(1);
expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vectors');
expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vchord');
});
it(`should not try to drop pgvector when using vectorchord`, async () => {
@@ -426,21 +380,5 @@ describe(DatabaseService.name, () => {
expect(mocks.database.dropExtension).not.toHaveBeenCalled();
});
it(`should warn if using pgvecto.rs`, async () => {
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.Vectors,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
]);
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vectors);
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.logger.warn).toHaveBeenCalledTimes(1);
expect(mocks.logger.warn.mock.calls[0][0]).toContain('DEPRECATION WARNING');
});
});
});
+1 -14
View File
@@ -9,7 +9,6 @@ import { VectorExtension } from 'src/types';
type CreateFailedArgs = { name: string; extension: string };
type UpdateFailedArgs = { name: string; extension: string; availableVersion: string };
type DropFailedArgs = { name: string; extension: string };
type RestartRequiredArgs = { name: string; availableVersion: string };
type NightlyVersionArgs = { name: string; extension: string; version: string };
type OutOfRangeArgs = { name: string; extension: string; version: string; range: string };
type InvalidDowngradeArgs = { name: string; extension: string; installedVersion: string; availableVersion: string };
@@ -46,16 +45,10 @@ const messages = {
Please run 'DROP EXTENSION ${extension};' manually as a superuser.
See https://docs.immich.app/guides/database-queries for how to query the database.`,
restartRequired: ({ name, availableVersion }: RestartRequiredArgs) =>
`The ${name} extension has been updated to ${availableVersion}.
Please restart the Postgres instance to complete the update.`,
invalidDowngrade: ({ name, installedVersion, availableVersion }: InvalidDowngradeArgs) =>
`The database currently has ${name} ${installedVersion} activated, but the Postgres instance only has ${availableVersion} available.
This most likely means the extension was downgraded.
If ${name} ${installedVersion} is compatible with Immich, please ensure the Postgres instance has this available.`,
deprecatedExtension: (name: string) =>
`DEPRECATION WARNING: The ${name} extension is deprecated and support for it will be removed very soon.
See https://docs.immich.app/install/upgrading#migrating-to-vectorchord in order to switch to the VectorChord extension instead.`,
};
@Injectable()
@@ -74,9 +67,6 @@ export class DatabaseService extends BaseService {
await this.databaseRepository.withLock(DatabaseLock.Migrations, async () => {
const extension = await this.databaseRepository.getVectorExtension();
const name = EXTENSION_NAMES[extension];
if (extension === DatabaseExtension.Vectors) {
this.logger.warn(messages.deprecatedExtension(name));
}
const extensionRange = this.databaseRepository.getExtensionVersionRange(extension);
const extensionVersions = await this.databaseRepository.getExtensionVersions(VECTOR_EXTENSIONS);
@@ -156,10 +146,7 @@ export class DatabaseService extends BaseService {
private async updateExtension(extension: VectorExtension, availableVersion: string) {
this.logger.log(`Updating ${EXTENSION_NAMES[extension]} extension to ${availableVersion}`);
try {
const { restartRequired } = await this.databaseRepository.updateVectorExtension(extension, availableVersion);
if (restartRequired) {
this.logger.warn(messages.restartRequired({ name: EXTENSION_NAMES[extension], availableVersion }));
}
await this.databaseRepository.updateVectorExtension(extension, availableVersion);
} catch (error) {
this.logger.warn(messages.updateFailed({ name: EXTENSION_NAMES[extension], extension, availableVersion }));
throw error;