{"id":51085265,"url":"https://github.com/mayflower/pgturbohybrid","last_synced_at":"2026-06-23T21:02:44.312Z","repository":{"id":360041924,"uuid":"1248074158","full_name":"mayflower/pgturbohybrid","owner":"mayflower","description":"Hot-hatch hybrid vector + BM25 search for PostgreSQL on top of pgvector","archived":false,"fork":false,"pushed_at":"2026-06-18T08:00:27.000Z","size":10730,"stargazers_count":7,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T08:20:43.171Z","etag":null,"topics":["bm25","hybrid","hybrid-search","pgvector","postgres-extension","postgresql","rag","retrieval","turboquant","vector-search"],"latest_commit_sha":null,"homepage":"https://github.com/mayflower/pgturbohybrid#readme","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"postgresql","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mayflower.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":"MAINTAINERS.md","copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-24T06:40:35.000Z","updated_at":"2026-06-18T08:00:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mayflower/pgturbohybrid","commit_stats":null,"previous_names":["mayflower/pgturbohybrid"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mayflower/pgturbohybrid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayflower%2Fpgturbohybrid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayflower%2Fpgturbohybrid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayflower%2Fpgturbohybrid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayflower%2Fpgturbohybrid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mayflower","download_url":"https://codeload.github.com/mayflower/pgturbohybrid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayflower%2Fpgturbohybrid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34706579,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bm25","hybrid","hybrid-search","pgvector","postgres-extension","postgresql","rag","retrieval","turboquant","vector-search"],"created_at":"2026-06-23T21:02:42.923Z","updated_at":"2026-06-23T21:02:44.305Z","avatar_url":"https://github.com/mayflower.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/logo.png\" alt=\"pgturbohybrid logo with a compact hot hatchback theme\" width=\"720\"\u003e\n\u003c/p\u003e\n\n# pgturbohybrid\n\nThis README helps you understand what `pgturbohybrid` does, when hybrid search\nis useful, how to install it, how to create your first index, and how to check\nwhether the fast path is working.\n\n\u003e The hot hatch of pgvector hybrid search: practical PostgreSQL retrieval with\n\u003e a surprising turn of speed.\n\n[![build](https://github.com/mayflower/pgturbohybrid/actions/workflows/build.yml/badge.svg)](https://github.com/mayflower/pgturbohybrid/actions/workflows/build.yml)\n![Status: alpha](https://img.shields.io/badge/status-alpha-orange)\n![PostgreSQL 14–19](https://img.shields.io/badge/PostgreSQL-14--19-336791)\n![PostgreSQL extension](https://img.shields.io/badge/PostgreSQL-extension-336791)\n\n`pgturbohybrid` is a PostgreSQL extension for hybrid dense-vector + lexical\nretrieval on top of pgvector. It combines pgvector dense nearest-neighbor search\nwith PostgreSQL text search using BM25-style ranking. BM25, short for Best\nMatching 25, is a classic keyword-ranking method that rewards exact term\nmatches without requiring embeddings.\n\nIt is a standalone companion extension for pgvector: install pgvector first,\nthen install `pgturbohybrid` alongside it. The logo nods to hot hatchbacks:\ncompact, practical, quick, and daily-driver friendly. Not a million-euro\nhypercar, but still has room for groceries.\n\n## Status\n\n`pgturbohybrid` is alpha software.\n\n- APIs and on-disk index formats may change.\n- Benchmarks are early and should be repeated on your own data.\n- It is a good fit for evaluation, prototypes, and controlled experiments.\n- Treat production use as something to validate carefully, not assume.\n\nPer-feature maturity (stable public, experimental, research-only, diagnostic) is\ntracked in the [feature \u0026 maturity matrix](docs/feature-matrix.md).\n\n## Benchmarks\n\nDense (vector-only) retrieval on **dbpedia-openai-1M** (1,000,000 × 1536-d,\ncosine), top-10, run with the [vector-db-benchmark][vdbb] harness at\n`parallel=8` on a single node (AWS c6i, Intel Xeon Platinum 8375C). Every engine\nis measured at **steady state**: an untimed warm-up pass precedes the timed run\nso each engine's cache/buffers are hot. (Without this, engines that keep their\nworking set in a separate cache populated on first access — including\npgturbohybrid's native scan cache — are unfairly penalized against engines whose\nindex is already warm in shared buffers from the build.)\n\n| engine | recall@10 | queries/s | mean latency |\n|---|---:|---:|---:|\n| **pgturbohybrid `dense`** | 0.836 | **5739** | 1.27 ms |\n| **pgturbohybrid `high_recall`** | **0.983** | **2800** | 2.71 ms |\n| weaviate | 0.977 | 2633 | 2.90 ms |\n| pgvector (HNSW) | 0.979 | 1770 | 4.37 ms |\n| qdrant | 0.986 | 853 | 9.24 ms |\n| milvus | 0.988 | 750 | 10.39 ms |\n\n- `dense` is the speed profile (4-bit, no rescore): the highest throughput here,\n  ~3.2× pgvector, at recall 0.84 — use it when approximate recall is acceptable.\n- `high_recall` is the exact-free high-recall profile (4-bit + heap-band\n  rescore): **0.983 recall at 2800 q/s** — the best recall-per-throughput in this\n  set. It beats pgvector on both recall *and* throughput, and delivers ~3× the\n  throughput of qdrant/milvus at near-equal recall.\n\nSingle machine, single dataset — repeat on your own data and hardware. 4-bit\nquantization is strongest on cosine / inner-product embeddings; high-dimensional\nL2 (e.g. GIST-960) is a weaker case where recall holds up but latency does not.\n\n[vdbb]: https://github.com/johannhartmann/vector-db-benchmark\n\n## What It Does\n\nTurboHybrid, the feature provided by `pgturbohybrid`, aims to make hybrid search\nfeel like a normal PostgreSQL index:\n\n- combines pgvector dense retrieval with PostgreSQL text search/BM25-style\n  retrieval\n- fuses dense and lexical candidates inside one `turbohybrid` index access\n  method\n- uses reciprocal-rank fusion, or RRF, by default. RRF combines ranked result\n  lists by position instead of trying to compare unlike score scales directly.\n- aims to avoid the overhead of SQL-level two-index RRF plans for common\n  top-k queries\n- keeps pgvector unmodified\n\nThe basic idea: dense search helps with meaning, lexical search helps with exact\nwords, product names, IDs, and the weird little terms users actually type.\nRRF is the fusion method that combines the two ranked candidate lists.\nFor score-level experiments, `turbohybrid_query(fusion =\u003e 'calibrated')` uses\nmonotonic dense/BM25 score normalization, chooses a dense alpha from query shape\nwhen `alpha` is omitted, and can add a small bonus for candidates that appear in\nboth branches. This is a separate score-fusion mode; it does not preserve RRF\nsemantics and does not enable the `fast_weighted` BM25 score-bound pruning path.\n\n```sql\nSET turbohybrid.calibrated_fusion_both_match_bonus = 0.06;\nSET turbohybrid.calibrated_fusion_identifier_bm25_alpha = 0.35;\nSET turbohybrid.calibrated_fusion_broad_dense_alpha = 0.70;\nSET turbohybrid.calibrated_fusion_default_alpha = 0.50;\n\nSELECT id\nFROM documents\nORDER BY embedding \u003c~\u003e turbohybrid_query(\n  vector_query =\u003e $1,\n  text_query =\u003e $2,\n  fusion =\u003e 'calibrated'\n)\nLIMIT 10;\n```\n\nInspect `calibrated_fusion_enabled`,\n`calibrated_fusion_query_shape`, `calibrated_fusion_alpha_effective`,\n`calibrated_fusion_both_match_bonus`,\n`calibrated_fusion_dense_norm_mode`, and\n`calibrated_fusion_bm25_norm_mode` in `turbohybrid_last_scan_stats()`.\n\n## Multivector Late Interaction\n\n`pgturbohybrid` includes a public `multivector` column type for\nlate-interaction retrieval models such as ColBERT-style MaxSim. A multivector\nstores several same-dimensional token vectors for one document row. The native\ngraph build expands those token vectors into graph subnodes, while query output\nis aggregated back to heap rows so the same document is not returned multiple\ntimes.\n\n```sql\nCREATE TABLE passages (\n  id bigint PRIMARY KEY,\n  colbert multivector\n);\n\nINSERT INTO passages VALUES\n  (1, turbohybrid_multivector(ARRAY[\n    '[1,0,0]'::vector,\n    '[0,1,0]'::vector\n  ]));\n\nCREATE INDEX passages_colbert_idx\nON passages USING turbohybrid (\n  colbert multivector_cosine_turbohybrid_ops\n);\n\nSELECT id\nFROM passages\nORDER BY colbert \u003c~\u003e turbohybrid_query(\n  multivector_query =\u003e turbohybrid_multivector(ARRAY[\n    '[1,0,0]'::vector\n  ])\n)\nLIMIT 10;\n```\n\nContract, in brief: `vector_query` and `multivector_query` are mutually\nexclusive; hybrid multivector + `text_query` fuses document-keyed (RRF /\n`weighted` / `fast_weighted` / `calibrated` / `dbsf`); scans exact-rerank a\nbounded top-document prefix from the heap by default (no full f32 vectors stored\nin the index); and textual literal input is intentionally unsupported (construct\nfrom `vector[]` or `turbohybrid_multivector_from_float4(...)`).\n\nCandidate budgets, exact-rerank policy, document-node storage tiers\n(`f32`/`f16`/`sq8`/`proxy_only`/`centroid_only`), the native ColBERT candidate\nsources (`proxy_vector`, `document_nodes`, experimental `centroid_lite`,\nresearch-only `quantized_inverted_experimental`), and tuning live in\n**[docs/multivector-late-interaction.md](docs/multivector-late-interaction.md)**.\nFor generating embeddings locally, the companion `llama_embed` extension is\ndocumented in\n[docs/colbert-llama-extension.md](docs/colbert-llama-extension.md).\n\n## Native Sparse Retrieval (alpha)\n\n\u003e **Alpha / experimental.** The on-disk sparse format and SQL surface may change;\n\u003e version mismatches fail clearly and recommend `REINDEX`.\n\n`pgturbohybrid` stores and searches learned-sparse (SPLADE-style) vectors\nnatively via the `turbohybrid_sparse_vector` type, the `\u003c~*\u003e` distance operator,\nand the `sparse_ip_turbohybrid_ops` opclass. Sparse keys work alongside a dense\nor multivector graph, or stand alone (sparse-only / sparse+BM25), and fuse with\ndense/BM25 via RRF. Postings can be exact (f32) or quantized (q16/q8) with an\nexact top-band rerank.\n\n```sql\nCREATE INDEX ON docs USING turbohybrid (s sparse_ip_turbohybrid_ops);\nSELECT id FROM docs\nORDER BY s \u003c~*\u003e turbohybrid_query(sparse_query =\u003e q.s, final_k =\u003e 10)\nLIMIT 10;\n```\n\nSee [docs/sparse-embeddings.md](docs/sparse-embeddings.md) for the full type,\nindex shapes, quantization, fusion, GUCs, and stats, and\n[the `llama_embed` sparse API](docs/colbert-llama-extension.md#sparse-splade-output--alpha)\nfor generating sparse vectors.\n\n## When It Is Useful\n\nTry `pgturbohybrid` when you are evaluating:\n\n- RAG, or retrieval-augmented generation, over documents, tickets, support\n  content, or knowledge bases\n- semantic + lexical product or documentation search\n- queries where dense-only retrieval misses exact terms\n- queries where full-text search misses paraphrases\n- PostgreSQL-first systems that want hybrid retrieval without adding another\n  search service on day one\n\n## When Not To Use It Yet\n\nWait, or isolate it carefully, if you need:\n\n- stable on-disk index compatibility across releases\n- official pgvector support\n- production workloads without your own benchmark and relevance validation\n- a mature operational story for every PostgreSQL and pgvector combination\n\nAlpha means the paint is dry enough to touch, not enough to take through a car\nwash.\n\n## Install\n\nA reproducible **Nix development shell** (PostgreSQL 17 + pgvector + the\nextension, with `th-*` helper commands and benchmark shells) is documented in\n[CONTRIBUTING.md](CONTRIBUTING.md#nix-development-shell). For a system install,\nuse the manual steps below.\n\n### Manual install\n\nInstall pgvector first:\n\n```sh\ngit clone --depth 1 --branch v0.8.2 https://github.com/pgvector/pgvector.git ../pgvector\nmake -C ../pgvector\nmake -C ../pgvector install\n```\n\nThen build and install `pgturbohybrid`:\n\n```sh\ngit clone https://github.com/mayflower/pgturbohybrid.git\ncd pgturbohybrid\nmake\nmake install\n```\n\nCreate both extensions in your database:\n\n```sql\nCREATE EXTENSION vector;\nCREATE EXTENSION pgturbohybrid;\n```\n\nFor a repeatable local setup, you can also use:\n\n```sh\nPG_CONFIG=pg_config PGVECTOR_REF=v0.8.2 scripts/dev-install.sh\n```\n\n## Quick Start\n\n### Dense-Only Vector Search\n\nUse a one-column `turbohybrid` index when you only need vector retrieval:\n\n```sql\nCREATE TABLE documents (\n    id bigserial PRIMARY KEY,\n    embedding vector(3) NOT NULL,\n    body text NOT NULL\n);\n\nCREATE INDEX documents_dense_idx ON documents\nUSING turbohybrid (embedding vector_cosine_turbohybrid_ops);\n\nSELECT id, body\nFROM documents\nORDER BY embedding \u003c~\u003e turbohybrid_query(vector_query =\u003e $1::vector)\nLIMIT 10;\n```\n\nFor the common shapes there are shorter convenience wrappers that forward to\n`turbohybrid_query(...)`: `turbohybrid_dense_query($1)`,\n`turbohybrid_hybrid_query($1, $2)`, `turbohybrid_sparse_query($1)`, and\n`turbohybrid_multivector_query($1)` (each takes optional `final_k` / per-branch\nbudgets). See [docs/how-it-works.md](docs/how-it-works.md#convenience-query-constructors).\n\n### Hybrid Vector + Text Search\n\nAdd a `tsvector` key when queries use `text_query`:\n\n```sql\nCREATE TABLE documents (\n    id bigserial PRIMARY KEY,\n    embedding vector(3) NOT NULL,\n    body text NOT NULL,\n    body_tsv tsvector GENERATED ALWAYS AS (\n        to_tsvector('english', body)\n    ) STORED\n);\n\nINSERT INTO documents (embedding, body)\nVALUES\n    ('[1,0,0]', 'postgres vector search'),\n    ('[1,1,0]', 'hybrid search with bm25'),\n    ('[0,1,0]', 'lexical search in postgres'),\n    ('[0,0,1]', 'unrelated document');\n\nCREATE INDEX documents_turbohybrid_idx ON documents\nUSING turbohybrid (\n    embedding vector_cosine_turbohybrid_ops,\n    body_tsv bm25_tsvector_turbohybrid_ops\n);\n\nANALYZE documents;\n\nSELECT id, body\nFROM documents\nORDER BY embedding \u003c~\u003e turbohybrid_query(\n    vector_query =\u003e '[1,0,0]'::vector,\n    text_query =\u003e websearch_to_tsquery('english', 'postgres hybrid search')\n)\nLIMIT 10;\n```\n\nFor tables that also store a ColBERT document column, use ColBERT as a reranker\nfor a dense-vector + BM25 hybrid candidate set by keeping the first-stage hybrid\nquery on the vector index and reranking only the bounded heap rows:\n\n```sql\nWITH q AS (\n    SELECT\n        turbohybrid_query(\n            vector_query =\u003e '[1,0,0]'::vector,\n            text_query =\u003e websearch_to_tsquery('english', 'postgres hybrid search'),\n            dense_k =\u003e 200,\n            bm25_k =\u003e 200,\n            final_k =\u003e 200\n        ) AS hybrid_query,\n        turbohybrid_multivector(ARRAY[\n            '[1,0,0]'::vector,\n            '[0,1,0]'::vector\n        ]) AS colbert_query\n),\ncandidates AS MATERIALIZED (\n    SELECT d.id, d.body, d.colbert\n    FROM documents d, q\n    ORDER BY d.embedding \u003c~\u003e q.hybrid_query\n    LIMIT 200\n)\nSELECT c.id, c.body\nFROM candidates c, q\nORDER BY turbohybrid_multivector_maxsim(q.colbert_query, c.colbert) DESC\nLIMIT 10;\n```\n\nThis is the supported shape for ColBERT reranking a vector+BM25 hybrid today:\n`vector_query` and `multivector_query` still remain mutually exclusive inside\none `turbohybrid_query`, so the reranker query is passed to the scalar MaxSim\nfunction instead of being mixed into the first-stage index payload.\n\nCurrent DBpedia ColBERT benchmark evidence for this pattern is positive but\nstill bounded by the first-stage candidate window. With a dense+BM25 RRF\nfirst-stage window of 200 candidates and exact ColBERT MaxSim reranking over\nthat window, top-10 quality changed as follows:\n\n| corpus | stage | recall@10 | ndcg@10 | mrr@10 | map@10 |\n|---|---|---:|---:|---:|---:|\n| 50k docs / 25 queries | RRF first stage | 0.188000 | 0.135688 | 0.240000 | - |\n| 50k docs / 25 queries | exact ColBERT rerank | 0.308000 | 0.251833 | 0.460000 | - |\n| 1M docs / 381 queries | RRF first stage | 0.098838 | 0.072247 | 0.120932 | 0.041474 |\n| 1M docs / 381 queries | exact ColBERT rerank | 0.128778 | 0.132511 | 0.241557 | 0.106777 |\n\nOn the 1M run, exact ColBERT reranking improved recall@10 by `30.3%`,\nndcg@10 by `83.4%`, mrr@10 by `99.7%`, and map@10 by `157.5%` relative to\nthe RRF candidate ordering. The measured full-path latency for RRF retrieval\nplus exact ColBERT rerank over 200 candidates was p50 `30.745 ms`, p95\n`148.354 ms`, and p99 `367.001 ms` over 381 queries. The corresponding 50k\nrun measured p50 `63.966 ms`, p95 `156.849 ms`, and p99 `483.225 ms` over 25\nqueries.\n\nTreat these numbers as benchmark evidence for the rerank workflow, not as a\ndefault serving profile: this mode computes BEIR/qrel quality only and does not\nrun a full exact MaxSim admission oracle. Recall is also limited by the RRF\ncandidate window, so larger windows should be benchmarked when higher recall is\nthe target.\n\nDo not read this as evidence for a three-branch dense+BM25+ColBERT proxy\nretriever. On the same 1M DBpedia corpus, the current proxy-only ColBERT branch\nwas a fast but effectively dead candidate source (`recall@10 = 0.000262`,\n`ndcg@10 = 0.000364`), and naive RRF over dense, BM25, and that ColBERT branch\nreduced quality (`recall@10 = 0.010892`, `ndcg@10 = 0.005273`) compared with\ndense+BM25 RRF alone. Until native ColBERT candidate generation has stronger\nadmission evidence, use ColBERT as the bounded exact reranker shown above.\n\n`text_query` requires a turbohybrid index with a `tsvector` key. A dense-only\nindex accepts vector queries and rejects text or vector+text queries with a clear\nerror.\n\nUse `\u003c~-\u003e` for L2, `\u003c~#\u003e` for negative inner product, and `\u003c~\u003e` for cosine\nhybrid ordering. A longer copy-paste walkthrough lives in\n[examples/fast_start.sql](examples/fast_start.sql) and\n[docs/fast_setup.md](docs/fast_setup.md).\n\nMigration note: existing two-key hybrid indexes remain valid. Dense-only users\ncan now create smaller one-key indexes. To change an index from hybrid to\ndense-only, or from dense-only to hybrid, rebuild it with `DROP INDEX` /\n`CREATE INDEX` or `REINDEX` after changing the index definition.\n\n## Fast Defaults\n\nFresh sessions use the `latency` profile:\n\n```sql\nSHOW turbohybrid.profile;\n```\n\nThe default path uses a 4-bit quantized index, exact vector storage off,\nadaptive dense widening off, dense and BM25 candidate budgets of 100, RRF\nconstant 60, and the SQL `LIMIT` as the final result target when possible. In\nplain terms: create the default index, query with\n`ORDER BY ... turbohybrid_query(...) LIMIT n`, then inspect the scan stats\nbelow.\n\nPublic candidate and cache settings are intentionally capped in this alpha so a\nuser cannot set runaway per-query budgets in a shared PostgreSQL server. If you\nhit a cap during evaluation, please open an issue with the dataset size, query\nshape, and the settings you tried.\n\nFor the normal fast path, keep the query simple:\n\n```sql\nSELECT id, body\nFROM documents\nORDER BY embedding \u003c~\u003e turbohybrid_query(\n    vector_query =\u003e $1::vector,\n    text_query =\u003e websearch_to_tsquery('english', $2)\n)\nLIMIT 10;\n```\n\nThat is the \"daily driver\" mode: compact settings, fewer knobs, enough speed to\nbe interesting, and no need to pack a racing helmet.\n\n## Profile choice\n\nPick a profile by what the query workload needs; all are compact 4-bit,\nexact-free by default. Always validate on your own data — the guidance below is\nqualitative, and any numbers in `benchmarks/` are local synthetic examples, not\nportable performance claims.\n\n- **`latency`** (default): fastest. A good fit for easy corpora and\n  latency-sensitive serving where approximate recall is acceptable.\n- **`matched_recall`**: the compact comparison profile, intended to approximate\n  full-vector HNSW recall (pgvector/Qdrant) without exact storage. Treat it as a\n  comparison baseline and **validate its recall on your real workload** before\n  relying on it.\n- **`high_recall`**: use when hard or ambiguous dense recall matters and you have\n  latency headroom. It recovers recall on hard queries by using wider\n  `graph_ef_search` / `graph_oversampling` (and heuristic build) — i.e. by\n  searching more, at higher per-query latency. The recall gain comes from those\n  wider search windows, not from the opt-in features below. (A local synthetic\n  hard case is documented in `benchmarks/README.md`.)\n- **`quality`**: relevance-oriented (stronger, slower). **Benchmark it before\n  making it a default** — its extra cost is only worth it if your data shows a\n  relevance gain.\n\nThe newer retrieval features — residual rerank, dense uncertainty retry, BM25\nheap-tsvector rerank, and final diversity — are **opt-in or profile-gated**, off\nin the default profiles, and should be **benchmarked separately** on your data\nbefore enabling. They change behavior independently of the profile's graph/search\nwindows, so measure them one at a time.\n\nThese are guidelines, not defaults to change: no profile's compiled defaults\nshould be retuned from synthetic benchmarks alone.\n\n## Deep Profile Tuning\n\nBeyond the profile choice above, TurboHybrid exposes `matched_recall`,\n`high_recall`, and `quality` modes plus `quantization_bits`, `exact_storage`,\nheap rescore, residual rerank, adaptive widening, uncertainty retry, entry\nsidecars, payload seeding, final diversity, and segment controls. The full\ntuning guide -- with worked examples and the `turbohybrid_last_scan_stats()`\nkeys to inspect for each knob -- is in\n**[docs/profile-tuning.md](docs/profile-tuning.md)**.\n\nNo profile's compiled defaults should be retuned from synthetic benchmarks alone.\n\n## Diagnostics\n\nAfter a query, check whether PostgreSQL used the expected TurboHybrid path:\n\n```sql\nSELECT turbohybrid_last_scan_stats();\nSELECT turbohybrid_last_scan_diagnosis();   -- single bottleneck label + key fields\nSELECT turbohybrid_index_stats('documents_turbohybrid_idx'::regclass);\nSELECT turbohybrid_simd_capabilities();\n```\n\n`turbohybrid_last_scan_diagnosis()` reduces the full stats JSON to the key dense\nhot-path fields and a single `diagnosis` label (for example `healthy_u8_x4`,\n`traversal_dominated`, `rescore_dominated`, or `scalar_lut_fallback`):\n\n```sql\nSELECT turbohybrid_last_scan_diagnosis() -\u003e\u003e 'diagnosis';\n```\n\nThe stable vs experimental vs diagnostic-only keys of\n`turbohybrid_last_scan_stats()` are documented in\n[docs/diagnostics-schema.md](docs/diagnostics-schema.md). For cache-scope\nsizing, per-backend memory multiplication, `turbohybrid_estimate_memory()`\nprojections, the read-only `turbohybrid_graph_repair_dry_run()` diagnostic, and\nVACUUM/REINDEX/upgrade guidance, see [docs/operations.md](docs/operations.md).\n\n## More Benchmark Snapshots\n\nThe headline dbpedia-1M dense comparison is in [Benchmarks](#benchmarks) above.\nAdditional **local snapshots** -- FIQA/OpenAI hybrid, DBPedia 1M hybrid, and a\ndense-only Turbovec reference, with full settings, baselines, and tables -- live\nin **[docs/benchmarks/local-snapshots.md](docs/benchmarks/local-snapshots.md)**.\nThey are local snapshots, not global claims; repeat them on your own hardware\nand query mix. Reproduction notes:\n[fiqa-openai.md](docs/benchmarks/fiqa-openai.md),\n[dbpedia_openai3_large.md](benchmarks/dbpedia_openai3_large.md),\n[benchmarks/README.md](benchmarks/README.md), and\n[bring-your-own-rag.md](docs/benchmarks/bring-your-own-rag.md).\n\n## How It Works, Short Version\n\n`pgturbohybrid` defines a `turbohybrid` PostgreSQL index access method over:\n\n- one pgvector `vector` column for dense retrieval\n- optionally, one `tsvector` column for lexical/BM25 retrieval\n\nDense-only indexes run the dense branch. Hybrid indexes can gather dense and\nBM25-style lexical candidates, fuse them with reciprocal-rank fusion, and return\nrows through PostgreSQL's normal `ORDER BY ... LIMIT` index-scan shape.\n\nIt depends on pgvector's SQL `vector` type but does not require pgvector to be\npatched. Some graph/index code is derived from pgvector's HNSW implementation;\nsee [NOTICE](NOTICE) and [docs/architecture.md](docs/architecture.md).\n\n## Documentation\n\n- [Feature \u0026 maturity matrix](docs/feature-matrix.md)\n- [Beta scope](docs/beta-scope.md)\n- [Roadmap](ROADMAP.md)\n- [How TurboHybrid works](docs/how-it-works.md)\n- [Easy fast setup](docs/fast_setup.md)\n- [Diagnostics schema](docs/diagnostics-schema.md)\n- [Operations guide](docs/operations.md)\n- [Storage format](docs/storage-format.md)\n- [Profile tuning](docs/profile-tuning.md)\n- [Sparse (SPLADE) embeddings](docs/sparse-embeddings.md)\n- [Multivector late interaction](docs/multivector-late-interaction.md)\n- [FIQA/OpenAI benchmark snapshot](docs/benchmarks/fiqa-openai.md)\n- [Bring-your-own RAG benchmark](docs/benchmarks/bring-your-own-rag.md)\n- [DBPedia OpenAI3-large benchmark spec](benchmarks/dbpedia_openai3_large.md)\n- [Benchmark methodology](benchmarks/README.md)\n- [Compatibility notes](docs/compatibility.md)\n- [Architecture notes](docs/architecture.md)\n- [Release policy](RELEASE.md)\n- [v0.1.0-alpha.2 release notes](docs/release-notes/v0.1.0-alpha.2.md)\n- [Release hygiene summary](RELEASE_HYGIENE.md)\n\n## Compatibility\n\nThe release target is PostgreSQL 14 through 19 (CI builds and runs the\nregression tests on every version; PostgreSQL 19 is tested against pgvector\n`master`). The pgvector compatibility target is pgvector 0.8.2 through current\npgvector `master`.\n\nSee [docs/compatibility.md](docs/compatibility.md) for the tested matrix and\nboundary notes. If pgvector changes its internal vector layout, `pgturbohybrid`\nshould fail directly rather than silently reading malformed data.\n\n## Contributing\n\nContributions are welcome, especially:\n\n- real benchmark reports with complete context\n- compatibility results across PostgreSQL, pgvector, CPU, and OS versions\n- bug reports with `EXPLAIN` output and diagnostic JSON\n- documentation fixes that make the alpha easier to evaluate safely\n\nStart with [CONTRIBUTING.md](CONTRIBUTING.md). For community expectations,\nsee [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), [SUPPORT.md](SUPPORT.md), and\n[SECURITY.md](SECURITY.md).\n\n## Attribution\n\n`pgturbohybrid` depends on pgvector and contains code derived from pgvector's\nHNSW implementation. pgvector is an excellent PostgreSQL vector search\nextension; `pgturbohybrid` is a separate experimental companion project, not an\nofficial pgvector project.\n\n`pgturbohybrid` is distributed under the PostgreSQL License. See\n[LICENSE](LICENSE) for the license text and [NOTICE](NOTICE) for pgvector\nattribution.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayflower%2Fpgturbohybrid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmayflower%2Fpgturbohybrid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayflower%2Fpgturbohybrid/lists"}