Muchas pruebas de inteligencia artificial agregan una base vectorial separada aunque los documentos, permisos y metadatos ya residan en PostgreSQL. Con pgvector, la base puede almacenar embeddings junto con esos datos y ejecutar búsquedas exactas o aproximadas mediante SQL.

Esto simplifica la infraestructura, pero no elimina la necesidad de medir calidad, controlar permisos y versionar el modelo de embeddings. El índice vectorial acelera la recuperación; no decide qué documentos puede ver cada usuario.

Cuándo conviene usar pgvector

pgvector resulta especialmente útil para:

  • Sistemas RAG internos.
  • Buscadores semánticos.
  • Recomendaciones simples.
  • Prototipos con datos relacionales.
  • Aplicaciones que necesitan combinar similitud y filtros SQL.
  • Volúmenes que todavía pueden operarse razonablemente en PostgreSQL.

Para documentos extensos, normalmente conviene almacenar un embedding por fragmento recuperable, no uno por archivo completo.

Requisitos previos

Necesitarás:

  • Una versión de PostgreSQL soportada por el paquete elegido.
  • La extensión pgvector instalada en el servidor.
  • Permisos para ejecutar CREATE EXTENSION.
  • Un modelo de embeddings definido y versionado.
  • Una estrategia de fragmentación del contenido.
  • Backups probados antes de modificar el esquema.
  • Un conjunto de consultas reales para medir calidad y latencia.

En servicios administrados, la extensión puede habilitarse desde el panel o mediante el mecanismo del proveedor, sin acceso al sistema operativo.

Arquitectura de trabajo

El flujo recomendado es:

  1. Recibir o actualizar un documento.
  2. Dividirlo en fragmentos.
  3. Generar embeddings con un modelo conocido.
  4. Guardar texto, metadatos, permisos y vectores en PostgreSQL.
  5. Aplicar filtros de acceso en SQL.
  6. Ordenar los candidatos por distancia vectorial.
  7. Entregar únicamente el contexto autorizado.

El embedding y los datos relacionales pueden guardarse en la misma transacción. La generación del vector ocurre normalmente en la aplicación antes de abrir esa transacción.

Paso 1: instalar y habilitar pgvector

En Debian o Ubuntu, el paquete está disponible mediante el repositorio APT oficial de PostgreSQL:

sudo apt install postgresql-18-pgvector

Sustituye 18 por la versión real del servidor. El paquete no estará necesariamente disponible hasta configurar el repositorio PostgreSQL APT correspondiente.

Después, habilita la extensión en cada base que la utilizará:

psql --dbname appdb \
  --command 'CREATE EXTENSION IF NOT EXISTS vector;'

Comprueba la instalación:

SELECT extname, extversion
FROM pg_extension
WHERE extname = 'vector';

La documentación oficial también ofrece instalación mediante RPM, Homebrew, PGXN, compilación y contenedores. Instalación de pgvector.

Antes de modificar una base existente, genera un backup y ensaya su restauración:

pg_dump \
  --format=custom \
  --file=appdb-before-pgvector.dump \
  appdb

El servidor de destino también debe tener disponible la extensión antes de restaurar.

Paso 2: diseñar la tabla y versionar el modelo

El siguiente esquema almacena un vector por fragmento y conserva la identidad del modelo:

CREATE TABLE documento_fragmentos (
  id bigserial PRIMARY KEY,
  documento_id bigint NOT NULL,
  tenant_id bigint NOT NULL,
  posicion integer NOT NULL,
  titulo text NOT NULL,
  contenido text NOT NULL,
  embedding_modelo text NOT NULL,
  embedding_version text NOT NULL,
  embedding vector(768) NOT NULL,
  creado_en timestamptz NOT NULL DEFAULT now(),

  UNIQUE (documento_id, posicion),
  CHECK (vector_norm(embedding) > 0)
);

CREATE INDEX documento_fragmentos_tenant_idx
ON documento_fragmentos (tenant_id);

vector(768) obliga a que cada valor tenga exactamente 768 dimensiones. La comprobación adicional rechaza vectores de norma cero, que no pueden indexarse para distancia coseno.

El tipo vector admite más dimensiones para almacenamiento, pero los índices HNSW sobre vector admiten hasta 2000. Para dimensiones mayores existen alternativas como halfvec, cuantización o reducción dimensional. Límites e índices de pgvector.

No mezcles embeddings de modelos incompatibles en la misma columna indexada. Si una actualización cambia la dimensión, crea otra columna o tabla, rellénala en paralelo y realiza un cambio controlado.

Paso 3: insertar embeddings de forma segura

La aplicación debe utilizar parámetros, no construir SQL concatenando el vector:

