mirror of
https://github.com/immich-app/immich.git
synced 2026-04-29 20:48:48 -07:00
Compare commits
1 Commits
fix/stack-
...
chore/serv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f1a778f94 |
@@ -81,7 +81,7 @@ VectorChord is the successor extension to pgvecto.rs, allowing for higher perfor
|
||||
|
||||
### Migrating from pgvecto.rs
|
||||
|
||||
Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so.
|
||||
Support for pgvecto.rs has been dropped as of 3.0, hence all users currently using pgvecto.rs should migrate to VectorChord. There are two primary approaches to do so.
|
||||
|
||||
The easiest option is to have both extensions installed during the migration:
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ Information on the current workers can be found [here](/administration/jobs-work
|
||||
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
|
||||
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
|
||||
| `DB_SSL_MODE` | Database SSL mode | | server |
|
||||
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server |
|
||||
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`]) | | server |
|
||||
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
|
||||
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
|
||||
|
||||
|
||||
@@ -130,7 +130,3 @@ These storage mediums have different performance characteristics. As a result, t
|
||||
#### Can I use the new database image as a general PostgreSQL image outside of Immich?
|
||||
|
||||
It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image.
|
||||
|
||||
#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord?
|
||||
|
||||
VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption.
|
||||
|
||||
@@ -14,7 +14,6 @@ export const ErrorMessages = {
|
||||
|
||||
export const POSTGRES_VERSION_RANGE = '>=14.0.0';
|
||||
export const VECTORCHORD_VERSION_RANGE = '>=0.3 <2';
|
||||
export const VECTORS_VERSION_RANGE = '>=0.2 <0.4';
|
||||
export const VECTOR_VERSION_RANGE = '>=0.5 <1';
|
||||
|
||||
export const JOBS_ASSET_PAGINATION_SIZE = 1000;
|
||||
@@ -24,15 +23,10 @@ export const EXTENSION_NAMES: Record<DatabaseExtension, string> = {
|
||||
cube: 'cube',
|
||||
earthdistance: 'earthdistance',
|
||||
vector: 'pgvector',
|
||||
vectors: 'pgvecto.rs',
|
||||
vchord: 'VectorChord',
|
||||
} as const;
|
||||
|
||||
export const VECTOR_EXTENSIONS = [
|
||||
DatabaseExtension.VectorChord,
|
||||
DatabaseExtension.Vectors,
|
||||
DatabaseExtension.Vector,
|
||||
] as const;
|
||||
export const VECTOR_EXTENSIONS = [DatabaseExtension.VectorChord, DatabaseExtension.Vector] as const;
|
||||
|
||||
export const VECTOR_INDEX_TABLES = {
|
||||
[VectorIndex.Clip]: 'smart_search',
|
||||
|
||||
@@ -77,7 +77,7 @@ export const EnvSchema = z
|
||||
DB_SSL_MODE: DatabaseSslModeSchema.optional(),
|
||||
DB_URL: z.string().optional(),
|
||||
DB_USERNAME: z.string().optional(),
|
||||
DB_VECTOR_EXTENSION: z.enum(['pgvector', 'pgvecto.rs', 'vectorchord']).optional(),
|
||||
DB_VECTOR_EXTENSION: z.enum(['pgvector', 'vectorchord']).optional(),
|
||||
NO_COLOR: z.string().optional(),
|
||||
REDIS_HOSTNAME: z.string().optional(),
|
||||
REDIS_PORT: z.coerce.number().int().optional(),
|
||||
|
||||
@@ -607,7 +607,6 @@ export enum DatabaseExtension {
|
||||
Cube = 'cube',
|
||||
EarthDistance = 'earthdistance',
|
||||
Vector = 'vector',
|
||||
Vectors = 'vectors',
|
||||
VectorChord = 'vchord',
|
||||
}
|
||||
|
||||
|
||||
@@ -248,10 +248,6 @@ const getEnv = (): EnvData => {
|
||||
vectorExtension = DatabaseExtension.Vector;
|
||||
break;
|
||||
}
|
||||
case 'pgvecto.rs': {
|
||||
vectorExtension = DatabaseExtension.Vectors;
|
||||
break;
|
||||
}
|
||||
case 'vectorchord': {
|
||||
vectorExtension = DatabaseExtension.VectorChord;
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { schemaDiff, schemaFromCode, schemaFromDatabase } from '@immich/sql-tools';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import AsyncLock from 'async-lock';
|
||||
import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely';
|
||||
import { FileMigrationProvider, Kysely, Migrator, sql } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { readdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
VECTOR_VERSION_RANGE,
|
||||
VECTORCHORD_LIST_SLACK_FACTOR,
|
||||
VECTORCHORD_VERSION_RANGE,
|
||||
VECTORS_VERSION_RANGE,
|
||||
} from 'src/constants';
|
||||
import { GenerateSql } from 'src/decorators';
|
||||
import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum';
|
||||
@@ -23,7 +22,7 @@ import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import 'src/schema'; // make sure all schema definitions are imported for schemaFromCode
|
||||
import { DB } from 'src/schema';
|
||||
import { immich_uuid_v7 } from 'src/schema/functions';
|
||||
import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types';
|
||||
import { ExtensionVersion, VectorExtension } from 'src/types';
|
||||
import { vectorIndexQuery } from 'src/utils/database';
|
||||
import { isValidInteger } from 'src/validation';
|
||||
|
||||
@@ -73,7 +72,7 @@ export class DatabaseRepository {
|
||||
return getVectorExtension(this.db);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DatabaseExtension.Vectors]] })
|
||||
@GenerateSql({ params: [[DatabaseExtension.Vector]] })
|
||||
async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise<ExtensionVersion[]> {
|
||||
const { rows } = await sql<ExtensionVersion>`
|
||||
SELECT name, default_version as "availableVersion", installed_version as "installedVersion"
|
||||
@@ -88,9 +87,6 @@ export class DatabaseRepository {
|
||||
case DatabaseExtension.VectorChord: {
|
||||
return VECTORCHORD_VERSION_RANGE;
|
||||
}
|
||||
case DatabaseExtension.Vectors: {
|
||||
return VECTORS_VERSION_RANGE;
|
||||
}
|
||||
case DatabaseExtension.Vector: {
|
||||
return VECTOR_VERSION_RANGE;
|
||||
}
|
||||
@@ -125,7 +121,7 @@ export class DatabaseRepository {
|
||||
await sql`DROP EXTENSION IF EXISTS ${sql.raw(extension)}`.execute(this.db);
|
||||
}
|
||||
|
||||
async updateVectorExtension(extension: VectorExtension, targetVersion?: string): Promise<VectorUpdateResult> {
|
||||
async updateVectorExtension(extension: VectorExtension, targetVersion?: string): Promise<void> {
|
||||
const [{ availableVersion, installedVersion }] = await this.getExtensionVersions([extension]);
|
||||
if (!installedVersion) {
|
||||
throw new Error(`${EXTENSION_NAMES[extension]} extension is not installed`);
|
||||
@@ -136,10 +132,8 @@ export class DatabaseRepository {
|
||||
}
|
||||
targetVersion ??= availableVersion;
|
||||
|
||||
let restartRequired = false;
|
||||
const diff = semver.diff(installedVersion, targetVersion);
|
||||
if (!diff) {
|
||||
return { restartRequired: false };
|
||||
if (!semver.diff(installedVersion, targetVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
@@ -147,22 +141,8 @@ export class DatabaseRepository {
|
||||
this.db.schema.dropIndex(VectorIndex.Face).ifExists().execute(),
|
||||
]);
|
||||
|
||||
await this.db.transaction().execute(async (tx) => {
|
||||
await this.setSearchPath(tx);
|
||||
|
||||
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx);
|
||||
|
||||
if (extension === DatabaseExtension.Vectors && (diff === 'major' || diff === 'minor')) {
|
||||
await sql`SELECT pgvectors_upgrade()`.execute(tx);
|
||||
restartRequired = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!restartRequired) {
|
||||
await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]);
|
||||
}
|
||||
|
||||
return { restartRequired };
|
||||
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(this.db);
|
||||
await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]);
|
||||
}
|
||||
|
||||
async prewarm(index: VectorIndex): Promise<void> {
|
||||
@@ -198,12 +178,6 @@ export class DatabaseRepository {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DatabaseExtension.Vectors: {
|
||||
if (!row.indexdef.toLowerCase().includes('using vectors')) {
|
||||
promises.push(this.reindexVectors(indexName));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DatabaseExtension.VectorChord: {
|
||||
const matches = row.indexdef.match(/(?<=lists = \[)\d+/g);
|
||||
const lists = matches && matches.length > 0 ? Number(matches[0]) : 1;
|
||||
@@ -260,11 +234,10 @@ export class DatabaseRepository {
|
||||
await sql`ALTER TABLE ${sql.raw(table)} ADD COLUMN embedding real[] NOT NULL`.execute(tx);
|
||||
}
|
||||
await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.execute(tx);
|
||||
const schema = vectorExtension === DatabaseExtension.Vectors ? 'vectors.' : '';
|
||||
await sql`
|
||||
ALTER TABLE ${sql.raw(table)}
|
||||
ALTER COLUMN embedding
|
||||
SET DATA TYPE ${sql.raw(schema)}vector(${sql.raw(String(dimSize))})`.execute(tx);
|
||||
SET DATA TYPE vector(${sql.raw(String(dimSize))})`.execute(tx);
|
||||
await sql.raw(vectorIndexQuery({ vectorExtension, table, indexName, lists })).execute(tx);
|
||||
});
|
||||
try {
|
||||
@@ -275,10 +248,6 @@ export class DatabaseRepository {
|
||||
this.logger.log(`Reindexed ${indexName}`);
|
||||
}
|
||||
|
||||
private async setSearchPath(tx: Transaction<DB>): Promise<void> {
|
||||
await sql`SET search_path TO "$user", public, vectors`.execute(tx);
|
||||
}
|
||||
|
||||
private async getDatabaseName(): Promise<string> {
|
||||
const { rows } = await sql<{ db: string }>`SELECT current_database() as db`.execute(this.db);
|
||||
return rows[0].db;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
import { ErrorMessages } from 'src/constants';
|
||||
import { DatabaseExtension } from 'src/enum';
|
||||
import { getVectorExtension } from 'src/repositories/database.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { vectorIndexQuery } from 'src/utils/database';
|
||||
@@ -107,9 +106,6 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;`.execute(db);
|
||||
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||
await sql`SET search_path TO "$user", public, vectors`.execute(db);
|
||||
}
|
||||
await sql`CREATE TYPE "assets_status_enum" AS ENUM ('active','trashed','deleted');`.execute(db);
|
||||
await sql`CREATE TYPE "sourcetype" AS ENUM ('machine-learning','exif','manual');`.execute(db);
|
||||
await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -394,10 +394,6 @@ export interface ExtensionVersion {
|
||||
installedVersion: string | null;
|
||||
}
|
||||
|
||||
export interface VectorUpdateResult {
|
||||
restartRequired: boolean;
|
||||
}
|
||||
|
||||
export interface ImmichFile extends Express.Multer.File {
|
||||
uuid: string;
|
||||
/** sha1 hash of file */
|
||||
|
||||
@@ -427,16 +427,6 @@ export function vectorIndexQuery({ vectorExtension, table, indexName, lists }: V
|
||||
sampling_factor = 1024
|
||||
$$)`;
|
||||
}
|
||||
case DatabaseExtension.Vectors: {
|
||||
return `
|
||||
CREATE INDEX IF NOT EXISTS ${indexName} ON ${table}
|
||||
USING vectors (embedding vector_cos_ops) WITH (options = $$
|
||||
optimizing.optimizing_threads = 4
|
||||
[indexing.hnsw]
|
||||
m = 16
|
||||
ef_construction = 300
|
||||
$$)`;
|
||||
}
|
||||
case DatabaseExtension.Vector: {
|
||||
return `
|
||||
CREATE INDEX IF NOT EXISTS ${indexName} ON ${table}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ConfigRepository, EnvData } from 'src/repositories/config.repository';
|
||||
import { RepositoryInterface } from 'src/types';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
const envData: EnvData = {
|
||||
export const envData: EnvData = {
|
||||
port: 2283,
|
||||
environment: ImmichEnvironment.Production,
|
||||
logFormat: LogFormat.Console,
|
||||
@@ -30,9 +30,8 @@ const envData: EnvData = {
|
||||
username: 'postgres',
|
||||
password: 'postgres',
|
||||
},
|
||||
|
||||
skipMigrations: false,
|
||||
vectorExtension: DatabaseExtension.Vectors,
|
||||
vectorExtension: DatabaseExtension.VectorChord,
|
||||
},
|
||||
|
||||
helmet: {
|
||||
|
||||
Reference in New Issue
Block a user