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_results segú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 uson_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:

FilterSearch spacep95
Sin filter10,00017ms
category="support"~5006ms
category="support" AND language="es" AND version="2.3"~503ms

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 sizep95 típico (sin filter, ef_search=10)
1,0002-5ms
10,0008-15ms
100,00018-35ms
1,000,00040-80ms
10,000,00080-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_search default
  • 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:

  1. 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.
  2. n_results=15 es alto para RAG típico — sobre todo si después se pasa al LLM, donde 5-7 chunks suele bastar.
  3. Sin metadata filtering: busca en los 250K vectores siempre.
  4. ef_search default (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:

  1. Inventariar qué metadata tienen los chunks actualmente. Si no tienen suficiente, agregar (re-ingest con category, source_type, language, date_range).
  2. Identificar queries del log de producción y agruparlos por intent (¿son "soporte"? "facturación"? "general"?). Si hay clasificación obvia, filtrar.
  3. Pre-clasificar queries con un modelo simple (incluso un LLM con prompt corto) para asignar category antes 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_search puede sacar un poco más de latencia sin tocar calidad.
  • Si recall@5 actual < 85%: subir ef_search a 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ónEsfuerzoRiesgoImpacto p95 esperado
1. n_results 15→530 minBajo (medible en eval)-30%
2. Metadata filtering4-8 hrs (ingest + clasificación)Medio (requiere metadata)-30% adicional
3. Ajustar ef_search1 hrBajo (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_search y tamaño del dataset.
  • Optimizar en orden: primero bajar n_results al mínimo necesario, después agregar metadata filtering, después ajustar ef_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, usar get() directo en vez de query().

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_results para 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

  1. ChromaDB — Querying Collections — Referencia de query y parámetros
  2. HNSW Algorithm Paper — Malkov & Yashunin — Sección 4.2 explica ef_search en profundidad
  3. Latency vs Throughput in Vector Databases (Pinecone Blog) — Análisis de patterns de latencia
  4. Lost in the Middle: How Language Models Use Long Contexts — Por qué bajar n_results en RAG mejora calidad
  5. Percentiles: Why Averages Lie (Brendan Gregg) — Sobre la importancia de p99 en sistemas
  6. hyperfine — Command-line Benchmarking Tool — Referencia para benchmarks rigurosos

Tiempo estimado: 35-45 minutos Siguiente: 07-persistence-durability.md