Todos los recursos

Llega un punto en que meter los vectores dentro de Postgres deja de dar abasto: el corpus crece demasiado, el QPS sube mucho o el filtrado pesa tanto que el planner ya no puede ir rápido. Qdrant es el siguiente paso, y puedes auto-hospedarlo: una base vectorial en Rust con búsqueda HNSW, filtrado que ocurre dentro de la ruta del índice y cuantización que cambia recall por una memoria que sí te puedes permitir. Aquí te cuento qué es, cuándo justifica el segundo servicio que te cuesta, cómo levantarlo y consultarlo, y las trampas de recall que, sin que te enteres, dejan tu búsqueda incorrecta en lugar de lenta.

Qdrant: un motor vectorial dedicado

En resumen

  • Qdrant es una base de datos vectorial especializada, escrita en Rust. Hace búsqueda aproximada de vecinos más cercanos (HNSW), corre como un solo contenedor y habla tanto REST como gRPC.
  • Su rasgo distintivo son los payloads filtrables: los filtros se evalúan dentro del recorrido del índice, así que «docs similares donde status = published y lang = es» se mantiene rápido en lugar de caer en un post-filtro.
  • La cuantización (escalar y binaria) baja muchísimo el consumo de memoria, la binaria puede ocupar hasta ~32x menos, a cambio de recall. El rescoring contra los vectores completos te recupera casi todo.
  • Es la jugada cuando pgvector deja de dar abasto: decenas de millones de vectores, QPS muy alto o recuperación con muchos filtros. Por debajo de eso, pgvector da menos trabajo de operar; quédate en lo aburrido hasta que de verdad duela.
  • Auto-hospedarlo de verdad es un solo contenedor, pero ahora cargas con un servicio con estado: en producción los snapshots, el presupuesto de memoria y una API key no son opcionales.

Cuando la recuperación empieza a doler, el síntoma rara vez es solo "la búsqueda va lenta". Es una consulta filtrada que antes tomaba milisegundos y ahora escanea, un corpus que ya no entra cómodo en la misma base que tu tráfico OLTP, o una factura de memoria que sube más rápido que el valor de los resultados. Qdrant responde a una forma muy concreta de ese dolor: un motor vectorial dedicado que puedes auto-hospedar, escrito en Rust, que mantiene la búsqueda filtrada rápida a escala y te deja cambiar recall por memoria de forma deliberada. Vas a terminar sabiendo exactamente cuándo justifica el segundo servicio que te cuesta, cómo levantarlo y consultarlo, y las dos o tres trampas de recall que, sin avisar, dejan tus resultados incorrectos en lugar de solo lentos.

Nota

Si todavía andas por debajo de unos pocos millones de vectores y ya usas Postgres, lo más probable es que esta aún no sea tu herramienta. pgvector mantiene los embeddings en la base que ya corres, respaldas y aseguras: un servicio menos, una credencial menos, una cosa menos que se te puede romper. Pásate a un motor dedicado cuando hayas medido que la opción aburrida no da abasto, no porque un motor dedicado suene más serio.

01 · Qué es

Qdrant es una base de datos vectorial hecha para una sola tarea: guardar vectores de alta dimensión y encontrar rápido los vecinos más cercanos de un vector de consulta, con filtrado. No es una extensión de Postgres ni una base de datos de propósito general. Corre como un solo contenedor, expone una API REST en el puerto 6333 y una API gRPC en el 6334, y guarda sus datos en un volumen que tú montas.

Tres conceptos cubren casi todo lo que vas a hacer:

  • Colecciones, la unidad de almacenamiento, como una tabla. Una colección fija de entrada la dimensión del vector y la métrica de distancia (Cosine, Dot o Euclid).
  • Puntos, un punto es un registro: un id, el vector en sí y un payload (metadatos JSON arbitrarios: tenant, estado, idioma, timestamps, lo que sea por lo que filtres).
  • Filtros, condiciones booleanas sobre el payload («must», «should», «must_not») que se aplican durante el recorrido del índice, no después.

