pgvector Performance Tuning: HNSW, Memory, and Query Optimization

TL;DR — pgvector performance tuning: 5 parameters, EXPLAIN ANALYZE, iterative scans, memory sizing. SelfHostDev: "Five parameters drive 90% of performance: maintenance_work_mem 8-16GB, ef_construction 256-512, ef_search 10-200, m 8-32, shared_buffers 25-40% RAM. Six common failures: HNSW OOM, silent seq scans, type mismatches, filter mismatches, write contention, cold-cache p99 spikes." ParadeDB: "Three decisions: use HNSW, tune ef_search to recall needed, give index enough memory to stay resident. Index that spills to disk has long latency tail no parameter will fix." Rivestack: "HNSW is right index for production. Start with defaults, measure recall and latency, tune ef_search, filters, memory, storage. EXPLAIN (ANALYZE, BUFFERS) shows cache hits vs physical reads." AWS: "pgvector 0.8.0 iterative scans fix overfiltering. max_scan_tuples and scan_mem_multiplier bound the scan. 10K-50K vectors: skip index, use parallel seq scan." NerdLevelTech: "Recall climbs sharply 10-40, slowly to 95-99% by 80-160, asymptotes after. Latency rises faster than linearly. Docker shm_size 2gb to match maintenance_work_mem." Learn more with pgvector tutorial, HNSW vs IVFFlat, HNSW explained, and pgvector Docker.

SelfHostDev provides the definitive 2026 reality check: "Five parameters drive 90% of pgvector production performance: maintenance_work_mem (8-16 GB for HNSW builds), ef_construction (256-512 for production-grade indexes), ef_search (10-200, tunable per query), m (8-32, default 16), and shared_buffers (25-40% of total RAM)."

ParadeDB distills it further: "Most pgvector performance comes from three decisions: use HNSW unless you have a reason not to, tune ef_search (or probes) to the recall you need, and give the index enough memory to stay resident. Everything else is refinement."

Performance Tuning Architecture

