{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "46e94147-fc78-4423-8142-024587261562",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "7edb270b",
   "metadata": {},
   "source": [
    "# Comparativa: almacenamiento y recuperación local de embeddings\n",
    "\n",
    "Buscamos el equivalente a SQLite/DuckDB para vectores: **embebido, sin servidor, archivo local**.\n",
    "\n",
    "Candidatos:\n",
    "- **FAISS** — Meta, gold standard ANN, sin metadata nativa\n",
    "- **sqlite-vec** — Extensión SQLite pura en C, vectores junto a datos SQL\n",
    "- **LanceDB** — DB columnar para vectores, persiste en directorio\n",
    "- **ChromaDB** — Popular, metadata rica, modo local\n",
    "- **USearch** — Ultra ligero, rivaliza FAISS en velocidad\n",
    "\n",
    "Medimos: inserción, búsqueda top-10, persistencia a disco, carga desde disco, tamaño en disco."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8a2144f",
   "metadata": {},
   "source": [
    "## 1. Setup y corpus de embeddings pre-generados"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "36e79f24",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dimensión: 384\n",
      "Tamaños de corpus: [1000, 10000, 50000]\n",
      "Queries: 100\n",
      "Embeddings pre-generados (normalizados, float32)\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import time, os, shutil, json\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.style.use('seaborn-v0_8-whitegrid')\n",
    "\n",
    "# Generamos embeddings sintéticos (dim=384 como e5-small) para no depender del modelo\n",
    "# Esto aísla el benchmark de storage del benchmark de encoding\n",
    "np.random.seed(42)\n",
    "\n",
    "SIZES = [1_000, 10_000, 50_000]\n",
    "DIM = 384\n",
    "DATA_DIR = 'data/vector_bench'\n",
    "os.makedirs(DATA_DIR, exist_ok=True)\n",
    "\n",
    "# Pre-generar embeddings normalizados (simulan output de e5-small)\n",
    "datasets = {}\n",
    "for n in SIZES:\n",
    "    vecs = np.random.randn(n, DIM).astype(np.float32)\n",
    "    # Normalizar como haría sentence-transformers\n",
    "    norms = np.linalg.norm(vecs, axis=1, keepdims=True)\n",
    "    vecs = vecs / norms\n",
    "    datasets[n] = vecs\n",
    "\n",
    "# Queries (100 vectores)\n",
    "N_QUERIES = 100\n",
    "queries = np.random.randn(N_QUERIES, DIM).astype(np.float32)\n",
    "queries = queries / np.linalg.norm(queries, axis=1, keepdims=True)\n",
    "\n",
    "# Metadata simulada\n",
    "def make_metadata(n):\n",
    "    categories = ['programación', 'ciencia', 'cocina', 'finanzas', 'geografía']\n",
    "    return [{'id': i, 'category': categories[i % len(categories)], 'text': f'documento_{i}'} for i in range(n)]\n",
    "\n",
    "print(f'Dimensión: {DIM}')\n",
    "print(f'Tamaños de corpus: {SIZES}')\n",
    "print(f'Queries: {N_QUERIES}')\n",
    "print(f'Embeddings pre-generados (normalizados, float32)')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbadd0ba",
   "metadata": {},
   "source": [
    "## 2. Benchmark framework\n",
    "\n",
    "Cada backend implementa: insert, search, save, load, size_on_disk."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f187cb44",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Framework de benchmark listo\n"
     ]
    }
   ],
   "source": [
    "K = 10  # top-k para búsqueda\n",
    "\n",
    "def bench_backend(name, insert_fn, search_fn, save_fn, load_search_fn, size_fn, cleanup_fn):\n",
    "    \"\"\"Benchmark completo de un backend de vectores.\"\"\"\n",
    "    results = []\n",
    "    for n in SIZES:\n",
    "        vecs = datasets[n]\n",
    "        meta = make_metadata(n)\n",
    "        path = os.path.join(DATA_DIR, f'{name}_{n}')\n",
    "\n",
    "        # Limpiar estado previo\n",
    "        cleanup_fn(path)\n",
    "\n",
    "        # INSERT\n",
    "        t0 = time.perf_counter()\n",
    "        state = insert_fn(vecs, meta, path)\n",
    "        insert_time = time.perf_counter() - t0\n",
    "\n",
    "        # SEARCH (100 queries)\n",
    "        t0 = time.perf_counter()\n",
    "        results_search = search_fn(state, queries, K)\n",
    "        search_time = time.perf_counter() - t0\n",
    "        search_per_query = search_time / N_QUERIES\n",
    "\n",
    "        # SAVE / persist\n",
    "        t0 = time.perf_counter()\n",
    "        save_fn(state, path)\n",
    "        save_time = time.perf_counter() - t0\n",
    "\n",
    "        # SIZE on disk\n",
    "        disk_mb = size_fn(path)\n",
    "\n",
    "        # LOAD from disk + search\n",
    "        t0 = time.perf_counter()\n",
    "        loaded_results = load_search_fn(path, queries[:1], K)\n",
    "        load_search_time = time.perf_counter() - t0\n",
    "\n",
    "        results.append({\n",
    "            'backend': name,\n",
    "            'n_vectors': n,\n",
    "            'insert_s': round(insert_time, 4),\n",
    "            'search_100q_s': round(search_time, 4),\n",
    "            'per_query_ms': round(search_per_query * 1000, 3),\n",
    "            'save_s': round(save_time, 4),\n",
    "            'load_and_search_s': round(load_search_time, 4),\n",
    "            'disk_mb': round(disk_mb, 2),\n",
    "        })\n",
    "\n",
    "        print(f'  {name:12s} | {n:6d} vecs | insert={insert_time:.3f}s | '\n",
    "              f'search={search_per_query*1000:.2f}ms/q | save={save_time:.3f}s | '\n",
    "              f'load+search={load_search_time:.3f}s | disk={disk_mb:.1f}MB')\n",
    "\n",
    "        cleanup_fn(path)\n",
    "\n",
    "    return results\n",
    "\n",
    "def dir_size_mb(path):\n",
    "    total = 0\n",
    "    if os.path.isfile(path):\n",
    "        return os.path.getsize(path) / (1024*1024)\n",
    "    if not os.path.exists(path):\n",
    "        return 0\n",
    "    for dp, dn, fns in os.walk(path):\n",
    "        for f in fns:\n",
    "            total += os.path.getsize(os.path.join(dp, f))\n",
    "    return total / (1024*1024)\n",
    "\n",
    "def cleanup_path(path):\n",
    "    if os.path.isfile(path):\n",
    "        os.remove(path)\n",
    "    elif os.path.isdir(path):\n",
    "        shutil.rmtree(path, ignore_errors=True)\n",
    "    # También limpiar archivos con sufijos\n",
    "    for suffix in ['.faiss', '.ids.npy', '.usearch', '.meta.json', '.db']:\n",
    "        p = path + suffix\n",
    "        if os.path.exists(p):\n",
    "            os.remove(p)\n",
    "\n",
    "print('Framework de benchmark listo')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47e89c3c",
   "metadata": {},
   "source": [
    "## 3. Implementaciones por backend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "bc86400d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ FAISS listo\n"
     ]
    }
   ],
   "source": [
    "# ── FAISS ──────────────────────────────────────────────────────\n",
    "import faiss\n",
    "\n",
    "def faiss_insert(vecs, meta, path):\n",
    "    index = faiss.IndexFlatIP(DIM)\n",
    "    index.add(vecs)\n",
    "    return index\n",
    "\n",
    "def faiss_search(index, queries, k):\n",
    "    scores, ids = index.search(queries, k)\n",
    "    return ids\n",
    "\n",
    "def faiss_save(index, path):\n",
    "    faiss.write_index(index, path + '.faiss')\n",
    "\n",
    "def faiss_load_search(path, queries, k):\n",
    "    index = faiss.read_index(path + '.faiss')\n",
    "    scores, ids = index.search(queries, k)\n",
    "    return ids\n",
    "\n",
    "def faiss_size(path):\n",
    "    return dir_size_mb(path + '.faiss')\n",
    "\n",
    "print('✓ FAISS listo')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "30a64cf0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ sqlite-vec listo\n"
     ]
    }
   ],
   "source": [
    "# ── sqlite-vec ─────────────────────────────────────────────────\n",
    "import sqlite3\n",
    "import sqlite_vec\n",
    "\n",
    "def sqlvec_insert(vecs, meta, path):\n",
    "    db = sqlite3.connect(path + '.db')\n",
    "    db.enable_load_extension(True)\n",
    "    sqlite_vec.load(db)\n",
    "    db.execute(f'CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(embedding float[{DIM}])')\n",
    "    # Insert en batches\n",
    "    batch_size = 500\n",
    "    for i in range(0, len(vecs), batch_size):\n",
    "        batch = [(j, vecs[j].tobytes()) for j in range(i, min(i + batch_size, len(vecs)))]\n",
    "        db.executemany('INSERT INTO vec_items(rowid, embedding) VALUES (?, ?)', batch)\n",
    "    db.commit()\n",
    "    return db\n",
    "\n",
    "def sqlvec_search(db, queries, k):\n",
    "    results = []\n",
    "    for q in queries:\n",
    "        rows = db.execute(\n",
    "            'SELECT rowid, distance FROM vec_items WHERE embedding MATCH ? ORDER BY distance LIMIT ?',\n",
    "            (q.tobytes(), k)\n",
    "        ).fetchall()\n",
    "        results.append([r[0] for r in rows])\n",
    "    return results\n",
    "\n",
    "def sqlvec_save(db, path):\n",
    "    db.close()  # Ya está persistido en el .db\n",
    "\n",
    "def sqlvec_load_search(path, queries, k):\n",
    "    db = sqlite3.connect(path + '.db')\n",
    "    db.enable_load_extension(True)\n",
    "    sqlite_vec.load(db)\n",
    "    q = queries[0]\n",
    "    rows = db.execute(\n",
    "        'SELECT rowid, distance FROM vec_items WHERE embedding MATCH ? ORDER BY distance LIMIT ?',\n",
    "        (q.tobytes(), k)\n",
    "    ).fetchall()\n",
    "    db.close()\n",
    "    return [r[0] for r in rows]\n",
    "\n",
    "def sqlvec_size(path):\n",
    "    return dir_size_mb(path + '.db')\n",
    "\n",
    "print('✓ sqlite-vec listo')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "74d1bb27",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ LanceDB listo\n"
     ]
    }
   ],
   "source": [
    "# ── LanceDB ────────────────────────────────────────────────────\n",
    "import lancedb\n",
    "import pyarrow as pa\n",
    "\n",
    "def lance_insert(vecs, meta, path):\n",
    "    db = lancedb.connect(path)\n",
    "    data = pa.table({\n",
    "        'id': list(range(len(vecs))),\n",
    "        'category': [meta[i]['category'] for i in range(len(vecs))],\n",
    "        'vector': [v.tolist() for v in vecs],\n",
    "    })\n",
    "    tbl = db.create_table('vectors', data, mode='overwrite')\n",
    "    return tbl\n",
    "\n",
    "def lance_search(tbl, queries, k):\n",
    "    results = []\n",
    "    for q in queries:\n",
    "        r = tbl.search(q.tolist()).limit(k).to_list()\n",
    "        results.append([row['id'] for row in r])\n",
    "    return results\n",
    "\n",
    "def lance_save(tbl, path):\n",
    "    pass  # LanceDB persiste automáticamente\n",
    "\n",
    "def lance_load_search(path, queries, k):\n",
    "    db = lancedb.connect(path)\n",
    "    tbl = db.open_table('vectors')\n",
    "    q = queries[0]\n",
    "    r = tbl.search(q.tolist()).limit(k).to_list()\n",
    "    return [row['id'] for row in r]\n",
    "\n",
    "def lance_size(path):\n",
    "    return dir_size_mb(path)\n",
    "\n",
    "print('✓ LanceDB listo')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1541cef1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ ChromaDB listo\n"
     ]
    }
   ],
   "source": [
    "# ── ChromaDB ───────────────────────────────────────────────────\n",
    "import chromadb\n",
    "\n",
    "def chroma_insert(vecs, meta, path):\n",
    "    client = chromadb.PersistentClient(path=path)\n",
    "    col = client.get_or_create_collection('vectors', metadata={'hnsw:space': 'ip'})\n",
    "    # Chroma tiene límite de batch, insertar en chunks\n",
    "    batch_size = 5000\n",
    "    for i in range(0, len(vecs), batch_size):\n",
    "        end = min(i + batch_size, len(vecs))\n",
    "        col.add(\n",
    "            ids=[str(j) for j in range(i, end)],\n",
    "            embeddings=[v.tolist() for v in vecs[i:end]],\n",
    "            metadatas=[meta[j] for j in range(i, end)],\n",
    "        )\n",
    "    return (client, col)\n",
    "\n",
    "def chroma_search(state, queries, k):\n",
    "    _, col = state\n",
    "    results = col.query(query_embeddings=[q.tolist() for q in queries], n_results=k)\n",
    "    return results['ids']\n",
    "\n",
    "def chroma_save(state, path):\n",
    "    pass  # PersistentClient persiste automáticamente\n",
    "\n",
    "def chroma_load_search(path, queries, k):\n",
    "    client = chromadb.PersistentClient(path=path)\n",
    "    col = client.get_collection('vectors')\n",
    "    results = col.query(query_embeddings=[queries[0].tolist()], n_results=k)\n",
    "    return results['ids']\n",
    "\n",
    "def chroma_size(path):\n",
    "    return dir_size_mb(path)\n",
    "\n",
    "print('✓ ChromaDB listo')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6bec353d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ USearch listo\n"
     ]
    }
   ],
   "source": [
    "# ── USearch ────────────────────────────────────────────────────\n",
    "from usearch.index import Index\n",
    "\n",
    "def usearch_insert(vecs, meta, path):\n",
    "    index = Index(ndim=DIM, metric='ip', dtype='f32')\n",
    "    keys = np.arange(len(vecs), dtype=np.uint64)\n",
    "    index.add(keys, vecs)\n",
    "    return index\n",
    "\n",
    "def usearch_search(index, queries, k):\n",
    "    results = index.search(queries, k)\n",
    "    return results.keys\n",
    "\n",
    "def usearch_save(index, path):\n",
    "    index.save(path + '.usearch')\n",
    "\n",
    "def usearch_load_search(path, queries, k):\n",
    "    index = Index(ndim=DIM, metric='ip', dtype='f32')\n",
    "    index.load(path + '.usearch')\n",
    "    results = index.search(queries[:1], k)\n",
    "    return results.keys\n",
    "\n",
    "def usearch_size(path):\n",
    "    return dir_size_mb(path + '.usearch')\n",
    "\n",
    "print('✓ USearch listo')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c82f6260",
   "metadata": {},
   "source": [
    "## 4. Ejecutar benchmarks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "e02de33b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "── FAISS ──\n",
      "  FAISS        |   1000 vecs | insert=0.003s | search=0.23ms/q | save=0.006s | load+search=0.003s | disk=1.5MB\n",
      "  FAISS        |  10000 vecs | insert=0.020s | search=0.48ms/q | save=0.030s | load+search=0.034s | disk=14.6MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  FAISS        |  50000 vecs | insert=0.240s | search=2.77ms/q | save=0.169s | load+search=0.211s | disk=73.2MB\n",
      "\n",
      "── sqlite-vec ──\n",
      "  sqlite-vec   |   1000 vecs | insert=0.053s | search=0.35ms/q | save=0.001s | load+search=0.001s | disk=1.6MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  sqlite-vec   |  10000 vecs | insert=0.293s | search=4.87ms/q | save=0.000s | load+search=0.006s | disk=15.3MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  sqlite-vec   |  50000 vecs | insert=1.275s | search=19.50ms/q | save=0.000s | load+search=0.018s | disk=74.7MB\n",
      "\n",
      "── LanceDB ──\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[90m[\u001b[0m2026-04-02T15:27:41Z \u001b[33mWARN \u001b[0m lance::dataset::write::insert\u001b[90m]\u001b[0m No existing dataset at /home/lucas/fn_registry/analysis/estudio_embeddings/notebooks/data/vector_bench/LanceDB_1000/vectors.lance, it will be created\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  LanceDB      |   1000 vecs | insert=0.040s | search=3.32ms/q | save=0.000s | load+search=0.007s | disk=1.5MB\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[90m[\u001b[0m2026-04-02T15:27:42Z \u001b[33mWARN \u001b[0m lance::dataset::write::insert\u001b[90m]\u001b[0m No existing dataset at /home/lucas/fn_registry/analysis/estudio_embeddings/notebooks/data/vector_bench/LanceDB_10000/vectors.lance, it will be created\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  LanceDB      |  10000 vecs | insert=0.297s | search=12.30ms/q | save=0.000s | load+search=0.011s | disk=14.7MB\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[90m[\u001b[0m2026-04-02T15:27:47Z \u001b[33mWARN \u001b[0m lance::dataset::write::insert\u001b[90m]\u001b[0m No existing dataset at /home/lucas/fn_registry/analysis/estudio_embeddings/notebooks/data/vector_bench/LanceDB_50000/vectors.lance, it will be created\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  LanceDB      |  50000 vecs | insert=3.740s | search=28.88ms/q | save=0.000s | load+search=0.030s | disk=73.7MB\n",
      "\n",
      "── ChromaDB ──\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  ChromaDB     |   1000 vecs | insert=0.492s | search=0.30ms/q | save=0.000s | load+search=0.006s | disk=4.1MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  ChromaDB     |  10000 vecs | insert=2.778s | search=0.53ms/q | save=0.000s | load+search=0.006s | disk=29.6MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  ChromaDB     |  50000 vecs | insert=19.123s | search=1.23ms/q | save=0.000s | load+search=0.010s | disk=108.6MB\n",
      "\n",
      "── USearch ──\n",
      "  ERROR: usearch.index.Index() got multiple values for keyword argument 'ndim'\n",
      "\n",
      "Benchmark completo\n"
     ]
    }
   ],
   "source": [
    "all_results = []\n",
    "\n",
    "backends = [\n",
    "    ('FAISS',      faiss_insert,   faiss_search,   faiss_save,   faiss_load_search,   faiss_size,   cleanup_path),\n",
    "    ('sqlite-vec', sqlvec_insert,  sqlvec_search,   sqlvec_save,  sqlvec_load_search,  sqlvec_size,  cleanup_path),\n",
    "    ('LanceDB',    lance_insert,   lance_search,   lance_save,   lance_load_search,   lance_size,   cleanup_path),\n",
    "    ('ChromaDB',   chroma_insert,  chroma_search,  chroma_save,  chroma_load_search,  chroma_size,  cleanup_path),\n",
    "    ('USearch',    usearch_insert, usearch_search, usearch_save, usearch_load_search, usearch_size, cleanup_path),\n",
    "]\n",
    "\n",
    "for name, *fns in backends:\n",
    "    print(f'\\n── {name} ──')\n",
    "    try:\n",
    "        res = bench_backend(name, *fns)\n",
    "        all_results.extend(res)\n",
    "    except Exception as e:\n",
    "        print(f'  ERROR: {e}')\n",
    "\n",
    "df = pd.DataFrame(all_results)\n",
    "print('\\nBenchmark completo')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f126167a",
   "metadata": {},
   "source": [
    "## 5. Tabla resumen y visualizaciones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "311f88fc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "#T_814d2_row0_col2, #T_814d2_row0_col7, #T_814d2_row1_col2, #T_814d2_row3_col2, #T_814d2_row3_col7, #T_814d2_row6_col2, #T_814d2_row6_col7, #T_814d2_row12_col2, #T_814d2_row12_col4, #T_814d2_row12_col7, #T_814d2_row13_col4 {\n",
       "  background-color: #67000d;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row0_col4, #T_814d2_row14_col4 {\n",
       "  background-color: #69000d;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row0_col6 {\n",
       "  background-color: #00451c;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row1_col4, #T_814d2_row10_col4 {\n",
       "  background-color: #6f020e;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row1_col6 {\n",
       "  background-color: #087432;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row1_col7, #T_814d2_row7_col7 {\n",
       "  background-color: #a30f15;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row2_col2, #T_814d2_row3_col4, #T_814d2_row4_col2, #T_814d2_row7_col2, #T_814d2_row13_col2 {\n",
       "  background-color: #6d010e;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row2_col4 {\n",
       "  background-color: #960b13;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row2_col6 {\n",
       "  background-color: #f7fcf5;\n",
       "  color: #000000;\n",
       "}\n",
       "#T_814d2_row2_col7 {\n",
       "  background-color: #fca183;\n",
       "  color: #000000;\n",
       "}\n",
       "#T_814d2_row3_col6, #T_814d2_row12_col6 {\n",
       "  background-color: #00441b;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row4_col4 {\n",
       "  background-color: #b21218;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row4_col6, #T_814d2_row9_col6, #T_814d2_row10_col6 {\n",
       "  background-color: #004a1e;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row4_col7 {\n",
       "  background-color: #a50f15;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row5_col2 {\n",
       "  background-color: #880811;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row5_col4, #T_814d2_row8_col7 {\n",
       "  background-color: #fca285;\n",
       "  color: #000000;\n",
       "}\n",
       "#T_814d2_row5_col6 {\n",
       "  background-color: #005e26;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row5_col7 {\n",
       "  background-color: #fca588;\n",
       "  color: #000000;\n",
       "}\n",
       "#T_814d2_row6_col4 {\n",
       "  background-color: #9f0e14;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row6_col6 {\n",
       "  background-color: #004d1f;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row7_col4 {\n",
       "  background-color: #f44d38;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row7_col6 {\n",
       "  background-color: #005221;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row8_col2 {\n",
       "  background-color: #bb141a;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row8_col4, #T_814d2_row11_col2, #T_814d2_row11_col7 {\n",
       "  background-color: #fff5f0;\n",
       "  color: #000000;\n",
       "}\n",
       "#T_814d2_row8_col6 {\n",
       "  background-color: #03702e;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row9_col2, #T_814d2_row9_col7 {\n",
       "  background-color: #73030f;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row9_col4 {\n",
       "  background-color: #6b010e;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row10_col2 {\n",
       "  background-color: #ab1016;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row10_col7 {\n",
       "  background-color: #cf1c1f;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row11_col4 {\n",
       "  background-color: #7a0510;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row11_col6 {\n",
       "  background-color: #005120;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row13_col6 {\n",
       "  background-color: #005f26;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row13_col7 {\n",
       "  background-color: #a81016;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row14_col2 {\n",
       "  background-color: #dc2924;\n",
       "  color: #f1f1f1;\n",
       "}\n",
       "#T_814d2_row14_col6 {\n",
       "  background-color: #e8f6e3;\n",
       "  color: #000000;\n",
       "}\n",
       "#T_814d2_row14_col7 {\n",
       "  background-color: #fcb79c;\n",
       "  color: #000000;\n",
       "}\n",
       "</style>\n",
       "<table id=\"T_814d2\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_814d2_level0_col0\" class=\"col_heading level0 col0\" >Backend</th>\n",
       "      <th id=\"T_814d2_level0_col1\" class=\"col_heading level0 col1\" >Vectors</th>\n",
       "      <th id=\"T_814d2_level0_col2\" class=\"col_heading level0 col2\" >Insert (s)</th>\n",
       "      <th id=\"T_814d2_level0_col3\" class=\"col_heading level0 col3\" >Search 100q (s)</th>\n",
       "      <th id=\"T_814d2_level0_col4\" class=\"col_heading level0 col4\" >Per query (ms)</th>\n",
       "      <th id=\"T_814d2_level0_col5\" class=\"col_heading level0 col5\" >Save (s)</th>\n",
       "      <th id=\"T_814d2_level0_col6\" class=\"col_heading level0 col6\" >Load+Search (s)</th>\n",
       "      <th id=\"T_814d2_level0_col7\" class=\"col_heading level0 col7\" >Disk (MB)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row0\" class=\"row_heading level0 row0\" >0</th>\n",
       "      <td id=\"T_814d2_row0_col0\" class=\"data row0 col0\" >FAISS</td>\n",
       "      <td id=\"T_814d2_row0_col1\" class=\"data row0 col1\" >1000</td>\n",
       "      <td id=\"T_814d2_row0_col2\" class=\"data row0 col2\" >0.002700</td>\n",
       "      <td id=\"T_814d2_row0_col3\" class=\"data row0 col3\" >0.022600</td>\n",
       "      <td id=\"T_814d2_row0_col4\" class=\"data row0 col4\" >0.226000</td>\n",
       "      <td id=\"T_814d2_row0_col5\" class=\"data row0 col5\" >0.006000</td>\n",
       "      <td id=\"T_814d2_row0_col6\" class=\"data row0 col6\" >0.002600</td>\n",
       "      <td id=\"T_814d2_row0_col7\" class=\"data row0 col7\" >1.460000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row1\" class=\"row_heading level0 row1\" >1</th>\n",
       "      <td id=\"T_814d2_row1_col0\" class=\"data row1 col0\" >FAISS</td>\n",
       "      <td id=\"T_814d2_row1_col1\" class=\"data row1 col1\" >10000</td>\n",
       "      <td id=\"T_814d2_row1_col2\" class=\"data row1 col2\" >0.020000</td>\n",
       "      <td id=\"T_814d2_row1_col3\" class=\"data row1 col3\" >0.048000</td>\n",
       "      <td id=\"T_814d2_row1_col4\" class=\"data row1 col4\" >0.480000</td>\n",
       "      <td id=\"T_814d2_row1_col5\" class=\"data row1 col5\" >0.030200</td>\n",
       "      <td id=\"T_814d2_row1_col6\" class=\"data row1 col6\" >0.034000</td>\n",
       "      <td id=\"T_814d2_row1_col7\" class=\"data row1 col7\" >14.650000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row2\" class=\"row_heading level0 row2\" >2</th>\n",
       "      <td id=\"T_814d2_row2_col0\" class=\"data row2 col0\" >FAISS</td>\n",
       "      <td id=\"T_814d2_row2_col1\" class=\"data row2 col1\" >50000</td>\n",
       "      <td id=\"T_814d2_row2_col2\" class=\"data row2 col2\" >0.240500</td>\n",
       "      <td id=\"T_814d2_row2_col3\" class=\"data row2 col3\" >0.277200</td>\n",
       "      <td id=\"T_814d2_row2_col4\" class=\"data row2 col4\" >2.772000</td>\n",
       "      <td id=\"T_814d2_row2_col5\" class=\"data row2 col5\" >0.169200</td>\n",
       "      <td id=\"T_814d2_row2_col6\" class=\"data row2 col6\" >0.210900</td>\n",
       "      <td id=\"T_814d2_row2_col7\" class=\"data row2 col7\" >73.240000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row3\" class=\"row_heading level0 row3\" >3</th>\n",
       "      <td id=\"T_814d2_row3_col0\" class=\"data row3 col0\" >sqlite-vec</td>\n",
       "      <td id=\"T_814d2_row3_col1\" class=\"data row3 col1\" >1000</td>\n",
       "      <td id=\"T_814d2_row3_col2\" class=\"data row3 col2\" >0.053100</td>\n",
       "      <td id=\"T_814d2_row3_col3\" class=\"data row3 col3\" >0.035500</td>\n",
       "      <td id=\"T_814d2_row3_col4\" class=\"data row3 col4\" >0.355000</td>\n",
       "      <td id=\"T_814d2_row3_col5\" class=\"data row3 col5\" >0.000600</td>\n",
       "      <td id=\"T_814d2_row3_col6\" class=\"data row3 col6\" >0.001300</td>\n",
       "      <td id=\"T_814d2_row3_col7\" class=\"data row3 col7\" >1.550000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row4\" class=\"row_heading level0 row4\" >4</th>\n",
       "      <td id=\"T_814d2_row4_col0\" class=\"data row4 col0\" >sqlite-vec</td>\n",
       "      <td id=\"T_814d2_row4_col1\" class=\"data row4 col1\" >10000</td>\n",
       "      <td id=\"T_814d2_row4_col2\" class=\"data row4 col2\" >0.292600</td>\n",
       "      <td id=\"T_814d2_row4_col3\" class=\"data row4 col3\" >0.486800</td>\n",
       "      <td id=\"T_814d2_row4_col4\" class=\"data row4 col4\" >4.868000</td>\n",
       "      <td id=\"T_814d2_row4_col5\" class=\"data row4 col5\" >0.000300</td>\n",
       "      <td id=\"T_814d2_row4_col6\" class=\"data row4 col6\" >0.006000</td>\n",
       "      <td id=\"T_814d2_row4_col7\" class=\"data row4 col7\" >15.260000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row5\" class=\"row_heading level0 row5\" >5</th>\n",
       "      <td id=\"T_814d2_row5_col0\" class=\"data row5 col0\" >sqlite-vec</td>\n",
       "      <td id=\"T_814d2_row5_col1\" class=\"data row5 col1\" >50000</td>\n",
       "      <td id=\"T_814d2_row5_col2\" class=\"data row5 col2\" >1.274600</td>\n",
       "      <td id=\"T_814d2_row5_col3\" class=\"data row5 col3\" >1.949800</td>\n",
       "      <td id=\"T_814d2_row5_col4\" class=\"data row5 col4\" >19.498000</td>\n",
       "      <td id=\"T_814d2_row5_col5\" class=\"data row5 col5\" >0.000300</td>\n",
       "      <td id=\"T_814d2_row5_col6\" class=\"data row5 col6\" >0.018300</td>\n",
       "      <td id=\"T_814d2_row5_col7\" class=\"data row5 col7\" >74.690000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row6\" class=\"row_heading level0 row6\" >6</th>\n",
       "      <td id=\"T_814d2_row6_col0\" class=\"data row6 col0\" >LanceDB</td>\n",
       "      <td id=\"T_814d2_row6_col1\" class=\"data row6 col1\" >1000</td>\n",
       "      <td id=\"T_814d2_row6_col2\" class=\"data row6 col2\" >0.040000</td>\n",
       "      <td id=\"T_814d2_row6_col3\" class=\"data row6 col3\" >0.332400</td>\n",
       "      <td id=\"T_814d2_row6_col4\" class=\"data row6 col4\" >3.324000</td>\n",
       "      <td id=\"T_814d2_row6_col5\" class=\"data row6 col5\" >0.000000</td>\n",
       "      <td id=\"T_814d2_row6_col6\" class=\"data row6 col6\" >0.007100</td>\n",
       "      <td id=\"T_814d2_row6_col7\" class=\"data row6 col7\" >1.480000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row7\" class=\"row_heading level0 row7\" >7</th>\n",
       "      <td id=\"T_814d2_row7_col0\" class=\"data row7 col0\" >LanceDB</td>\n",
       "      <td id=\"T_814d2_row7_col1\" class=\"data row7 col1\" >10000</td>\n",
       "      <td id=\"T_814d2_row7_col2\" class=\"data row7 col2\" >0.296800</td>\n",
       "      <td id=\"T_814d2_row7_col3\" class=\"data row7 col3\" >1.230500</td>\n",
       "      <td id=\"T_814d2_row7_col4\" class=\"data row7 col4\" >12.305000</td>\n",
       "      <td id=\"T_814d2_row7_col5\" class=\"data row7 col5\" >0.000000</td>\n",
       "      <td id=\"T_814d2_row7_col6\" class=\"data row7 col6\" >0.010700</td>\n",
       "      <td id=\"T_814d2_row7_col7\" class=\"data row7 col7\" >14.740000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row8\" class=\"row_heading level0 row8\" >8</th>\n",
       "      <td id=\"T_814d2_row8_col0\" class=\"data row8 col0\" >LanceDB</td>\n",
       "      <td id=\"T_814d2_row8_col1\" class=\"data row8 col1\" >50000</td>\n",
       "      <td id=\"T_814d2_row8_col2\" class=\"data row8 col2\" >3.740100</td>\n",
       "      <td id=\"T_814d2_row8_col3\" class=\"data row8 col3\" >2.888400</td>\n",
       "      <td id=\"T_814d2_row8_col4\" class=\"data row8 col4\" >28.884000</td>\n",
       "      <td id=\"T_814d2_row8_col5\" class=\"data row8 col5\" >0.000000</td>\n",
       "      <td id=\"T_814d2_row8_col6\" class=\"data row8 col6\" >0.030100</td>\n",
       "      <td id=\"T_814d2_row8_col7\" class=\"data row8 col7\" >73.670000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row9\" class=\"row_heading level0 row9\" >9</th>\n",
       "      <td id=\"T_814d2_row9_col0\" class=\"data row9 col0\" >ChromaDB</td>\n",
       "      <td id=\"T_814d2_row9_col1\" class=\"data row9 col1\" >1000</td>\n",
       "      <td id=\"T_814d2_row9_col2\" class=\"data row9 col2\" >0.491600</td>\n",
       "      <td id=\"T_814d2_row9_col3\" class=\"data row9 col3\" >0.029500</td>\n",
       "      <td id=\"T_814d2_row9_col4\" class=\"data row9 col4\" >0.295000</td>\n",
       "      <td id=\"T_814d2_row9_col5\" class=\"data row9 col5\" >0.000000</td>\n",
       "      <td id=\"T_814d2_row9_col6\" class=\"data row9 col6\" >0.006000</td>\n",
       "      <td id=\"T_814d2_row9_col7\" class=\"data row9 col7\" >4.090000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row10\" class=\"row_heading level0 row10\" >10</th>\n",
       "      <td id=\"T_814d2_row10_col0\" class=\"data row10 col0\" >ChromaDB</td>\n",
       "      <td id=\"T_814d2_row10_col1\" class=\"data row10 col1\" >10000</td>\n",
       "      <td id=\"T_814d2_row10_col2\" class=\"data row10 col2\" >2.778100</td>\n",
       "      <td id=\"T_814d2_row10_col3\" class=\"data row10 col3\" >0.053500</td>\n",
       "      <td id=\"T_814d2_row10_col4\" class=\"data row10 col4\" >0.535000</td>\n",
       "      <td id=\"T_814d2_row10_col5\" class=\"data row10 col5\" >0.000000</td>\n",
       "      <td id=\"T_814d2_row10_col6\" class=\"data row10 col6\" >0.005500</td>\n",
       "      <td id=\"T_814d2_row10_col7\" class=\"data row10 col7\" >29.570000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row11\" class=\"row_heading level0 row11\" >11</th>\n",
       "      <td id=\"T_814d2_row11_col0\" class=\"data row11 col0\" >ChromaDB</td>\n",
       "      <td id=\"T_814d2_row11_col1\" class=\"data row11 col1\" >50000</td>\n",
       "      <td id=\"T_814d2_row11_col2\" class=\"data row11 col2\" >19.122500</td>\n",
       "      <td id=\"T_814d2_row11_col3\" class=\"data row11 col3\" >0.122800</td>\n",
       "      <td id=\"T_814d2_row11_col4\" class=\"data row11 col4\" >1.228000</td>\n",
       "      <td id=\"T_814d2_row11_col5\" class=\"data row11 col5\" >0.000000</td>\n",
       "      <td id=\"T_814d2_row11_col6\" class=\"data row11 col6\" >0.009800</td>\n",
       "      <td id=\"T_814d2_row11_col7\" class=\"data row11 col7\" >108.640000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row12\" class=\"row_heading level0 row12\" >12</th>\n",
       "      <td id=\"T_814d2_row12_col0\" class=\"data row12 col0\" >USearch</td>\n",
       "      <td id=\"T_814d2_row12_col1\" class=\"data row12 col1\" >1000</td>\n",
       "      <td id=\"T_814d2_row12_col2\" class=\"data row12 col2\" >0.010800</td>\n",
       "      <td id=\"T_814d2_row12_col3\" class=\"data row12 col3\" >0.001300</td>\n",
       "      <td id=\"T_814d2_row12_col4\" class=\"data row12 col4\" >0.013000</td>\n",
       "      <td id=\"T_814d2_row12_col5\" class=\"data row12 col5\" >0.001100</td>\n",
       "      <td id=\"T_814d2_row12_col6\" class=\"data row12 col6\" >0.001500</td>\n",
       "      <td id=\"T_814d2_row12_col7\" class=\"data row12 col7\" >1.610000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row13\" class=\"row_heading level0 row13\" >13</th>\n",
       "      <td id=\"T_814d2_row13_col0\" class=\"data row13 col0\" >USearch</td>\n",
       "      <td id=\"T_814d2_row13_col1\" class=\"data row13 col1\" >10000</td>\n",
       "      <td id=\"T_814d2_row13_col2\" class=\"data row13 col2\" >0.289400</td>\n",
       "      <td id=\"T_814d2_row13_col3\" class=\"data row13 col3\" >0.002500</td>\n",
       "      <td id=\"T_814d2_row13_col4\" class=\"data row13 col4\" >0.025000</td>\n",
       "      <td id=\"T_814d2_row13_col5\" class=\"data row13 col5\" >0.013400</td>\n",
       "      <td id=\"T_814d2_row13_col6\" class=\"data row13 col6\" >0.018800</td>\n",
       "      <td id=\"T_814d2_row13_col7\" class=\"data row13 col7\" >16.070000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_814d2_level0_row14\" class=\"row_heading level0 row14\" >14</th>\n",
       "      <td id=\"T_814d2_row14_col0\" class=\"data row14 col0\" >USearch</td>\n",
       "      <td id=\"T_814d2_row14_col1\" class=\"data row14 col1\" >50000</td>\n",
       "      <td id=\"T_814d2_row14_col2\" class=\"data row14 col2\" >5.965700</td>\n",
       "      <td id=\"T_814d2_row14_col3\" class=\"data row14 col3\" >0.013500</td>\n",
       "      <td id=\"T_814d2_row14_col4\" class=\"data row14 col4\" >0.135000</td>\n",
       "      <td id=\"T_814d2_row14_col5\" class=\"data row14 col5\" >0.111700</td>\n",
       "      <td id=\"T_814d2_row14_col6\" class=\"data row14 col6\" >0.188300</td>\n",
       "      <td id=\"T_814d2_row14_col7\" class=\"data row14 col7\" >80.320000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x7f23d0fd1940>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_display = df.copy()\n",
    "df_display.columns = ['Backend', 'Vectors', 'Insert (s)', 'Search 100q (s)', 'Per query (ms)',\n",
    "                       'Save (s)', 'Load+Search (s)', 'Disk (MB)']\n",
    "df_display.style.background_gradient(subset=['Per query (ms)', 'Insert (s)', 'Disk (MB)'], cmap='Reds_r')     .background_gradient(subset=['Load+Search (s)'], cmap='Greens_r')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "6b183baa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 275548 characters]",
      "text/plain": [
       "<Figure size 1600x1200 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    }
   ],
   "source": [
    "fig, axes = plt.subplots(2, 2, figsize=(16, 12))\n",
    "colors = {'FAISS': '#e74c3c', 'sqlite-vec': '#3498db', 'LanceDB': '#2ecc71', 'ChromaDB': '#f39c12', 'USearch': '#9b59b6'}\n",
    "\n",
    "# 1: Search latency per query\n",
    "ax = axes[0, 0]\n",
    "for name in df['backend'].unique():\n",
    "    sub = df[df['backend'] == name]\n",
    "    ax.plot(sub['n_vectors'], sub['per_query_ms'], 'o-', color=colors.get(name, 'gray'),\n",
    "            label=name, linewidth=2, markersize=8)\n",
    "ax.set_xlabel('Vectores en índice')\n",
    "ax.set_ylabel('Latencia por query (ms)')\n",
    "ax.set_title('Velocidad de búsqueda (top-10)')\n",
    "ax.legend()\n",
    "ax.set_xscale('log')\n",
    "ax.set_yscale('log')\n",
    "\n",
    "# 2: Insert time\n",
    "ax = axes[0, 1]\n",
    "for name in df['backend'].unique():\n",
    "    sub = df[df['backend'] == name]\n",
    "    ax.plot(sub['n_vectors'], sub['insert_s'], 's-', color=colors.get(name, 'gray'),\n",
    "            label=name, linewidth=2, markersize=8)\n",
    "ax.set_xlabel('Vectores')\n",
    "ax.set_ylabel('Tiempo de inserción (s)')\n",
    "ax.set_title('Velocidad de inserción')\n",
    "ax.legend()\n",
    "ax.set_xscale('log')\n",
    "\n",
    "# 3: Disk usage\n",
    "ax = axes[1, 0]\n",
    "for name in df['backend'].unique():\n",
    "    sub = df[df['backend'] == name]\n",
    "    ax.plot(sub['n_vectors'], sub['disk_mb'], 'D-', color=colors.get(name, 'gray'),\n",
    "            label=name, linewidth=2, markersize=8)\n",
    "ax.set_xlabel('Vectores')\n",
    "ax.set_ylabel('Tamaño en disco (MB)')\n",
    "ax.set_title('Almacenamiento en disco')\n",
    "ax.legend()\n",
    "ax.set_xscale('log')\n",
    "\n",
    "# 4: Load from disk + search\n",
    "ax = axes[1, 1]\n",
    "for name in df['backend'].unique():\n",
    "    sub = df[df['backend'] == name]\n",
    "    ax.plot(sub['n_vectors'], sub['load_and_search_s'], '^-', color=colors.get(name, 'gray'),\n",
    "            label=name, linewidth=2, markersize=8)\n",
    "ax.set_xlabel('Vectores')\n",
    "ax.set_ylabel('Load + 1 búsqueda (s)')\n",
    "ax.set_title('Cold start: cargar de disco y buscar')\n",
    "ax.legend()\n",
    "ax.set_xscale('log')\n",
    "ax.set_yscale('log')\n",
    "\n",
    "plt.suptitle('Comparativa de backends de vectores (dim=384, inner product)', fontsize=14)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75c7c573",
   "metadata": {},
   "source": [
    "## 6. Resumen: pros y contras para fn_registry\n",
    "\n",
    "| Backend | Pro | Contra | Ideal para |\n",
    "|---|---|---|---|\n",
    "| **FAISS** | Más rápido, battle-tested | Sin metadata nativa, hay que gestionar IDs aparte | Índices de solo vectores donde la velocidad importa |\n",
    "| **sqlite-vec** | Mismo archivo SQLite, SQL completo, metadata con JOINs | Búsqueda más lenta en brute-force | Integrar vectores en registry.db o operations.db |\n",
    "| **LanceDB** | Vector-native, filtros, columnar | Directorio no archivo único, API puede cambiar | Datasets medianos-grandes con filtros |\n",
    "| **ChromaDB** | Metadata rica, popular | Más overhead, más lento, directorio grande | Prototipado rápido con metadata compleja |\n",
    "| **USearch** | Ultra ligero, casi tan rápido como FAISS | Sin metadata, API minimalista | Índice puro de vectores, mínima dependencia |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ccb97d3a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "── USearch ──\n",
      "  USearch      |   1000 vecs | insert=0.011s | search=0.01ms/q | save=0.001s | load+search=0.002s | disk=1.6MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  USearch      |  10000 vecs | insert=0.289s | search=0.02ms/q | save=0.013s | load+search=0.019s | disk=16.1MB\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  USearch      |  50000 vecs | insert=5.966s | search=0.13ms/q | save=0.112s | load+search=0.188s | disk=80.3MB\n",
      "USearch añadido al dataframe\n"
     ]
    }
   ],
   "source": [
    "# Re-run solo USearch\n",
    "print('── USearch ──')\n",
    "try:\n",
    "    usearch_res = bench_backend('USearch', usearch_insert, usearch_search, usearch_save, usearch_load_search, usearch_size, cleanup_path)\n",
    "    all_results.extend(usearch_res)\n",
    "    df = pd.DataFrame(all_results)\n",
    "    print('USearch añadido al dataframe')\n",
    "except Exception as e:\n",
    "    import traceback\n",
    "    traceback.print_exc()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
