immich-app/immich · warning

Column 'embedding' does not exist in table

Error message

Column 'embedding' does not exist in table '${table}', truncating and adding column.

What it means

While reindexing a vector table, if the expected 'embedding' column is missing, the code truncates the table and recreates the column as real[]. All existing embeddings in that table are destroyed. This indicates schema drift: the table exists but its embedding column does not.

Solutions

  1. Re-run Immich so migrations complete, then trigger reindexing via the admin settings (vector extension update)
  2. Back up the database before reindexing since embeddings will be re-generated (smart search / facial recognition will rebuild)
  3. If the data is expendable, accept the TRUNCATE and let Immich re-embed the library
  4. Check migration history (migrations table) for failed migrations and fix them before reindexing

Example fix

// before (partial schema)
ALTER TABLE smart_search ADD COLUMN embedding vector(512); // applied without full migration
// after (let migrations + reindex rebuild)
-- ensure migrations succeeded, then in Immich admin: Update vector extension, which drops/recreates the index correctly
Defensive patterns

Strategy: fallback

Validate before calling

const hasEmbedding = await db
  .selectFrom('information_schema.columns')
  .where('table_name', '=', table)
  .where('column_name', '=', 'embedding')
  .select(({ fn }) => fn.countAll().as('n'))
  .executeTakeFirst();
if (!hasEmbedding || Number(hasEmbedding.n) === 0) {
  throw new Error(`Refusing reindex: ${table} lacks 'embedding' column — run migrations first`);
}

Try / catch

try {
  await backupDatabase();
  await db.reindexVectorsIfNeeded();
} catch (err) {
  logger.error('Reindex failed; restore from backup if schema is inconsistent');
  throw err;
}

Prevention

When it happens

Trigger: reindexVectors (via updateVectorExtension or reindexVectorsIfNeeded) runs against a table where every information_schema row shows no column named 'embedding' — e.g. a partially applied migration or an old schema.

Common situations: Interrupted or failed migrations leaving the table without the embedding column; manual schema edits; restoring a partial dump; switching vector extensions against a legacy table layout.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/40f354dbf7d2145e. Report an issue: GitHub.

Appendix: source

Thrown at server/src/repositories/database.repository.ts:235

    const { rows } = await sql<{
      columnName: string;
    }>`SELECT column_name as "columnName" FROM information_schema.columns WHERE table_name = ${table}`.execute(this.db);
    if (rows.length === 0) {
      this.logger.warn(
        `Table ${table} does not exist, skipping reindexing. This is only normal if this is a new Immich instance.`,
      );
      return;
    }
    const dimSize = await this.getDimensionSize(table);
    lists ||= this.targetListCount(await this.getRowCount(table));
    await this.db.transaction().execute(async (tx) => {
      await sql`DROP INDEX IF EXISTS ${sql.raw(indexName)}`.execute(tx);
      if (table === 'smart_search') {
        await sql`ALTER TABLE ${sql.raw(table)} DROP CONSTRAINT IF EXISTS dim_size_constraint`.execute(tx);
      }
      if (rows.every((row) => row.columnName !== 'embedding')) {
        this.logger.warn(`Column 'embedding' does not exist in table '${table}', truncating and adding column.`);
        await sql`TRUNCATE TABLE ${sql.raw(table)}`.execute(tx);
        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);
      await sql`
        ALTER TABLE ${sql.raw(table)}
        ALTER COLUMN embedding
        SET DATA TYPE vector(${sql.raw(String(dimSize))})`.execute(tx);
      await sql.raw(vectorIndexQuery({ vectorExtension, table, indexName, lists })).execute(tx);
    });
    this.logger.log(`Reindexed ${indexName}`);
    void this.vacuum({ table }).catch((error) => this.logger.warn(`Failed to vacuum ${table}: ${error}`));
  }

  async vacuum({ analyze = false, table }: { analyze?: boolean; table?: keyof DB } = {}): Promise<void> {
    try {
      await sql`VACUUM ${sql.raw(analyze ? 'ANALYZE' : '')} ${sql.raw(table ?? '')}`.execute(this.db);
    } catch (error) {

View on GitHub (pinned to e55ac299a4)