flowchart TD subgraph Build["Index Build Tuning"] MW["maintenance_work_mem
8-16 GB during build
revert after"] EfC["ef_construction
256-512 for production
better graph quality"] M["m: connections per node
8-32, default 16
memory vs recall"] Parallel["max_parallel_maintenance_workers
parallel index build"] Shm["shm_size: 2gb
Docker shared memory
match maintenance_work_mem"] end subgraph Query["Query-Time Tuning"] EfS["ef_search
10-200 per query
SET LOCAL for tuning"] Sweep["Recall Sweep
20, 40, 80, 160, 320, 640
plot recall vs latency"] Explain["EXPLAIN ANALYZE
detect seq scans
check index usage"] Buffers["EXPLAIN BUFFERS
cache hits vs disk reads
cache hit ratio"] end subgraph Memory["Memory Configuration"] SB["shared_buffers
25-40% of RAM
keep HNSW index resident"] WM["work_mem
64-128 MB
for hybrid sorts/joins"] Cache["OS Page Cache
remaining RAM
for index pages"] end subgraph Filter["Filtered Search"] Iter["iterative_scan
pgvector 0.8.0+
fix overfiltering"] MaxT["max_scan_tuples
default 20,000
cap scan depth"] ScanMem["scan_mem_multiplier
default 1
cap scan memory"] BTree["B-tree indexes
on filter columns
support WHERE clauses"] end subgraph Storage["Storage Optimization"] Half["halfvec
halve storage
preserve quality"] Quant["binary quantization
pgvector 0.8.0+
reduce memory"] Partition["partitioning
for selective filters
partial indexes"] end Build --> Query --> Memory Filter --> Query Storage --> Memory

Five Key Parameters

Parameter Default Production Target What It Does
maintenance_work_mem 64MB 8-16 GB Speeds HNSW builds 10-50x. Per-build, revert after.
ef_construction 64 256-512 Higher = better recall during build, slower build
ef_search 40 10-200 (dynamic) Recall vs latency at query time. SET LOCAL per query.
m 16 8-32 Graph connections per node. Memory vs recall tradeoff.
shared_buffers 128MB 25-40% of RAM Keeps hot index pages in memory

ef_search Tuning Guide

ef_search Recall Latency Use Case
10 ~85-90% Fastest Product search, speed beats precision
40 ~92-95% Fast Recommendations, ~50ms tolerance
80 ~95-97% Moderate General RAG, content similarity
100-200 ~97-99% Slower RAG where missing chunk is costly
320-640 ~99%+ Slowest Maximum recall, benchmarking

Implementation

from dataclasses import dataclass
from typing import Optional
from enum import Enum

class TuningPreset(Enum):
    FAST = "fast"
    BALANCED = "balanced"
    HIGH_RECALL = "high_recall"
    MAX_RECALL = "max_recall"

@dataclass
class PgvectorPerformanceTuner:
    """pgvector performance tuning guide and utilities."""

    def get_build_sql(self) -> str:
        """HNSW index build with production tuning."""
        return (
            "-- Bump maintenance_work_mem for index build\n"
            "SET maintenance_work_mem = '16GB';\n"
            "SET max_parallel_maintenance_workers = 4;\n"
            "\n"
            "-- Build HNSW index concurrently (no lock)\n"
            "CREATE INDEX CONCURRENTLY docs_hnsw_idx\n"
            "ON documents USING hnsw (embedding vector_cosine_ops)\n"
            "WITH (\n"
            "    m = 16,\n"
            "    ef_construction = 256\n"
            ");\n"
            "\n"
            "-- Revert maintenance_work_mem after build\n"
            "RESET maintenance_work_mem;\n"
            "RESET max_parallel_maintenance_workers;\n"
            "\n"
            "-- Analyze table for query planner\n"
            "VACUUM (ANALYZE) documents;"
        )

    def get_ef_search_sweep(self) -> str:
        """Recall vs ef_search sweep script."""
        return (
            "-- Create a validation set of known queries\n"
            "-- with exact nearest neighbors\n"
            "\n"
            "-- Sweep ef_search values\n"
            "DO $$\n"
            "DECLARE\n"
            "    ef_values INT[] :=\n"
            "        ARRAY[10, 20, 40, 80, 160, 320, 640];\n"
            "    ef INT;\n"
            "    recall FLOAT;\n"
            "    latency_ms FLOAT;\n"
            "BEGIN\n"
            "    FOREACH ef IN ARRAY ef_values LOOP\n"
            "        EXECUTE format(\n"
            "            'SET LOCAL hnsw.ef_search = %s', ef);\n"
            "        \n"
            "        -- Measure recall and latency\n"
            "        EXECUTE format(\n"
            "            'EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)\n"
            "             SELECT id FROM documents\n"
            "             ORDER BY embedding <=>\n"
            "                 ''[0.1,...]''::vector\n"
            "             LIMIT 10');\n"
            "        \n"
            "        RAISE NOTICE\n"
            "            'ef_search=%, recall=%, latency=%ms',\n"
            "            ef, recall, latency_ms;\n"
            "    END LOOP;\n"
            "END $$;"
        )

    def get_query_tuning_sql(self) -> str:
        """Per-query ef_search tuning."""
        return (
            "-- Per-query ef_search with SET LOCAL\n"
            "BEGIN;\n"
            "SET LOCAL hnsw.ef_search = 100;\n"
            "\n"
            "SELECT id, title,\n"
            "       1 - (embedding <=> $1::vector)\n"
            "           AS similarity\n"
            "FROM documents\n"
            "WHERE category = 'technical'\n"
            "ORDER BY embedding <=> $1::vector\n"
            "LIMIT 10;\n"
            "COMMIT;\n"
            "\n"
            "-- EXPLAIN ANALYZE to verify index usage\n"
            "EXPLAIN (ANALYZE, BUFFERS)\n"
            "SELECT id FROM documents\n"
            "ORDER BY embedding <=> '[0.1,...]::vector'\n"
            "LIMIT 10;\n"
            "\n"
            "-- Look for:\n"
            "--   Index Scan using docs_hnsw_idx (good)\n"
            "--   Seq Scan on documents (bad — index not used)\n"
            "--   Buffers: shared hit=1234 read=56\n"
            "--     (high hit ratio = good, high read = cache miss)"
        )

    def get_iterative_scan_sql(self) -> str:
        """pgvector 0.8.0+ iterative scan for filtered queries."""
        return (
            "-- Enable iterative scan for filtered queries\n"
            "-- Fixes overfiltering: WHERE + vector search\n"
            "-- returning fewer results than LIMIT\n"
            "\n"
            "SET hnsw.iterative_scan = strict;\n"
            "-- Options: off (default), strict, relaxed_order\n"
            "\n"
            "-- max_scan_tuples: cap scan depth\n"
            "SET hnsw.max_scan_tuples = 20000;\n"
            "\n"
            "-- scan_mem_multiplier: cap scan memory\n"
            "SET hnsw.scan_mem_multiplier = 2;\n"
            "\n"
            "-- Filtered vector search with iterative scan\n"
            "SELECT id, title,\n"
            "       1 - (embedding <=> $1::vector)\n"
            "           AS similarity\n"
            "FROM documents\n"
            "WHERE category = 'technical'\n"
            "  AND created_at > '2026-01-01'\n"
            "ORDER BY embedding <=> $1::vector\n"
            "LIMIT 10;\n"
            "\n"
            "-- Add B-tree index on filter columns\n"
            "CREATE INDEX CONCURRENTLY\n"
            "    docs_category_idx\n"
            "ON documents (category);\n"
            "\n"
            "-- For extremely selective filters,\n"
            "-- consider partitioning or partial index\n"
            "CREATE INDEX CONCURRENTLY\n"
            "    docs_tech_hnsw_idx\n"
            "ON documents USING hnsw (embedding vector_cosine_ops)\n"
            "WHERE category = 'technical';"
        )

    def get_memory_config(self) -> str:
        """Memory configuration for production."""
        return (
            "# postgresql.conf for pgvector production\n"
            "# (8GB host example)\n"
            "\n"
            "# shared_buffers: 25-40% of RAM\n"
            "shared_buffers = '2GB'\n"
            "\n"
            "# effective_cache_size: 75% of RAM\n"
            "effective_cache_size = '6GB'\n"
            "\n"
            "# maintenance_work_mem: 8-16GB during builds\n"
            "# Set per-session, not system-wide\n"
            "# SET maintenance_work_mem = '16GB';\n"
            "\n"
            "# work_mem: for hybrid queries with sorts\n"
            "work_mem = '64MB'\n"
            "\n"
            "# Parallel workers\n"
            "max_parallel_workers_per_gather = 2\n"
            "max_parallel_workers = 4\n"
            "max_parallel_maintenance_workers = 4\n"
            "\n"
            "# SSD optimization\n"
            "random_page_cost = 1.1\n"
            "effective_io_concurrency = 200"
        )

    def get_docker_shm_config(self) -> str:
        """Docker shared memory for parallel builds."""
        return (
            "# docker-compose.yml\n"
            "# Parallel workers communicate through shared memory\n"
            "# Default /dev/shm is 64MB — too small for HNSW builds\n"
            "# Match shm_size to maintenance_work_mem\n"
            "\n"
            "services:\n"
            "  pgvector:\n"
            "    image: pgvector/pgvector:0.8.0-pg16\n"
            "    shm_size: '2gb'  # match maintenance_work_mem\n"
            "    # ... rest of config"
        )

    def get_production_checklist_sql(self) -> str:
        """Production verification queries."""
        return (
            "-- 1. Verify index is used\n"
            "EXPLAIN (ANALYZE, BUFFERS)\n"
            "SELECT id FROM documents\n"
            "ORDER BY embedding <=> '[0.1,...]'::vector\n"
            "LIMIT 10;\n"
            "\n"
            "-- 2. Check index size\n"
            "SELECT pg_size_pretty(\n"
            "    pg_relation_size('docs_hnsw_idx'));\n"
            "\n"
            "-- 3. Check cache hit ratio\n"
            "SELECT sum(heap_blks_hit) / NULLIF(\n"
            "    sum(heap_blks_hit) +\n"
            "    sum(heap_blks_read), 0)\n"
            "    AS cache_hit_ratio\n"
            "FROM pg_statio_user_tables\n"
            "WHERE relname = 'documents';\n"
            "\n"
            "-- 4. Enable pg_stat_statements\n"
            "CREATE EXTENSION IF NOT EXISTS\n"
            "    pg_stat_statements;\n"
            "\n"
            "-- 5. Warm cache after deploy\n"
            "SELECT pg_prewarm('docs_hnsw_idx');\n"
            "\n"
            "-- 6. Check for dead tuples\n"
            "SELECT n_dead_tup, n_live_tup\n"
            "FROM pg_stat_user_tables\n"
            "WHERE relname = 'documents';\n"
            "\n"
            "-- 7. Monitor active connections\n"
            "SELECT count(*) FROM pg_stat_activity\n"
            "WHERE state = 'active';"
        )

    def get_tuning_presets(self) -> dict:
        """Tuning presets for different workloads."""
        return {
            "fast": {
                "m": 12,
                "ef_construction": 128,
                "ef_search": 10,
                "maintenance_work_mem": "4GB",
                "shared_buffers": "25% RAM",
                "expected_recall": "85-90%",
                "use_case": "Product search, speed > precision",
            },
            "balanced": {
                "m": 16,
                "ef_construction": 256,
                "ef_search": 80,
                "maintenance_work_mem": "8GB",
                "shared_buffers": "30% RAM",
                "expected_recall": "95-97%",
                "use_case": "Production default, general RAG",
            },
            "high_recall": {
                "m": 24,
                "ef_construction": 400,
                "ef_search": 160,
                "maintenance_work_mem": "16GB",
                "shared_buffers": "35% RAM",
                "expected_recall": "97-99%",
                "use_case": "RAG where missing chunk is costly",
            },
            "max_recall": {
                "m": 32,
                "ef_construction": 512,
                "ef_search": 320,
                "maintenance_work_mem": "16GB",
                "shared_buffers": "40% RAM",
                "expected_recall": "99%+",
                "use_case": "Benchmarking, maximum accuracy",
            },
        }

    def get_six_failures(self) -> dict:
        """Six common pgvector production failures."""
        return {
            "1_hnsw_oom": {
                "cause": "HNSW index builds running out of RAM",
                "fix": "Bump maintenance_work_mem to 8-16GB, "
                       "then revert after build",
            },
            "2_silent_seq_scan": {
                "cause": "Sequential scans replacing index scans silently",
                "fix": "Run EXPLAIN ANALYZE on every similarity "
                       "query in CI",
            },
            "3_type_mismatch": {
                "cause": "Implicit type mismatches disabling indexes",
                "fix": "Ensure operator class matches distance "
                       "function, cast explicitly",
            },
            "4_filter_mismatch": {
                "cause": "Pre-filter/post-filter returning wrong results",
                "fix": "Use pgvector 0.8.0 iterative scans, "
                       "add B-tree indexes on filter columns",
            },
            "5_write_contention": {
                "cause": "Concurrent writes contending with "
                         "index maintenance",
                "fix": "Use CREATE INDEX CONCURRENTLY, "
                       "defer maintenance to off-peak",
            },
            "6_cold_cache_p99": {
                "cause": "Cold-cache p99 spikes after deploys",
                "fix": "Warm caches with pg_prewarm after deploys, "
                       "size shared_buffers correctly",
            },
        }

pgvector Performance Tuning Checklist

  • [ ] Five parameters drive 90% of performance: maintenance_work_mem, ef_construction, ef_search, m, shared_buffers
  • [ ] maintenance_work_mem: 8-16 GB for HNSW builds, per-build, revert after
  • [ ] ef_construction: 256-512 for production-grade indexes
  • [ ] ef_search: 10-200, tunable per query with SET LOCAL
  • [ ] m: 8-32, default 16, graph connections per node
  • [ ] shared_buffers: 25-40% of total RAM
  • [ ] Use HNSW unless you have a reason not to — strong speed and recall tradeoff
  • [ ] HNSW works before table is fully loaded, accepts inserts incrementally
  • [ ] Tune ef_search to the recall you need — give index enough memory to stay resident
  • [ ] Index that spills to disk has long latency tail no parameter will fix
  • [ ] ef_search=10 for product search where speed beats precision
  • [ ] ef_search=40 for recommendations where users tolerate ~50ms latency
  • [ ] ef_search=100-200 for content similarity or RAG where missing chunk is costly
  • [ ] Recall rises monotonically with ef_search; latency rises faster than linearly
  • [ ] Recall climbs sharply 10-40, slowly to 95-99% by 80-160, asymptotes after
  • [ ] Sweep ef_search: 20, 40, 80, 160, 320, 640 — plot recall vs execution time
  • [ ] Use SET LOCAL hnsw.ef_search = 100 for per-query tuning in transactions
  • [ ] Run EXPLAIN ANALYZE on every similarity query in CI
  • [ ] Look for Index Scan (good) vs Seq Scan (bad — index not used)
  • [ ] EXPLAIN (ANALYZE, BUFFERS) shows cache hits vs physical reads
  • [ ] Check cache hit ratio: high hit = good, high read = cache miss
  • [ ] Six common failures: HNSW OOM, silent seq scans, type mismatches, filter mismatches, write contention, cold-cache p99
  • [ ] Fix HNSW OOM: bump maintenance_work_mem to 8-16GB, revert after
  • [ ] Fix silent seq scans: run EXPLAIN ANALYZE in CI on all similarity queries
  • [ ] Fix type mismatches: ensure operator class matches distance function
  • [ ] Fix filter mismatches: use pgvector 0.8.0 iterative scans
  • [ ] Fix write contention: CREATE INDEX CONCURRENTLY, defer maintenance off-peak
  • [ ] Fix cold-cache p99: warm caches with pg_prewarm after deploys
  • [ ] pgvector 0.8.0 iterative scans fix overfiltering (WHERE + vector returning fewer than LIMIT)
  • [ ] hnsw.iterative_scan: off (default), strict, relaxed_order
  • [ ] hnsw.max_scan_tuples: default 20,000, caps scan depth
  • [ ] hnsw.scan_mem_multiplier: default 1, caps scan memory as multiple of work_mem
  • [ ] Raise scan_mem_multiplier first if max_scan_tuples alone doesn't improve recall
  • [ ] Add B-tree indexes on filter columns for WHERE clause support
  • [ ] For extremely selective filters: partitioning or partial index may work better
  • [ ] work_mem: 64-128 MB for hybrid queries with sorts or joins
  • [ ] effective_cache_size: 75% of RAM for query planner
  • [ ] Docker shm_size: 2gb to match maintenance_work_mem — default 64MB too small
  • [ ] Parallel workers communicate through shared memory — shm_size must match
  • [ ] max_parallel_maintenance_workers: 4 for parallel index builds
  • [ ] Use halfvec to halve storage while preserving quality
  • [ ] binary quantization (pgvector 0.8.0+) reduces memory further
  • [ ] For 10K-50K vectors: parallel sequential scan is fast enough — skip index
  • [ ] For 100% recall in partitioned datasets: skip index, use per-partition scan
  • [ ] VACUUM (ANALYZE) documents after index creation for query planner stats
  • [ ] Enable pg_stat_statements for query monitoring
  • [ ] Warm caches with pg_prewarm after deploys
  • [ ] Track: index size, cache hit ratio, query latency p50/p95/p99, recall
  • [ ] Monitor dead tuples: pg_stat_user_tables for bloat
  • [ ] Monitor active connections: pg_stat_activity
  • [ ] Build indexes with CREATE INDEX CONCURRENTLY to avoid locks
  • [ ] Version embedding models alongside vectors for consistency
  • [ ] Read pgvector tutorial for setup
  • [ ] Read HNSW vs IVFFlat for index selection
  • [ ] Read HNSW explained for algorithm details
  • [ ] Read pgvector Docker for deployment
  • [ ] Read semantic search for search
  • [ ] Test: EXPLAIN ANALYZE shows Index Scan not Seq Scan
  • [ ] Test: ef_search sweep finds optimal recall/latency point
  • [ ] Test: iterative scan returns correct results with filters
  • [ ] Test: maintenance_work_mem bump speeds HNSW build 10-50x
  • [ ] Test: pg_prewarm eliminates cold-cache p99 spikes
  • [ ] Test: halfvec halves storage without recall degradation
  • [ ] Document parameter choices, ef_search sweep results, and monitoring plan

FAQ

What are the most important pgvector performance parameters?

Five parameters drive 90% of pgvector production performance: maintenance_work_mem, ef_construction, ef_search, m, and shared_buffers. SelfHostDev: "Five parameters drive 90% of pgvector production performance: maintenance_work_mem (8-16 GB for HNSW builds), ef_construction (256-512 for production-grade indexes), ef_search (10-200, tunable per query), m (8-32, default 16), and shared_buffers (25-40% of total RAM). maintenance_work_mem is per-build — bump it during index creation, then revert. Do not leave it at 16 GB system-wide. Set shared_buffers to 25-40% of RAM. Below that, HNSW index gets evicted under read load." ParadeDB: "Most pgvector performance comes from three decisions: use HNSW unless you have a reason not to, tune ef_search to the recall you need, and give the index enough memory to stay resident." Rivestack: "HNSW is usually the right pgvector index for production semantic search. Strong speed and recall tradeoff, works before table is fully loaded."

How do you tune hnsw.ef_search for optimal recall and latency?

Sweep ef_search from 10 to 640 against a labeled validation set, measure recall and latency, and pick the value where recall meets your target. NerdLevelTech: "Recall rises monotonically with ef_search; query latency rises faster than linearly because each candidate costs a distance computation. Recall climbs sharply between ef_search=10 and 40, climbs more slowly to 95-99% by 80-160, and asymptotes after that. Repeat with SET LOCAL hnsw.ef_search set to 20, 40, 80, 160, 320, 640. Plot recall vs execution time." SelfHostDev: "ef_search=10 for product search where speed beats precision. ef_search=40 for recommendations where users tolerate 50ms. ef_search=100-200 for content similarity or RAG where missing the right chunk costs more than latency." ParadeDB: "Increase ef_search until recall meets your target, then stop. Cost grows roughly linearly with this value." Use SET LOCAL for per-query tuning: BEGIN; SET LOCAL hnsw.ef_search = 100; SELECT ...; COMMIT;

How do you prevent sequential scans in pgvector queries?

Run EXPLAIN ANALYZE on every similarity query in CI to detect when PostgreSQL falls back to sequential scans instead of using the HNSW index. SelfHostDev: "Six common production failures include sequential scans replacing index scans silently. Fix: run EXPLAIN ANALYZE on every similarity query in CI." Rivestack: "Common causes: index not being used (check EXPLAIN), ef_search set too high, index spilled out of cache, filters missing supporting B-tree indexes, query missing LIMIT, concurrency saturating CPU. Start with EXPLAIN (ANALYZE, BUFFERS) and measure cache hit ratio. BUFFERS output shows whether query time is driven by cache hits or physical reads." ParadeDB: "Always check with EXPLAIN. A vector index that spills to disk has a long latency tail no parameter will fix." Prevention: (1) Run EXPLAIN ANALYZE in CI. (2) Ensure operator class matches distance function. (3) Check for implicit type mismatches. (4) Add B-tree indexes on filter columns. (5) Use LIMIT on all queries. (6) Monitor cache hit ratio.

How do you handle filtered vector search in pgvector 0.8.0+?

Use pgvector 0.8.0 iterative index scans with hnsw.iterative_scan, hnsw.max_scan_tuples, and hnsw.scan_mem_multiplier to maintain recall under selective WHERE filters. AWS: "pgvector 0.8.0 introduced iterative index scans, which fix the overfiltering problem. Before 0.8.0, a query combining WHERE clause with vector search often returned fewer results than LIMIT asked for, because index returned top-k candidates before filter was applied. Two parameters bound the scan: hnsw.max_scan_tuples (default 20,000) caps how far scan walks the index, and hnsw.scan_mem_multiplier (default 1) caps memory as multiple of work_mem. Raise scan_mem_multiplier first if max_scan_tuples alone does not improve recall." NerdLevelTech: "Three modes controlled by hnsw.iterative_scan GUC (default off)." ParadeDB: "If a filter is extremely selective, partitioning or a partial index may work better than asking one large ANN index to scan deeper."

How do you optimize pgvector memory and storage for production?

Size shared_buffers to 25-40% of RAM, use halfvec to halve storage, ensure HNSW index stays memory-resident, and bump maintenance_work_mem during index builds. SelfHostDev: "Set shared_buffers to 25-40% of RAM. Below that, HNSW index gets evicted under read load. Above that, OS page cache loses ground. maintenance_work_mem is per-build — bump to 8-16 GB during index creation, then revert. work_mem matters for hybrid queries with sorts or joins — 64-128 MB is usually enough." ParadeDB: "Index builds are bounded by maintenance_work_mem. If build does not fit, it spills and slows down sharply. At query time, performance depends on index staying in cache. Size shared_buffers and OS cache so working set of HNSW graph stays resident." NerdLevelTech: "Halve storage with halfvec. Docker shm_size: 2gb to match maintenance_work_mem — parallel workers communicate through shared memory. Default /dev/shm is 64 MB, build aborts without matching shm_size." AWS: "For tables with 10,000-50,000 vectors, parallel sequential scan is fast enough — skip index entirely."


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →