feat: parquet loading, pgvector support, multi-table datasets, and cohere-10m vector dataset - #67
Open
rebasedming wants to merge 24 commits into
Open
feat: parquet loading, pgvector support, multi-table datasets, and cohere-10m vector dataset#67rebasedming wants to merge 24 commits into
rebasedming wants to merge 24 commits into
Conversation
rebasedming
requested review from
jamessewell and
philippemnoel
as code owners
August 31, 2026 23:22
schema.yaml now accepts a tables list (one entry per table with its own columns) as an alternative to the single top-level table/columns form. The loader resolves one data source per table under data/ with the same preference order as before (<table>.parquet, <table>.csv, <table>/ shard directory), loads tables sequentially, and drops every table. Validation rejects mixed forms, nested entries, duplicates, and names the failing table in per-backend column-type errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds smallint/int2 to both conversion paths (CSV string parsing and parquet value mapping) with int16 bounds checks. Conversion happens in the shared RowSource layer, so every backend receives a Go int16. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vectors now flow through the shared row-source layer as neutral []float32; the postgres driver wraps them into pgvector.Vector at insert (COPY codec), and ES/OpenSearch marshal them as plain JSON arrays. The ES bulk path routes a schema _id column to the document id (ES reserves _id in sources). Vector schema validation now admits elasticsearch/opensearch. cohere-10m gains an elasticsearch backend: dense_vector mapping (cosine, float32 HNSW m=16/ef_construction=64 to mirror pgvector) with emb excluded from _source, an ES service in the compose file, and a kNN scenario in vector.js with ES_NUM_CANDIDATES as the recall knob. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stat-group and ingest-legend labels now read "paradedb 0.25.6" style, extracted from the captured backend config (pg_search composite record, PostgreSQL banner, or bare version string). Re-evaluated on every update since config can arrive after the label is first rendered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shared HTTP client caps requests at 15 minutes, which a _forcemerge to one segment on a 44GB vector index exceeds (the merge completed server-side; only the loader's post step reported failure). Operations may now carry a "timeout" duration; cohere-10m's forcemerge gets 2h. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stdlib-only sweep of num_candidates against the held-out ground truth, using the same kNN request shape as k6/vector.js. Ground truth ships as JSON (query_id 1..100 in query_vectors.json row order) so the k6 host needs no pyarrow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measured on the loaded 10m single-segment index: 40 candidates (the old default) sits at 0.835 recall@10; 150 reaches 0.951. Full curve recorded in the README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sweeps paradedb.vector_cluster_max_probe running the exact k6 query shape once per held-out vector via docker exec psql; same ground truth and intersection logic as the ES sweep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
48g ES vs 8g pg containers would hand ES the whole index in page cache while the others thrash. All three engines now get the same 8g cgroup (ES heap 4g per its half-of-container guidance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1m slice of the Cohere corpus targeting a separate benchmark_1m database so the 10m tables survive on the same containers. All containers capped at the same 8g; both indexes fit fully in cache, complementing the 10m data-larger-than-memory regime. Ships the 1m ground truth as JSON. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pg_search registers its bm25 vector opclasses only when pgvector already exists at install time; the old order left vector_cosine_ops undefined in any fresh database. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same dense_vector mapping as 10m; ES scenario added to vector.js with num_candidates env-tunable to the measured recall operating point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ES num_candidates=80 (0.955) vs paradedb max_probe=0.035 (0.958) against the 1m ground truth; curves in the README. pgvector scenario removed from vector.js (no HNSW index at 1m), ES stays unmerged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the paradedb.com performance-section shape — ~1%-selective full-text
filter ('battle') gating the kNN — mirroring upstream knn_top10_1pct.sql.
Both indexes now cover text (pdb unicode_words with english stemmer, ES
english analyzer, so the filter sets match); vector.js gains filtered
scenarios sharing a chart, with the pdb filtered probe on its own
connection string; recall scripts accept --filter-term and the 1pct
ground truth ships as JSON.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1m comparison runs the as-loaded multi-segment index (the calibrated num_candidates=80 operating point is measured against it); merging to one segment would silently change the geometry recall was tuned on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unfiltered re-measured after both rebuilds: ES num_candidates=95 (0.961) vs pdb max_probe=0.035 (0.959). Filtered shape saturates at 1.000 on both; cheapest saturation points pinned (ES 40, pdb 0.05). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Text indexed on both engines (filtered shape), no ES force-merge, vector.js ported from 1m (pdb + ES, filtered + unfiltered, VUS knob). pgvector dropped from the 10m matchup as at 1m. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pdb max_probe=0.02 and ES num_candidates=20 both measure 0.999 on the 1%-filtered shape; the previous 1.000 points paid 2-2.5x the work for one hit in a thousand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds everything needed to run vector benchmarks against the Cohere Wikipedia 10M dataset (the same corpus used by paradedb/paradedb CI), comparing the pg_search vector index against pgvector HNSW — plus multi-table dataset loading, groundwork for normalized-dataset benchmarks (e.g. StackOverflow joins).
Loader / driver changes
loader loadnow acceptsdata.parquet, or adata/directory of sharded parquet files (the S3 layout), alongsidedata.csv. Reading is streamed per row group; shards are chained with a column-consistency check.schema.yamlaccepts atableslist (one entry per table with its own columns) as an alternative to the single top-leveltable/columnsform. Each table resolves its own data source underdata/with the same preference order (<table>.parquet,<table>.csv,<table>/shard directory); tables load sequentially anddropremoves all of them. Validation rejects mixed forms, nested entries, and duplicates, and names the failing table in per-backend column-type errors. Single-table datasets are unchanged.vector(n)schema type: CSV cells parse JSON arrays; parquetlist<float>columns map directly topgvector.Vector.smallintschema type: supported on both CSV and parquet paths with int16 bounds checks. Conversion happens in the shared RowSource layer, so every backend receives a Goint16(pgx encodesint2over binary COPY; ClickHouse DDL should useInt16).AfterConnect(best-effort), and a newTypeReloaderhook resets the pool afterprescripts run — required becausepre.sqldoesCREATE EXTENSION vectoron connections that predate the extension.New dataset:
datasets/cohere-10m/paradedb/paradedbbenchmarks/datasets/cohere(cohere_wiki,emb vector(1024)).post.sqlbuilds the real pg_search vector index (USING bm25 ... emb vector_cosine_ops) vs pgvector HNSW, with the recall knobs (paradedb.vector_cluster_max_probe,hnsw.ef_search) set viaALTER DATABASEand documented as the operating points to tune.cohere_queries.parquetinto a k6 query pool.Testing
pgvector.Vectorreaches the driver, sharded-directory loading, multi-table schema validation/expansion, per-table data resolution, and smallint conversion/overflow on both paths.dropremoved both tables.smallintverified end-to-end from both CSV and parquet into realint2columns (including bounds value 32767).🤖 Generated with Claude Code