immich-app/immich · warning
Could not retrieve dimension size of column
Error message
Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512 What it means
getDimensionSize reads the embedding column's dimension from the database (e.g. via pgvector's typmod or a constraint). If the value cannot be retrieved or fails validation (must be an integer 1..65536), the code logs this warning and assumes a dimension of 512. A wrong assumption here can cause dimension mismatches during embedding operations.
Solutions
- Run migrations and the vector-extension reindex flow so the embedding column has the proper dim_size_constraint
- Check the constraint: SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'smart_search'::regclass;
- Set the dimension explicitly via the admin settings (which calls setDimensionSize) matching your CLIP model (512 for ViT-B-32)
- Verify pgvector extension is installed: CREATE EXTENSION IF NOT EXISTS vectors;
Example fix
// before (column without dimension) embedding real[] // after ALTER TABLE smart_search ADD CONSTRAINT dim_size_constraint CHECK (array_length(embedding::real[], 1) = 512); -- or let migrations define embedding vector(512)
Defensive patterns
Strategy: fallback
Validate before calling
const { rows } = await sql`
SELECT atttypmod AS dim FROM pg_attribute
WHERE attrelid = 'smart_search'::regclass AND attname = 'embedding'`.execute(db);
const dim = Number(rows[0]?.dim);
if (!Number.isInteger(dim) || dim < 1 || dim > 65536) {
throw new Error('Embedding dimension unknown — run migrations / set vector dimension explicitly');
} Type guard
function isValidDim(v: unknown): v is number {
return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 65536;
} Try / catch
try {
const dim = await db.getDimensionSize('smart_search', 'embedding');
assert(dim === expectedModelDim, `dimension ${dim} != model dim ${expectedModelDim}`);
} catch (err) {
logger.error('Dimension unavailable or mismatched; do not embed until fixed');
} Prevention
- Match CLIP model dimension (512) with the DB column dimension before embedding
- Run migrations after every upgrade so dim constraints exist
- Check dim_size_constraint when dimension warnings appear
- Install/verify pgvector extension before first boot
When it happens
Trigger: Querying the dimension of a column in smart_search/face_search returns null, or a value outside 1..2**16 — e.g. the embedding column is still real[] without a dim constraint, or the pgvector column attribute could not be parsed.
Common situations: Fresh table where embedding was added as plain real[] (see reindex path); migrations not fully applied; querying a non-vector column; broken pgvector installation.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Column 'embedding' does not exist in table
- Table does not exist, skipping reindexing. This is only…
- extension is not installed
- Migration " " failed
- No available version for
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/8d22f520a1f948cc.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/repositories/database.repository.ts:314
const { rows } = await sql<{ dimsize: number }>`
SELECT atttypmod as dimsize
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
WHERE c.relkind = 'r'::char
AND f.attnum > 0
AND c.relname = ${table}::text
AND f.attname = ${column}::text
`.execute(this.db);
const dimSize = rows[0]?.dimsize;
if (
!z
.int()
.min(1)
.max(2 ** 16)
.safeParse(dimSize).success
) {
this.logger.warn(`Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512`);
return 512;
}
return dimSize;
}
async setDimensionSize(dimSize: number): Promise<void> {
if (
!z
.int()
.min(1)
.max(2 ** 16)
.safeParse(dimSize).success
) {
throw new Error(`Invalid CLIP dimension size: ${dimSize}`);
}
// this is done in two transactions to handle concurrent writes
await this.db.transaction().execute(async (trx) => {View on GitHub (pinned to e55ac299a4)