- Connect to a storage root: a local directory or an
s3://prefix. - Create a table with a BM25 index on the text and a vector index on the embedding.
- Append rows with embeddings from any model. Indexing happens as the rows are written.
- Query with
bm25_search,vector_search, orhybrid_search, which fuses the two with reciprocal rank fusion. - Filter and aggregate results in SQL, where each search function is a table.
- Read the files back with DuckDB to confirm they are plain Parquet.
Keep Parquet as the source of truth
Say you have millions of events, documents, or tickets already sitting as Parquet on S3, and you need full-text and vector search over them. The usual route is migration: copy the rows into a search cluster or a specialized vector database, then operate two systems and keep them in sync forever. The copy costs storage, the sync costs pipeline work, and every schema change lands twice. The alternative is a file-based architecture: index the data where it lives. Infino is an embedded retrieval library that writes BM25, vector, and scalar indexes into the Parquet files themselves. Each file it produces is a superfile, a spec-compliant Apache Parquet file with the index regions spliced in ahead of the standard footer, referenced byinf.* metadata keys that a
conformant Parquet reader ignores. One copy of the data serves hybrid search
and analytics together, and every Parquet tool already pointed at your bucket
keeps working.
Storage is wherever your data already sits. This walkthrough uses a local
directory so it runs anywhere. To make Parquet on object storage searchable
without moving it into another database, point the connection at the bucket
instead:
Build a persistent index
banking77 is 10,003 real questions customers asked an online bank, labeled with 77 intents. It is a good hybrid testbed because the questions mix exact terms where keyword search wins (SWIFT, Apple Pay, PIN) with everyday paraphrases where it loses. Infino is bring-your-own-embeddings: you compute vectors with whatever model you like and the engine indexes them (the embeddings guide covers dimension and metric rules). This guide uses a small local model, so the pipeline stays key-free. Embedding all 10,003 rows took about five seconds on a laptop:*.sf.parquet files under the table’s
directory. Those files are the whole system: data, keyword index, and vector
index in one place, durable and rebuildable, growing with the dataset rather
than with a cluster plan. Search performance stays stable as the table grows
because the indexes are persistent, not rebuilt per process or held per node
in RAM.
Query it: hybrid search and analytics
BM25 ranks rows by the exact tokens they share with the query. On a term of art it is precise. Scores are relevance, higher is better:hybrid_search runs both retrievers and fuses the two rankings with
reciprocal rank fusion, so a row both retrievers surface outranks a row only
one of them found:
Filtered vector search
Most production vector queries carry a filter: this tenant, this document type, this language. Infino pushes a text predicate into the vector search itself, so you get the k nearest rows that match the filter, not a thinned slice of the global top k:topic. Predicates on numeric or other scalar columns compose in SQL instead,
as below. That path filters after the top k rather than inside it, so
over-fetch when the predicate is selective.
Search as a relation in SQL
Search functions are table-valued, so a result set is a relation you can join, filter, and aggregate. One query answers a question no single search call can: which topics dominate the neighborhood of this query?bm25_search and hybrid_search are available as table functions the same
way, so a hybrid ranking can feed a join or a window function directly. This
is where hybrid search and analytics in one engine pays: the retrieval step is
the first stage of a SQL plan, not a separate system’s output stitched
together client-side. The SQL reference covers the table
functions.
Read the files back with DuckDB
The index did not take the data hostage. Open the same files with DuckDB, which knows nothing about Infino:_id, text, and topic. It does not see embedding, because
the vectors are consumed into the embedded index rather than stored twice as
a plain column, and the index regions are invisible to a standard reader.
pandas and pyarrow read the same files the same way (the Parquet interop
guide covers those paths). Your search index and
your analytics tables are one copy of the data.
The complete script
infino package.
Common questions
How do I run vector search over Parquet files on S3 without a separate vector database?
Connect to the bucket withinfino.connect("s3://bucket/prefix"), create a
table with a vector index, and append rows with their embeddings. The vector
index is written into the table’s Parquet files in that bucket, and
vector_search or the vector_search SQL function queries it from your own
process. There is no vector database to provision or keep in sync. The
storage guide covers credentials for each backend.
Can I add search to Parquet files I already have?
Yes, with one pass over the data. Read the existing rows and append them to an Infino table, and Infino writes new Parquet files with the indexes inside, into whatever bucket you point it at. Your original files are not modified. Because the new files are themselves standard Parquet, the analytics tools already pointed at your data can read them, so one copy can serve both search and analytics from then on.Which object stores does it work with?
Amazon S3, Google Cloud Storage, and Azure Blob Storage, plus local disk. The code is the same for all of them. Only the connection string changes.Can DuckDB, pandas, or pyarrow still read the data?
Yes. Each file Infino writes is spec-compliant Parquet, so any conformant reader opens it and sees the scalar and text columns. Two things differ from a plain Parquet table: the embedding column lives inside the vector index rather than as a Parquet column, and a raw read does not apply updates or deletes. The Parquet interop guide covers both.Does it work for AI agent memory?
Yes. Infino runs as a library inside the agent’s process, so a memory read is a function call against a pinned snapshot of the table, with no connection pool or server in between. Hybrid search suits agent memory because an agent’s queries mix exact names and identifiers with loosely worded recall. Agents that speak MCP can use the Infino MCP server, and the agent memory use case walks through the pattern.I have millions of events stored as Parquet on S3. How does this hold up?
The indexes persist in the files, so a query reads only the index regions and byte ranges it cannot prune, not the whole dataset. Warm queries run from cached bytes: a warm single-term BM25 query on a 1-million-document index returns in the microsecond range. The first query to touch a file on object storage pays a round trip of hundreds of milliseconds. Each table has one active writer at a time. Tradeoffs states the full envelope, and the engine’s benchmarks record ingest, cold, and warm numbers with the hardware they ran on.How does hybrid search combine keyword and vector results?
hybrid_search runs BM25 and vector search separately and merges the two
ranked lists with reciprocal rank fusion. A row gets credit for its rank in
each list, so a row both retrievers place near the top outranks a row only
one of them found. Scores are fusion scores, not BM25 or cosine values. The
search guide covers the arguments.
