diff --git a/backend/open_webui/retrieval/vector/dbs/pgvector.py b/backend/open_webui/retrieval/vector/dbs/pgvector.py index 75daeccd12..18963bdf9a 100644 --- a/backend/open_webui/retrieval/vector/dbs/pgvector.py +++ b/backend/open_webui/retrieval/vector/dbs/pgvector.py @@ -223,6 +223,9 @@ class PgvectorClient(VectorDBBase): ) if not existing_index_def: + if index_method == 'ivfflat' and not self._has_enough_ivfflat_training_rows(): + return + index_sql = ( f'CREATE INDEX IF NOT EXISTS {index_name} ' f'ON document_chunk USING {index_method} (vector {VECTOR_OPCLASS})' @@ -237,6 +240,24 @@ class PgvectorClient(VectorDBBase): f' {index_options}' if index_options else '', ) + def _has_enough_ivfflat_training_rows(self) -> bool: + # ivfflat samples 50 rows per list to place its centroids, so recall stays poor until the table holds that many + min_training_rows = 50 * PGVECTOR_IVFFLAT_LISTS + row_count = self.session.execute( + text('SELECT count(*) FROM (SELECT 1 FROM document_chunk LIMIT :min_training_rows) AS sample'), + {'min_training_rows': min_training_rows}, + ).scalar() + + if row_count < min_training_rows: + log.info( + "Deferring vector index 'idx_document_chunk_vector' until document_chunk holds %s rows to cluster on, " + 'it has %s. Searches run as an exact scan until then.', + min_training_rows, + row_count, + ) + return False + return True + def _ensure_text_search_index(self) -> None: if PGVECTOR_PGCRYPTO: return