INSERT INTO documento_fragmentos (
  documento_id,
  tenant_id,
  posicion,
  titulo,
  contenido,
  embedding_modelo,
  embedding_version,
  embedding
)
VALUES (
  $1,
  $2,
  $3,
  $4,
  $5,
  $6,
  $7,
  $8::vector
);

El parámetro $8 debe contener los 768 valores producidos por el modelo. Utiliza el adaptador de pgvector disponible para el driver de tu lenguaje cuando corresponda.

Guarda en la misma operación:

  • Nombre estable del modelo.
  • Versión o fecha del modelo.
  • Dimensión.
  • Estrategia de fragmentación.
  • Identificador del documento original.
  • Hash o versión del contenido.

Así podrás determinar qué fragmentos deben recalcularse cuando cambie el modelo o el documento.

Paso 4: aplicar permisos dentro de SQL

Supongamos que los permisos por documento se almacenan en una tabla ACL:

CREATE TABLE documento_acl (
  documento_id bigint NOT NULL,
  principal_id bigint NOT NULL,
  PRIMARY KEY (documento_id, principal_id)
);

CREATE INDEX documento_acl_principal_idx
ON documento_acl (principal_id, documento_id);

La búsqueda debe incluir el tenant y el permiso antes de devolver contenido:

SELECT
  f.documento_id,
  f.titulo,
  f.contenido,
  1 - (f.embedding <=> $3::vector) AS similitud
FROM documento_fragmentos AS f
WHERE f.tenant_id = $1
  AND EXISTS (
    SELECT 1
    FROM documento_acl AS a
    WHERE a.documento_id = f.documento_id
      AND a.principal_id = $2
  )
ORDER BY f.embedding <=> $3::vector
LIMIT 5;

El operador <=> calcula distancia coseno. Restar el resultado a 1 permite presentar una similitud, pero el índice requiere que el ORDER BY conserve directamente la expresión de distancia.

Nunca recuperes candidatos globales para filtrarlos después en la aplicación. Además de alterar la calidad de los resultados, esa práctica puede exponer títulos, textos o metadatos no autorizados.

Como defensa adicional puede utilizarse Row-Level Security. PostgreSQL evalúa estas políticas para determinar qué filas son visibles al rol que ejecuta la consulta. Los superusuarios, roles con BYPASSRLS y normalmente el propietario de la tabla pueden omitirlas, por lo que la aplicación debe conectarse con un rol restringido. Políticas RLS de PostgreSQL.

Paso 5: medir antes de crear un índice aproximado

Sin un índice HNSW o IVFFlat, pgvector ejecuta una búsqueda exacta. Para conjuntos pequeños o filtros muy selectivos, esta búsqueda puede ser suficiente y ofrece recall completo.

Mide primero:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM documento_fragmentos
WHERE tenant_id = 42
ORDER BY embedding <=> $VECTOR$[0.03,0.01,...]$VECTOR$::vector
LIMIT 5;

El literal debe contener las 768 dimensiones reales; los puntos suspensivos se muestran únicamente para abreviar el ejemplo.

Crea el índice cuando las mediciones demuestren que la búsqueda exacta ya no cumple el objetivo de latencia.

Paso 6: crear el índice HNSW

Para búsquedas por distancia coseno:

CREATE INDEX CONCURRENTLY documento_fragmentos_embedding_hnsw
ON documento_fragmentos
USING hnsw (embedding vector_cosine_ops);

CONCURRENTLY evita bloquear inserciones, actualizaciones y eliminaciones durante la mayor parte de la construcción. No puede ejecutarse dentro de un bloque de transacción; algunos sistemas de migraciones requieren desactivar la transacción automática para este paso.

Después de una carga importante:

ANALYZE documento_fragmentos;

ANALYZE actualiza las estadísticas que utiliza el planificador para elegir el plan de ejecución. PostgreSQL también lo ejecuta mediante autovacuum, pero después de una migración masiva conviene hacerlo explícitamente. Documentación de ANALYZE.

HNSW ofrece un buen equilibrio entre velocidad y recall, pero consume memoria y tarda más en construirse que IVFFlat. Utiliza los parámetros predeterminados hasta contar con mediciones que justifiquen modificarlos.

Paso 7: ajustar búsquedas con filtros

En un índice aproximado global, pgvector recorre primero el índice y aplica después filtros como tenant_id o permisos. Por eso una consulta filtrada puede devolver menos resultados o perder vecinos relevantes.

Desde pgvector 0.8.0 pueden habilitarse escaneos iterativos:

BEGIN;

SET LOCAL hnsw.iterative_scan = strict_order;
SET LOCAL hnsw.ef_search = 100;

SELECT
  f.documento_id,
  f.titulo,
  f.contenido
