Módulo 4: ChromaDB Setup y Configuración
Cápsula 06: Query Optimization — donde se gana o se pierde la latencia real
Descripción de la cápsula
En las cápsulas anteriores aprendiste a insertar datos en ChromaDB de forma eficiente y a filtrar por metadata. Ahora viene la otra mitad de la ecuación: cómo hacer que las queries sean rápidas, predecibles y de calidad consistente.
La latencia de query no es un solo número. Es una distribución estadística — la mayoría de queries son rápidas, pero un porcentaje pequeño es muy lento. Si reportás solo el promedio, escondes el problema. Si solo optimizas el promedio, el peor 5% de tus usuarios sufre. Esta cápsula te enseña a medir correctamente (p50/p95/p99), a entender qué parámetros mueven la aguja en cada percentil, y a optimizar con criterio en vez de copiar configuraciones de tutoriales.
Cuando termines vas a saber por qué tu query rápida en desarrollo se vuelve lenta en producción, qué subir y qué bajar para llegar a un SLA específico, y qué trampas convierten "optimización" en "regresión silenciosa".
Al finalizar esta cápsula serás capaz de:
- ✅ Medir latencia correctamente con percentiles p50/p95/p99 y entender qué te dice cada uno
- ✅ Identificar los cuatro parámetros que mueven la latencia:
n_results, metadata filtering,ef_search, tamaño del dataset - ✅ Calcular el sweet spot de
n_resultssegún tu caso (RAG vs search) - ✅ Configurar
ef_search(HNSW search-time parameter) para balancear latencia vs accuracy - ✅ Diseñar un benchmark reproducible que valide cambios antes de deployar
- ✅ Anticipar cómo la latencia cambia cuando el dataset crece de 10K a 1M vectores
Tiempo estimado: 35-45 minutos
Por qué medir el promedio te miente
La pregunta "¿cuánto tarda un query?" tiene tres respuestas distintas y cada una cuenta una historia:
- p50 (mediana): la latencia en el caso típico. La mitad de los queries son más rápidos que esto.
- p95: la latencia del peor 5% de los queries. Lo que sufre 1 de cada 20 usuarios.
- p99: la latencia del peor 1% de los queries. Tail latency — el caso patológico que define tu SLA.
Imagina dos sistemas con el mismo promedio de 50ms:
Sistema A: latencias = [40, 45, 48, 50, 52, 55, 60] ms
promedio = 50ms, p50 = 50ms, p99 = 60ms
→ Distribución estable, predecible
Sistema B: latencias = [10, 15, 20, 25, 30, 50, 200] ms
promedio = 50ms, p50 = 25ms, p99 = 200ms
→ La mitad rapidísimo, pero el 1% es 4x más lento
Si tu SLA dice "p95 < 100ms", el sistema B falla mientras el sistema A pasa cómodo. Si miraras solo el promedio, los considerarías equivalentes.
Por qué esto importa especialmente en vector search: HNSW tiene latencia variable. La mayoría de queries navega el grafo eficientemente, pero algunas requieren más backtracking y pueden ser 5-10x más lentas. El promedio esconde el caso patológico.
Medir correctamente
# benchmark_query_latency.py
import chromadb
from chromadb.utils import embedding_functions
import os
import time
import statistics
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
model_name="text-embedding-3-small"
)
client = chromadb.PersistentClient(path="./chroma_query_bench")
collection = client.get_or_create_collection(
name="bench",
embedding_function=openai_ef
)
# Asegúrate de tener data (ej: 10K docs ingeridos previamente)
print(f"Collection size: {collection.count()}")
# Queries variadas (simulan tráfico real)
queries = [
"How do I configure HNSW for production?",
"What is the difference between cosine and L2 distance?",
"How does batch ingestion work in ChromaDB?",
# ... idealmente 50-100 queries reales o sintéticas
] * 25 # repetir para tener 75 mediciones
# Warm-up: las primeras queries son más lentas (cold cache)
for q in queries[:10]:
collection.query(query_texts=[q], n_results=10)
# Medición real
latencies = []
for query in queries:
start = time.perf_counter()
collection.query(query_texts=[query], n_results=10)
elapsed_ms = (time.perf_counter() - start) * 1000
latencies.append(elapsed_ms)
# Percentiles
latencies.sort()
n = len(latencies)
p50 = latencies[n // 2]
p95 = latencies[int(n * 0.95)]
p99 = latencies[int(n * 0.99)]
mean = statistics.mean(latencies)
stdev = statistics.stdev(latencies)
print(f"\nLatency benchmark ({n} queries):")
print(f" Mean: {mean:.1f}ms ± {stdev:.1f}ms")
print(f" p50: {p50:.1f}ms")
print(f" p95: {p95:.1f}ms")
print(f" p99: {p99:.1f}ms")
print(f" Worst: {latencies[-1]:.1f}ms")
Output típico (10K docs, OpenAI embeddings):
Latency benchmark (75 queries):
Mean: 142.3ms ± 38.5ms
p50: 128.5ms
p95: 198.2ms
p99: 245.7ms
Worst: 287.0ms
Lectura: el promedio (142ms) no es lo que ves típicamente — el p50 dice que el caso típico es 128ms. El p99 (245ms) es casi 2x el p50, normal para HNSW. Si tu SLA es "p95 < 200ms", estás en el límite — un poco más de carga y empiezas a fallar SLA.
Nota crítica: la latencia incluye el tiempo de generar el embedding de la query con OpenAI (~80-150ms en este ejemplo). Si querés medir solo la búsqueda en ChromaDB, debes pre-computar los embeddings y pasar query_embeddings= en lugar de query_texts=.
Los cuatro parámetros que mueven la latencia
Parámetro 1: n_results (top-K)
Cuántos resultados pedís. Más resultados = más trabajo de HNSW + más datos a serializar.
# Benchmark: latencia vs n_results
for n in [1, 5, 10, 20, 50, 100]:
latencies = []
for q in queries:
start = time.perf_counter()
collection.query(query_embeddings=[pre_computed_embedding], n_results=n)
latencies.append((time.perf_counter() - start) * 1000)
p95 = sorted(latencies)[int(len(latencies) * 0.95)]
print(f"n_results={n:3d}: p95 = {p95:.1f}ms")
Output típico:
n_results= 1: p95 = 12.3ms
n_results= 5: p95 = 14.8ms
n_results= 10: p95 = 17.2ms
n_results= 20: p95 = 24.1ms
n_results= 50: p95 = 38.6ms
n_results=100: p95 = 71.4ms
Patrón: la latencia crece sub-lineal con n_results para valores razonables (1-20), y empeora rápido para valores grandes (50+).
Recomendación según caso:
| Caso de uso | n_results típico |
|---|---|
| RAG con LLM (passing context al modelo) | 3-10 |
| Search interfaces (mostrar resultados al usuario) | 10-20 |
| Re-ranking pipeline (recuperar mucho, filtrar después) | 50-100 |
| Recommendations (multi-criterio) | 20-50 |
Trampa común: "más resultados = mejor por si acaso". Falso. En RAG, pasar 50 chunks al LLM (a) sube el costo de generation 5x, (b) mete información irrelevante en el contexto que distrae al modelo (lost in the middle), (c) duplica el tiempo de retrieval. El sweet spot para la mayoría de RAG es n_results=5.
Parámetro 2: metadata filtering (where clauses)
Como vimos en M04/04, filtrar por metadata reduce el espacio de búsqueda y acelera queries. Pero el efecto depende del tipo de filter y de cuánto filtra.
# Sin filter (busca en 10K docs)
no_filter = collection.query(query_embeddings=[emb], n_results=10)
# Filter agresivo (reduce a ~500 docs candidatos)
heavy_filter = collection.query(
query_embeddings=[emb],
n_results=10,
where={"category": "support"} # asume que 5% de docs son "support"
)
# Filter muy agresivo (reduce a ~50 docs)
very_heavy_filter = collection.query(
query_embeddings=[emb],
n_results=10,
where={"category": "support", "language": "es", "version": "2.3"}
)
Resultado típico:
| Filter | Search space | p95 |
|---|---|---|
| Sin filter | 10,000 | 17ms |
category="support" | ~500 | 6ms |
category="support" AND language="es" AND version="2.3" | ~50 | 3ms |
Pero hay un caso patológico: si tu filter es demasiado restrictivo y deja menos candidatos que n_results, la query vuelve más lenta:
# Filter que deja solo 5 candidatos, pero pides n_results=10
result = collection.query(
query_embeddings=[emb],
n_results=10,
where={"super_specific_id": "xyz123"} # solo 1 doc match
)
# HNSW tiene que escanear todo el espacio para encontrar suficientes matches
# que cumplan el filter → puede ser MÁS LENTO que sin filter
Regla: el filter debe dejar al menos 5-10x el n_results solicitado para que HNSW funcione eficientemente. Si tu filter es ultra-específico, considera get() con filter directo en vez de query().
Parámetro 3: ef_search (HNSW runtime parameter)
ef_search controla cuántos nodos del grafo HNSW visita cada query. Más alto = busca más exhaustivo = mejor recall = más lento.
# Configurar ef_search en runtime (modifica accuracy/latency dinámicamente)
collection_with_high_ef = client.get_or_create_collection(
name="high_ef_collection",
embedding_function=openai_ef,
metadata={
"hnsw:space": "cosine",
"hnsw:search_ef": 200 # default es 10 — mucho más exhaustivo
}
)
# Comparar
configs = [
("ef_search=10 (default)", 10),
("ef_search=50", 50),
("ef_search=100", 100),
("ef_search=200", 200),
]
for name, ef in configs:
coll = client.get_or_create_collection(
name=f"bench_ef_{ef}",
embedding_function=openai_ef,
metadata={"hnsw:space": "cosine", "hnsw:search_ef": ef}
)
# Asume datos ya cargados
latencies = [time_query(coll, q) for q in queries]
recall = measure_recall(coll, eval_set) # función de evaluación
p95 = sorted(latencies)[int(len(latencies) * 0.95)]
print(f"{name}: p95={p95:.1f}ms, recall@10={recall:.2%}")
Output típico:
ef_search=10 (default): p95=8.2ms, recall@10=88%
ef_search=50: p95=15.6ms, recall@10=94%
ef_search=100: p95=24.3ms, recall@10=97%
ef_search=200: p95=41.5ms, recall@10=99%
Patrón: ef_search tiene retornos decrecientes. De 10 → 50 mejora recall 6%; de 100 → 200 mejora solo 2% pero duplica la latencia.
Recomendación:
- Producción típica:
ef_search=50-100. Sweet spot accuracy/latencia. - Demo / prototipo: default (10) está bien.
- Recall crítico (médico, legal): 200+, justificar latencia.
Parámetro 4: tamaño del dataset
Lo último que controlas — y lo más importante a largo plazo. La latencia de HNSW crece logarítmica con el número de vectores, no lineal. Eso es bueno (10x docs ≠ 10x latencia), pero no es gratis.
| Dataset size | p95 típico (sin filter, ef_search=10) |
|---|---|
| 1,000 | 2-5ms |
| 10,000 | 8-15ms |
| 100,000 | 18-35ms |
| 1,000,000 | 40-80ms |
| 10,000,000 | 80-150ms |
Implicación operativa: si construyes RAG hoy con 10K docs y proyectas crecer a 1M en 6 meses, la latencia base subirá de 10ms a 50ms. Eso puede romper tu SLA. Anticiparlo significa:
- Diseñar SLA con margen para crecimiento (si target final es <100ms con 1M, define <50ms con 100K para tener margen).
- Considerar migración a vector DBs distribuidas (Pinecone, Weaviate cluster) cuando crucés 5-10M vectores.
Optimización: el orden correcto
No optimices a ciegas. La secuencia que da más rendimiento por menos esfuerzo:
1. Primero, mide
Antes de tocar nada, ejecuta el benchmark de latencia y recall sobre tu dataset real. Sin baseline, no podés saber si "optimizaste" o "regresionaste".
2. Después, baja n_results al mínimo necesario
Casi siempre hay margen aquí. Si tu RAG usa n_results=20 por inercia, probá con 5. Si la calidad de respuesta no baja, ya ganaste 30-50% de latencia gratis.
3. Después, agrega metadata filtering
Si tienes filters que filtran al 10-30% del dataset, aplícalos. Eso reduce el espacio de búsqueda dramáticamente. Si no tenés metadata útil, considera agregarla (categoría, fecha, fuente) — es trabajo de ingestion pero ahorra latencia para siempre.
4. Después, ajusta ef_search
Si necesitas más recall (queries fallan en encontrar docs relevantes que sabés que existen), sube ef_search. Si la latencia es el problema, bájalo. Mide ambas métricas — accuracy Y latencia — antes y después.
5. Si todo lo anterior no alcanza, considera hardware o vector DB distinta
- Más RAM permite cargar el índice HNSW completo en memoria (vs paginar a disco).
- SSD vs HDD hace diferencia notable para datasets grandes.
- Si superás 10M vectores y la latencia importa, ChromaDB no va a escalar bien. Migración a Pinecone o Qdrant distribuido.
Trampas y errores comunes
Trampa 1: medir sin warm-up
El error: primer benchmark, primera query incluye el costo de cargar el índice HNSW del disco a RAM. La medición es 5-10x más lenta que el estado estable.
Síntoma: latencias del primer benchmark mucho más altas que el segundo.
Cómo prevenir: ejecutar 5-10 queries de "warm-up" antes de empezar a medir. Esas queries no se cuentan.
# Warm-up
for q in queries[:10]:
collection.query(query_texts=[q], n_results=10)
# Medición real (descartando warm-up)
latencies = []
for q in queries:
start = time.perf_counter()
collection.query(query_texts=[q], n_results=10)
latencies.append((time.perf_counter() - start) * 1000)
Trampa 2: mezclar latencia de embedding con latencia de búsqueda
El error: medís collection.query(query_texts=["..."]) y ves p95=180ms. Asumís que ChromaDB es lento.
Síntoma: crees que la búsqueda en ChromaDB tarda 180ms, cuando en realidad ChromaDB tarda 15ms y los otros 165ms son de la API call de OpenAI para embebir la query.
Cómo prevenir: separar las dos mediciones.
# Pre-computar el embedding una sola vez
query_embedding = openai_client.embeddings.create(
model="text-embedding-3-small",
input=query_text
).data[0].embedding
# Medir solo el costo de ChromaDB
start = time.perf_counter()
collection.query(query_embeddings=[query_embedding], n_results=10)
chromadb_latency = (time.perf_counter() - start) * 1000
Trampa 3: optimizar latencia rompiendo recall
El error: bajás ef_search agresivamente para mejorar p95, sin medir el impacto en accuracy.
Síntoma: p95 mejora de 30ms a 8ms 🎉. Pero recall cae de 95% a 78% — el sistema responde rápido con resultados peores. Los usuarios reportan "el bot no encuentra cosas que sé que están".
Cómo prevenir: siempre medir las dos métricas juntas en un eval set. La optimización válida es la que mejora una sin destruir la otra.
# Eval set: queries con docs relevantes etiquetados
EVAL_SET = [
{"query": "How to configure HNSW?", "relevant_doc_ids": ["doc_42", "doc_87"]},
# ... 30+ entries
]
def measure_recall_at_k(collection, eval_set, k=10):
hits = 0
total = 0
for item in eval_set:
results = collection.query(query_texts=[item["query"]], n_results=k)
retrieved_ids = set(results['ids'][0])
relevant_ids = set(item["relevant_doc_ids"])
if retrieved_ids & relevant_ids:
hits += len(retrieved_ids & relevant_ids)
total += len(relevant_ids)
return hits / total
# Antes y después de cada cambio
print(f"Latency p95: {p95:.1f}ms")
print(f"Recall@10: {measure_recall_at_k(collection, EVAL_SET):.2%}")
Trampa 4: filter ultra-específico vuelve query más lenta
El error: asumes que más filter = más rápido. Aplicas un filter que deja solo 1-2 candidatos válidos.
Síntoma: queries con filter ultra-específico son más lentas que sin filter, contra-intuitivamente.
Por qué pasa: HNSW tiene que escanear más nodos para encontrar suficientes que cumplan el filter (target = n_results).
Cómo prevenir: si tu filter deja menos candidatos que n_results × 5, considera usar get() con filter directo:
# En vez de query con filter ultra-específico
results = collection.query(
query_embeddings=[emb],
n_results=10,
where={"unique_field": "specific_value"}
)
# Mejor: get directo si solo hay pocos matches
results = collection.get(
where={"unique_field": "specific_value"},
include=['documents', 'metadatas']
)
# No es semantic search, pero es mucho más rápido cuando el filter es muy restrictivo
Trampa 5: optimizar el promedio sin mirar las colas
El error: reportás "p50 mejoró 20%, deploy". Pero no miraste p99.
Síntoma: p50 baja de 50ms a 40ms. Pero p99 sube de 200ms a 800ms. El 1% de tus usuarios está sufriendo 4x más, y el promedio lo esconde.
Cómo prevenir: siempre reportar al menos p50, p95, p99. El cambio se valida si todos mejoran o se mantienen. Si alguno regresiona, revertir.
Trampa 6: benchmarks irreproducibles
El error: corres un benchmark, anotas el resultado, cambias algo, corres de nuevo. Las dos mediciones tienen runs distintos, queries distintos, dataset distinto.
Síntoma: "creo que mejoró pero no estoy seguro". Decisiones basadas en intuición.
Cómo prevenir:
- Mismo eval set fijo en cada benchmark.
- Mismo dataset (snapshotear si es necesario).
- Misma máquina, mismo nivel de carga (no mientras corre algo más).
- Múltiples ejecuciones (3-5 runs), reporta mediana de las medianas.
def run_benchmark_n_times(collection, queries, n_runs=3):
"""Corre el benchmark N veces y reporta estadísticas robustas."""
all_p50, all_p95, all_p99 = [], [], []
for run in range(n_runs):
latencies = sorted([time_query(collection, q) for q in queries])
all_p50.append(latencies[len(latencies) // 2])
all_p95.append(latencies[int(len(latencies) * 0.95)])
all_p99.append(latencies[int(len(latencies) * 0.99)])
print(f"p50: {statistics.median(all_p50):.1f}ms (variación {min(all_p50):.0f}-{max(all_p50):.0f})")
print(f"p95: {statistics.median(all_p95):.1f}ms (variación {min(all_p95):.0f}-{max(all_p95):.0f})")
print(f"p99: {statistics.median(all_p99):.1f}ms (variación {min(all_p99):.0f}-{max(all_p99):.0f})")
Ejercicio aplicado
Escenario: te llaman de un equipo que opera un RAG en producción. Síntomas:
- p50 actual: 180ms (aceptable)
- p95 actual: 450ms (rompe SLA de 300ms)
- p99 actual: 850ms (terrible)
- Configuración actual: ChromaDB con 250K vectores, OpenAI embeddings,
n_results=15, sin metadata filtering,ef_searchdefault - El equipo dice "necesitamos vector DB más rápida, hay que migrar a Pinecone"
Pregunta: sin migrar de DB, propón tres optimizaciones específicas que probarías en orden, justificá el impacto esperado de cada una, y qué medirías para validarlas.
Solución
Análisis del problema:
Antes de proponer optimizaciones, identificar qué causa la latencia alta. Los sospechosos:
- Overhead de OpenAI embedding de la query: 80-150ms del p50 son probablemente esto. Si lo verificás (separando query embedding de search), confirmás que ChromaDB en sí tarda ~30-80ms.
n_results=15es alto para RAG típico — sobre todo si después se pasa al LLM, donde 5-7 chunks suele bastar.- Sin metadata filtering: busca en los 250K vectores siempre.
ef_searchdefault (10): rápido pero recall puede ser pobre, y no afecta directamente al p95 actual a no ser que sea uno de los factores secundarios.
Optimización 1: bajar n_results de 15 a 5
Hipótesis: la mayoría de RAG no necesita 15 chunks. Bajar a 5 reduce latencia ~30% (basado en el patrón observado en benchmarks).
Impacto esperado:
- p50: 180ms → ~140ms
- p95: 450ms → ~320ms
- Costo de validar: 30 minutos. Probarlo en eval set, medir recall@5 vs recall@15. Si el recall no cae más de 3-5%, deployar.
Riesgo: algunos queries necesitan más contexto. Mitigar con re-ranking o expansion del top-k solo cuando el primer pase no encuentra suficiente.
Métrica a validar:
- p50/p95/p99 antes y después
- Recall@5 sobre eval set (debe mantenerse >90% del recall@15 actual)
- Calidad de respuestas del LLM sobre 50 queries de eval (subjetiva, comparación A/B)
Optimización 2: agregar metadata filtering
Hipótesis: si los queries tienen contexto identificable (tipo de documento, idioma, departamento), filtrar reduce el espacio de búsqueda 5-20x.
Implementación:
- Inventariar qué metadata tienen los chunks actualmente. Si no tienen suficiente, agregar (re-ingest con
category,source_type,language,date_range). - Identificar queries del log de producción y agruparlos por intent (¿son "soporte"? "facturación"? "general"?). Si hay clasificación obvia, filtrar.
- Pre-clasificar queries con un modelo simple (incluso un LLM con prompt corto) para asignar
categoryantes del retrieval.
Impacto esperado (si el filter reduce a 30-50K candidatos, ~20% del dataset):
- p50: 140ms → ~110ms
- p95: 320ms → ~220ms (entra al SLA de 300ms)
- p99: 850ms → ~400ms
Métrica a validar:
- p50/p95/p99 con y sin filter, sobre el mismo eval set
- Recall debería mejorar o mantenerse: filtrar bien quita ruido
- % de queries que pueden ser filtradas (cobertura)
Optimización 3: ajustar ef_search
Hipótesis: si después de las dos optimizaciones anteriores la latencia es buena pero el recall mediocre, subir ef_search puede mejorarlo. Si la latencia sigue alta y el recall sobra, bajarlo puede dar margen.
Cómo decidir:
- Si recall@5 actual > 92%: dataset rico, bajar
ef_searchpuede sacar un poco más de latencia sin tocar calidad. - Si recall@5 actual < 85%: subir
ef_searcha 50-100 mejora calidad a costa de latencia (que ya es buena después de optimizaciones 1-2).
Métrica a validar:
- Pareto plot: latencia (eje X) vs recall (eje Y) con distintos
ef_search. Elegir el punto que cumple SLA con mejor recall.
Resumen del plan:
| Optimización | Esfuerzo | Riesgo | Impacto p95 esperado |
|---|---|---|---|
| 1. n_results 15→5 | 30 min | Bajo (medible en eval) | -30% |
| 2. Metadata filtering | 4-8 hrs (ingest + clasificación) | Medio (requiere metadata) | -30% adicional |
| 3. Ajustar ef_search | 1 hr | Bajo (reversible) | ±10% (depende de dirección) |
Total esperado: p95 de 450ms → 200-250ms. Dentro del SLA sin migrar.
Si después de las 3 optimizaciones aún no llega:
- Verificar overhead real de OpenAI embedding (probablemente 80-120ms del p95). Si es eso, considerar cache de query embeddings (LRU sobre las queries más comunes).
- Considerar embeddings locales con GPU para queries (latencia más baja y predecible que API).
- Solo entonces evaluar migración a otra DB.
Argumento al equipo: "Migrar a Pinecone es proyecto de 4-6 semanas con costo recurrente de $$/mes. Las tres optimizaciones que propongo son de 1-2 semanas con costo cero. Probemos primero. Si no llegamos al SLA, la migración queda como Plan B con datos para justificarla."
Resumen y siguiente paso
Lo que aprendiste:
- Medir latencia con percentiles (p50/p95/p99), no promedios. El promedio esconde tail latency, que es lo que define tu SLA.
- Cuatro parámetros mueven la latencia de query:
n_results, metadata filtering,ef_searchy tamaño del dataset. - Optimizar en orden: primero bajar
n_resultsal mínimo necesario, después agregar metadata filtering, después ajustaref_search, último cambio de hardware o DB. - Siempre medir latencia y recall juntos. Optimizar uno destruyendo el otro no es optimización.
- El warm-up importa. Las primeras 5-10 queries de cualquier benchmark son outliers y no deben contarse.
- Filter ultra-específico puede ser más lento que sin filter — si deja menos candidatos que
n_results × 5, usarget()directo en vez dequery().
Checkpoint: antes de avanzar, deberías poder:
- Diseñar un benchmark reproducible que reporte p50/p95/p99 sobre tu dataset.
- Justificar la elección de
n_resultspara un caso (RAG vs search vs re-ranking). - Diagnosticar latencia alta en orden (n_results → filtering → ef_search → hardware) sin saltar a "necesito otra DB".
Siguiente cápsula: 07 — Persistence y durability.
Acabas de aprender a hacer queries rápidas. Pero ¿qué pasa con tus datos cuando reiniciás el proceso? ¿Qué pasa si la máquina crashea durante un write? ¿Cómo hacés backups y restoreás sin perder vectores? La cápsula 07 cubre persistence y durability — los aspectos operacionales que distinguen un prototipo (que se borra al reiniciar) de un sistema confiable.
Recursos
- ChromaDB — Querying Collections — Referencia de query y parámetros
- HNSW Algorithm Paper — Malkov & Yashunin — Sección 4.2 explica
ef_searchen profundidad - Latency vs Throughput in Vector Databases (Pinecone Blog) — Análisis de patterns de latencia
- Lost in the Middle: How Language Models Use Long Contexts — Por qué bajar n_results en RAG mejora calidad
- Percentiles: Why Averages Lie (Brendan Gregg) — Sobre la importancia de p99 en sistemas
- hyperfine — Command-line Benchmarking Tool — Referencia para benchmarks rigurosos
Tiempo estimado: 35-45 minutos Siguiente: 07-persistence-durability.md