Ese último punto es la razón de fondo para elegir Qdrant en vez de una columna vectorial añadida a la fuerza. El filtrado vive dentro de la ruta de búsqueda, no alrededor de ella.

Consejo

Elige la métrica de distancia que vaya con cómo se entrenó tu modelo de embeddings. La mayoría de los embeddings de texto modernos vienen normalizados y esperan coseno; usar Euclid sobre vectores normalizados suele funcionar igual, pero enturbia las cuentas. Decídelo una vez por colección: no puedes cambiarle la métrica a una colección sin reconstruirla.

02 · Por qué importan los payloads filtrables

La forma ingenua de hacer "documentos similares, pero solo los publicados y en español" es un post-filtro: le pides al índice los top-k vectores más cercanos y luego descartas los que no cumplen. Eso sirve hasta que el filtro es selectivo. Si solo el 2% de tu corpus está publicado y en español, esos top-k más cercanos podrían tener cero coincidencias, así que pides más, y más, y la consulta se degrada hasta quedar muy cerca de un escaneo. El recall se desploma o la latencia se dispara: elige uno.

Qdrant mete el filtro dentro del recorrido HNSW. A medida que recorre el grafo, solo considera puntos cuyo payload cumple el filtro, y mantiene índices de payload para que esas comprobaciones salgan baratas. El resultado es que una consulta muy filtrada se mantiene rápida y correcta, que es justo el caso donde el post-filtrado del lado de Postgres se cae a pedazos.

Esta es la función que debería guiar la decisión. Si tu recuperación es mayormente "busca similares, sin filtro", muchos motores sirven y pgvector es el más barato. Si tu recuperación es "busca similares dentro de este subconjunto", y ese subconjunto es selectivo, filtrar durante el recorrido bien vale un servicio dedicado.

# Un contenedor, REST en 6333, gRPC en 6334. Monta un volumen o pierdes los datos.
docker run -p 6333:6333 -p 6334:6334 \
  -v "$HOME/qdrant_storage:/qdrant/storage" \
  qdrant/qdrant

03 · Quickstart: una colección que sí usarías en serio

Crea una colección con la dimensión y la métrica correctas, agrega un índice de payload sobre el campo por el que filtras y luego haz upsert de los puntos. El índice de payload es la parte que la gente se salta: sin él, tu filtro es correcto pero lento.

import { QdrantClient } from "@qdrant/js-client-rest"

const client = new QdrantClient({
  url: "http://localhost:6333",
  apiKey: process.env.QDRANT_API_KEY, // pon una. ver sección 05.
})

// 1) colección: vectores de 1536 dims, distancia coseno
await client.createCollection("docs", {
  vectors: { size: 1536, distance: "Cosine" },
})

// 2) índice de payload para que los filtros sean rápidos, no solo correctos
await client.createPayloadIndex("docs", {
  field_name: "lang",
  field_schema: "keyword",
})

// 3) upsert de puntos (id + vector + payload)
await client.upsert("docs", {
  points: [
    { id: 1, vector: docVec, payload: { lang: "es", status: "published" } },
  ],
})

// 4) búsqueda filtrada: docs similares que estén publicados Y en español
const hits = await client.search("docs", {
  vector: queryVec,
  filter: {
    must: [
      { key: "lang", match: { value: "es" } },
      { key: "status", match: { value: "published" } },
    ],
  },
  limit: 5,
})

Algunos hábitos que conviene agarrar desde temprano:

  1. Crea índices de payload para cada campo por el que filtres. Un filtro sin índice de payload igual devuelve la respuesta correcta, solo que lento, y lo lento es el modo de falla que se esconde hasta que crece el tráfico.
  2. Agrupa tus upserts en lotes. Insertar los puntos de uno en uno, un request por cada uno, es la razón más común de que un backfill tarde horas en vez de minutos.
  3. Prefiere gRPC para las rutas de lectura con QPS alto. REST sirve para el setup y la administración; gRPC baja notablemente el overhead por request bajo carga.

04 · Cuantización: recuperar memoria

HNSW mantiene los vectores en memoria para ir rápido, y los vectores en precisión completa salen caros. Un millón de vectores float32 de 1536 dims son unos 6 GB solo en los vectores crudos, antes de contar el grafo. En un solo VPS eso se acumula rápido. La cuantización es la palanca de Qdrant para esto:

  • Cuantización escalar guarda cada dimensión como un int8 en lugar de un float32: ocupa unas 4x menos, con una pérdida de recall pequeña y por lo general aceptable.
  • Cuantización binaria guarda cada dimensión como un solo bit: hasta ~32x menos y muchísimo más rápida, pero con un costo de recall real que depende muchísimo de tu modelo de embeddings y de tus datos.

Lo que vuelve usable la cuantización binaria es el rescoring: Qdrant usa los vectores comprimidos para armar rápido un conjunto generoso de candidatos y luego los re-ordena contra los vectores en precisión completa (que guarda en disco) para recuperar exactitud. Con rescoring activado y un factor de oversampling, la cuantización binaria puede sostener el recall sorprendentemente bien, pero "sorprendentemente bien" no es "siempre", y la única forma de saberlo es medirlo con tus propios datos.

await client.createCollection("docs_bq", {
  vectors: { size: 1536, distance: "Cosine" },
  quantization_config: {
    binary: { always_ram: true },
  },
})

// en la consulta: oversample y luego rescoring contra los vectores completos
const hits = await client.search("docs_bq", {
  vector: queryVec,
  limit: 10,
  params: {
    quantization: { rescore: true, oversampling: 2.0 },
  },
})

Atención

La cuantización cambia recall por memoria, y la binaria puede bajarte la exactitud en silencio en consultas difíciles o fuera de distribución. Este es el modo de falla peligroso: el sistema va rápido y devuelve resultados, solo que peores, y nada te avisa. Antes de confiarle producción, mide recall@k contra un baseline sin cuantizar sobre un conjunto reservado de tus consultas, con rescoring activado. Si el recall cae más de lo que puedes tolerar, baja a cuantización escalar o quítala del todo.

05 · Cuándo recurrir a él (y cuándo no)

Elige Qdrant cuando al menos una de estas sea cierta y la hayas medido:

  • Escala. Pasaste lo que Postgres indexa con comodidad: decenas de millones de vectores y subiendo.
  • Latencia bajo carga. Necesitas ANN filtrado con QPS alto y metas de p99 exigentes, y la búsqueda vectorial le pelea recursos al OLTP en el mismo Postgres.
  • Recuperación con muchos filtros. Los filtros de payload selectivos son el centro de tus consultas, y el post-filtrado te está arruinando el recall o la latencia.
  • Presión de memoria. Necesitas cuantización con rescoring para que un corpus grande quepa en la RAM que de verdad tienes.

Quédate con pgvector cuando andes por debajo de unos pocos millones de vectores y ya uses Postgres. Una sola base, un solo backup, un solo conjunto de transacciones, filtros y foreign keys y row-level security todo en la misma consulta: eso es mucha simplicidad operativa para regalarla. Elige la opción aburrida hasta que un benchmark con datos reales diga que ya no da abasto.

Importante

Adoptar Qdrant significa cargar con un servicio con estado. Eso no es gratis: necesita almacenamiento persistente con snapshots (un store vectorial sin backups es un incidente esperando una fecha), un presupuesto de memoria que de verdad hayas dimensionado y una API key. Qdrant viene sin autenticación por defecto, así que un puerto expuesto es una base de datos abierta. En un solo VPS detrás de Traefik, déjalo en la red interna, exige API key y nunca publiques el 6333 al mundo.

06 · Concesiones, sin rodeos

Qdrant es excelente en lo que hace, y lo que hace es bien acotado. No es tu base de datos principal: no hay joins, no hay transacciones entre colecciones, no hay integridad relacional. Los embeddings viven en Qdrant; las filas que describen siguen viviendo en Postgres, lo que significa que ahora cargas con la consistencia entre ambos: una ruta de sincronización, un plan de reindexado para cuando cambies de modelo de embeddings y otro para los borrados. Ese acoplamiento es el costo real de un motor dedicado, y por eso el argumento de tenerlo todo junto en pgvector pesa tanto por debajo de cierta escala.

La otra nota honesta es la gravedad operativa. "Un contenedor" es cierto el día uno. Para el mes tres ya tienes snapshots que programar, memoria que vigilar, upgrades de versión que probar y una config de cuantización que afinaste para un corpus que desde entonces se movió. Nada de esto es difícil, pero ahora es tuyo, y es trabajo que la opción aburrida nunca te pidió. Adopta Qdrant cuando la ganancia medida supere ese impuesto continuo, y ni un día antes.

Si estás chocando contra escala real, filtrado pesado o muros de memoria, Qdrant es una de las respuestas auto-hospedables más limpias que hay, y su núcleo en Rust hace que se mantenga rápido y predecible bajo carga. Pero es un salto que te ganas, no un default. Pásate a él cuando tengas evidencia de que la opción aburrida ya no da abasto; entonces mide el recall con tus propios datos antes de confiar en la cuantización, asegura la API y programa los snapshots desde el día uno.

Puntos clave

  • Qdrant es un motor vectorial en Rust que hace bien una sola cosa: búsqueda de vecinos más cercanos rápida y filtrada a escala, en un solo contenedor que habla REST y gRPC.
  • Su función decisiva es filtrar dentro del recorrido HNSW: los filtros de payload selectivos se mantienen rápidos y correctos, justo donde el post-filtrado del lado de Postgres se cae.
  • La cuantización te recupera memoria (escalar ~4x, binaria ~32x) a cambio de recall. Haz siempre rescoring contra los vectores completos, y mide siempre el recall con tus propios datos antes de confiar.
  • Es un salto que te ganas frente a pgvector, no un default. Pásate cuando un benchmark con datos reales, escala, QPS, recuperación con muchos filtros o muros de memoria, diga que la opción aburrida ya no da abasto.
  • Ahora cargas con un servicio con estado: pon una API key (viene sin auth por defecto), programa snapshots, dimensiona la memoria y mantén la consistencia entre Qdrant y tu fuente de verdad.

Preguntas frecuentes

Estoy en pgvector y funciona. ¿Cuándo me paso de verdad a Qdrant?

Cuando un benchmark con datos y tráfico reales diga que pgvector ya no da abasto, no antes. Señales concretas: pasaste de las decenas de millones de vectores y el recall o la latencia se te van incluso con índices afinados; tu búsqueda vectorial le pelea recursos al OLTP en el mismo Postgres y no puedes separarlos; hay filtros de payload selectivos que están arruinando el recall por culpa del post-filtrado; o necesitas cuantización con rescoring para que tu corpus quepa en RAM. "Lo medí y no da abasto" es la razón correcta. "Un motor dedicado suena más serio" no lo es.

¿Por qué el filtrado de Qdrant le gana a simplemente agregarle un WHERE a una búsqueda vectorial?

Está en dónde corre el filtro. Un post-filtro (el clásico WHERE) primero busca los top-k vectores más cercanos y después descarta los que no cumplen, y eso colapsa cuando el filtro es selectivo, porque los más cercanos pueden tener pocas coincidencias o ninguna, y te toca pedir de más hasta quedar muy cerca de un escaneo. Qdrant aplica el filtro dentro del recorrido HNSW, apoyado en índices de payload, así que solo avanza hacia puntos que ya cumplen. La recuperación selectiva y con muchos filtros es justo donde esta diferencia se vuelve decisiva.

¿Es seguro usar cuantización binaria, o me va a arruinar los resultados?

Depende por completo de tu modelo de embeddings y de tus datos, y la única forma de saberlo es midiendo. La binaria puede ocupar ~32x menos y ser mucho más rápida, pero el costo de recall es real y desparejo entre consultas. El rescoring, usar los vectores comprimidos para juntar candidatos y luego re-ordenarlos contra los de precisión completa, recupera casi toda la exactitud y es lo que la vuelve usable. Antes de confiar en ella, mide recall@k contra un baseline sin cuantizar sobre tus propias consultas reservadas, con rescoring activado. Si el recall cae demasiado, baja a cuantización escalar (unas 4x, más suave) o quítala del todo.

¿Qdrant reemplaza mi base de datos? ¿Dónde viven en realidad los documentos?

No. Qdrant es un índice de búsqueda, no tu fuente de verdad. Guarda vectores más un payload de metadatos por los que filtras; las filas canónicas, los documentos, los usuarios, las órdenes, se quedan en Postgres. Eso significa que cargas con la consistencia entre ambos: una ruta de sincronización para inserts y borrados, y un plan de reindexado para cuando cambies de modelo de embeddings. Mucha gente guarda en Qdrant solo el payload justo para filtrar e identificar, y después trae los registros completos desde Postgres por id. Ese acoplamiento es el costo continuo real de correr un motor dedicado.

¿Cuál es el error de seguridad típico al auto-hospedar Qdrant?

Exponerlo sin autenticación. Qdrant viene sin auth por defecto, así que un puerto 6333 publicado es una base de datos abierta y sin autenticar que cualquiera puede leer, escribir y borrar. En un solo VPS detrás de un reverse proxy como Traefik, deja Qdrant en la red interna de Docker, configúrale una API key en el servicio y nunca ates el puerto a una interfaz pública. Trata los snapshots como no opcionales también: un store vectorial sin backups es nada más un incidente esperando una fecha.

REST o gRPC: ¿cuál de las dos APIs debo usar?

Usa REST para el setup, la administración y todo donde la comodidad le gane al throughput puro: se presta para hacer un curl y depurar. Usa gRPC para las rutas de lectura con QPS alto en producción, donde su menor overhead por request se nota bajo carga. La mayoría de las apps terminan usando las dos: REST para la gestión de colecciones y los scripts de ingesta, gRPC para la ruta caliente de búsqueda en la que el usuario está esperando. Los clientes oficiales soportan ambas, así que es una decisión por llamada, no un lock-in.

Abrir recurso (abre en pestaña nueva)

¿Prefieres que lo hagamos por ti?

Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.

Escríbenos por WhatsApp

Escríbenos por WhatsApp

Escanéalo con tu teléfono para escribirnos por WhatsApp.

Escanéalo con tu teléfono para escribirnos por WhatsApp.

¿Estás desde el teléfono y no puedes escanear? Escríbenos a info@ilustrari.com

Primera conversación gratis. Te responde el fundador.

Recursos relacionados

ToolPostgres

pgvector: vectores dentro de Postgres

La mayoría monta una base vectorial dedicada antes de tener el problema que esta resuelve. pgvector le suma un tipo vector e índices ANN al Postgres que ya tienes corriendo, así la búsqueda semántica vive junto a tus datos relacionales: una sola base, un solo backup, un solo conjunto de transacciones. Es el default pragmático para RAG, y aquí te explico cuándo es la decisión correcta, cómo montarlo en Supabase y los detalles de índice que, sin avisar, convierten consultas rápidas en escaneos completos.

9 abr 202612 min de lectura
Plugin / MCPMCP

MCP de búsqueda web para respuestas con fundamento

Dale a Claude dos tools, buscar en la web y traer una página convertida en texto limpio, para que sus respuestas lleguen con fuentes y no con suposiciones dichas con mucha seguridad. Después arma el cableado de modo que las páginas no confiables que lee jamás puedan tocar una tool que cambie algo.

14 may 202612 min de lectura
ToolOllama

Ollama: modelos locales con un comando

Ollama baja modelos de pesos abiertos cuantizados y los sirve en localhost detrás de una API REST limpia y un endpoint compatible con OpenAI. Así clasificar, enrutar y redactar te sale a costo marginal cero y sin que los datos salgan de la máquina. Acá va qué es, cuándo conviene un modelo local en lugar de uno de frontera, un quickstart listo para pegar y las concesiones sin maquillaje, incluyendo las cuentas de RAM que deciden si tu VPS termina muriéndose a fuerza de swap.

8 abr 202611 min de lectura