FROM documento_fragmentos AS f
WHERE f.tenant_id = $1
  AND EXISTS (
    SELECT 1
    FROM documento_acl AS a
    WHERE a.documento_id = f.documento_id
      AND a.principal_id = $2
  )
ORDER BY f.embedding <=> $3::vector
LIMIT 5;

COMMIT;

Un hnsw.ef_search mayor puede mejorar recall a costa de latencia. SET LOCAL limita el ajuste a la transacción actual.

Cuando existen pocos tenants grandes, considera índices parciales. Para muchos tenants con requisitos fuertes de aislamiento, evalúa particionamiento o tablas separadas. La documentación de pgvector advierte que compartir un índice aproximado entre tenants afecta tanto la velocidad como el recall. Filtrado y multitenancy.

Paso 8: validar exactitud, seguridad y rendimiento

La validación debe usar la misma consulta, el mismo usuario y los mismos filtros.

Obtén primero el resultado exacto:

BEGIN;

SET LOCAL enable_indexscan = off;

SELECT id
FROM documento_fragmentos
WHERE tenant_id = $1
ORDER BY embedding <=> $2::vector
LIMIT 5;

ROLLBACK;

Después ejecuta la búsqueda normal con HNSW y compara los IDs. Para cinco resultados:

recall@5 = resultados relevantes compartidos / 5

Evalúa con un conjunto representativo de preguntas:

  • Recall@5 y recall@10.
  • Latencia p50, p95 y p99.
  • Tiempo total del flujo RAG.
  • Resultados devueltos por tenant y usuario.
  • Plan obtenido con EXPLAIN (ANALYZE, BUFFERS).
  • Uso de CPU, memoria, caché y disco.
  • Tamaño y tiempo de construcción del índice.

La propia documentación de pgvector recomienda comparar la búsqueda aproximada con la exacta para controlar el recall. Monitoreo de pgvector.

Pruebas de funcionamiento

Antes del cambio en producción:

  1. Confirma la versión de la extensión.
  2. Verifica que todos los embeddings tengan 768 dimensiones.
  3. Comprueba que no existan vectores nulos o de norma cero.
  4. Ejecuta búsquedas exactas con preguntas reales.
  5. Crea el índice en un entorno aislado.
  6. Compara exactitud y HNSW.
  7. Prueba usuarios con y sin permiso sobre el mismo documento.
  8. Comprueba el plan de ejecución.
  9. Mide el impacto sobre inserciones, backups y réplicas.
  10. Ensaya el rollback antes de activar la nueva ruta de búsqueda.

Mantén disponible la búsqueda exacta o la implementación anterior hasta validar calidad y controles de acceso.

Problemas frecuentes

Error de dimensión

La columna vector(768) solo acepta vectores de esa dimensión. Comprueba el modelo utilizado por cada proceso:

SELECT
  embedding_modelo,
  embedding_version,
  vector_dims(embedding),
  count(*)
FROM documento_fragmentos
GROUP BY 1, 2, 3;

El índice no se utiliza

El planificador necesita una consulta con ORDER BY sobre el operador correspondiente y normalmente un LIMIT. Comprueba que el índice use vector_cosine_ops y la consulta <=>.

HNSW devuelve menos de cinco resultados

Los filtros, las tuplas eliminadas, los vectores nulos o los vectores de norma cero pueden reducir resultados. Habilita escaneo iterativo y mide un valor mayor de hnsw.ef_search.

La creación del índice consume demasiada memoria

HNSW utiliza maintenance_work_mem durante la construcción. No lo aumentes por encima de la memoria disponible. Construye durante una ventana controlada y supervisa la instancia.

La búsqueda devuelve documentos sin permiso

Confirma que tenant y ACL formen parte de la consulta o estén impuestos mediante RLS. Revisa también que el rol de la aplicación no tenga BYPASSRLS y no sea propietario de la tabla.

Diagrama de Consulta con pgvector

Buenas prácticas para producción

  • Versiona modelo, dimensión y estrategia de fragmentación.
  • Usa parámetros SQL y adaptadores del driver.
  • Aplica permisos antes de devolver cualquier contenido.
  • Empieza con búsqueda exacta y agrega HNSW cuando las métricas lo justifiquen.
  • Compara recall aproximado contra resultados exactos.
  • Crea índices con CONCURRENTLY en tablas activas.
  • Ejecuta ANALYZE después de cargas importantes.
  • Supervisa consultas con pg_stat_statements.
  • Trata embeddings y textos como datos potencialmente sensibles.
  • Prueba backups y restauraciones con la extensión instalada.
  • Migra modelos nuevos mediante backfill y cambio controlado.
  • Combina similitud vectorial con búsqueda textual cuando mejore la calidad.

Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting