User guide
This is a walkthrough of polign_db, from your first vector to a durable, searchable collection.
This guide assumes a server is already running — the
install & deploy guide gets you one in a minute
(download, unpack, ./polign-server). Every example uses polign,
the command-line client (in the release archive from the release after v0.1.0-rc.9, or
go build -tags cloud ./cmd/polign from a checkout); it talks to
http://localhost:23000 by default — point it elsewhere with -url
or $POLIGN_URL.
A server started with no flags keeps everything in memory — fine for trying things,
gone on restart. Start it with -store (a local directory like
fs:/var/lib/polign, or a bucket like s3://my-bkt/polign) and every
write is recorded before it's acknowledged; keyword search also needs it. The
install guide has the details.
Terminology
polign_db has a small vocabulary.
| Term | What it means |
|---|---|
| Vector | A list of numbers that represents the meaning of something, a sentence, an image or a product. Two things with similar meaning get vectors that are numerically close together. |
| Embedding model | The tool that turns your text (or images) into vectors. polign_db doesn't ship one — you bring any model you like (OpenAI, Cohere, a local model) and store what it produces. |
| Collection | A named group of vectors, like a table in a regular database. Created automatically the first time you write to it. |
| Metadata | Plain key–value labels you attach to each vector. The metadata is returned with every search hit, and usable for filtering. |
| Distance / score | How results are ranked. Vector search
returns a distance (smaller = closer match); keyword and hybrid search return
a score (larger = better match). |
Always embed your documents and your search queries with the same model. Vectors from different models live in different "spaces" and comparing them produces plausible-looking results. Pick one model per collection and stick with it.
Add your data
Each record is three things: an id you choose, the vector from your embedding model, and optional metadata. Writing a record whose id already exists overwrites it.
# one record: id "guide-1" in the collection "articles"
polign put articles guide-1 \
-values 0.12,-0.03,0.98 \
-meta title="Getting started" -meta url=/blog/start \
-meta text="How to get started with..."
Real vectors are long, so you won't type them — pass a file instead:
-values @embedding.json reads a JSON array, and -values - reads one
from stdin, so you can pipe your embedding script straight in. The first write creates the
collection and fixes its dimension: write a 768-number vector first, and every later vector
must also have 768 numbers.
Everything the CLI does is plain HTTP underneath, so curl always works too — and it's the way to batch up to 5,000 vectors per request:
# the same put, as raw HTTP curl -X PUT localhost:23000/v1/collections/articles/vectors/guide-1 \ -d '{"values": [0.12, -0.03, 0.98], "metadata": {"title": "Getting started"}}' # batch upsert — validated as a whole before anything is applied curl -X POST localhost:23000/v1/collections/articles/vectors:batch \ -d '{"vectors":[ {"id":"guide-2","values":[...],"metadata":{"title":"Filters"}}, {"id":"guide-3","values":[...],"metadata":{"title":"Hybrid search"}} ]}'
"text" metadata fieldIf you store each record's text under the metadata key "text" (as above),
keyword and hybrid search below work on it out of the box — that's the field the text
index reads by default.
Search
All searching is one command: polign search. What you give it decides which
kind of search you get.
Search by meaning
Embed your question with the same model you used for your documents, and pass the
resulting vector as -values. -k is how many results you want
back:
polign search articles -values @query.json -k 5 ID DISTANCE SCORE METADATA guide-1 0.0014 - {"title":"Getting started","url":"/blog/start",…} guide-3 0.0102 - {"title":"Hybrid search"}
Hits come back closest-first, each with its id, distance, and metadata — so the title and URL you stored earlier arrive with the result, ready to display.
Search by keyword
Give it -text instead of -values and you get classic keyword
search (BM25 — the ranking used by traditional search engines). No embedding model involved,
so exact words, names, and codes match reliably:
polign search articles -text "getting started" -k 5
Search both ways at once (hybrid)
Give it -values and -text together and the server runs
both searches and merges them into one ranking. This is usually the best default for search
over documents — meaning-search catches paraphrases, keyword-search catches exact terms:
polign search articles -values @query.json -text "getting started" -k 5
How the two lists merge is adjustable (fusion — rank-based by default, or a
weighted blend); the options are in the
API reference.
Narrow results with filters
A -filter limits any search to records whose metadata matches. The simplest
form is exact match:
# only records where lang is "en"
polign search articles -values @query.json -k 5 -filter '{"lang": "en"}'
Filters compose: value lists ($in), ranges ($gte, $lt, …),
existence checks, and $and/$or/$not combinations:
# published since Jan 2026, and either English or untagged
polign search articles -values @query.json -k 5 \
-filter '{"ts": {"$gte": "2026-01-01"},
"$or": [{"lang": "en"}, {"lang": {"$exists": false}}]}'
Filtering happens during the search, not after it — asking for 5 results with a filter still returns the 5 best matching results, not whatever survives a trim.
Everyday housekeeping
Three more operations round out the API — fetch a record, page through a collection, and delete:
# fetch one record by id polign get articles guide-1 # list records in a collection polign list articles # delete a record polign delete articles guide-1
And that's nearly the complete surface: put, get, list, delete, query — plus batch put and batch get in the client libraries. There is no index to create, rebuild, or babysit — as a collection grows, the server re-indexes it in the background and swaps the new index in without downtime. Every flag of every command, including the ones this walkthrough skips, is in the CLI reference.
Using it from Python or Go
Everything above works identically through the client libraries — same operations, same options, without hand-writing HTTP calls.
# pip install "git+https://github.com/Polign/polign_db.git#subdirectory=sdk/python" from polign import Client client = Client("http://localhost:23000") client.put("articles", "guide-1", embedding, metadata={"title": "Getting started", "text": "How to get started with..."}) for hit in client.search("articles", values=query_embedding, k=5, filter={"lang": "en"}): print(hit.id, hit.distance, hit.metadata["title"])
The Python SDK guide covers batching, hybrid search, error handling, and the gRPC transport.
// go get github.com/Polign/polign_db — the client talks gRPC (:23001) c, _ := client.Dial(ctx, "localhost:23001") defer c.Close() c.Put(ctx, "articles", client.Vector{ ID: "guide-1", Values: embedding, Metadata: map[string]string{"title": "Getting started"}, }) hits, _ := c.Search(ctx, "articles", queryEmbedding, 5, 0) for _, h := range hits { fmt.Println(h.ID, h.Distance, h.Metadata["title"]) }
The gRPC API guide covers the full client, typed filters, and hybrid search options.
When something doesn't work
| Symptom | Likely cause and fix |
|---|---|
| Write rejected with a dimension error | The collection's dimension was fixed by its first vector, and this one has a different length. Check which model produced each vector; if the first write was a mistake, delete the collection's records and reload with consistent vectors. |
| Connection refused | The server isn't running, or you're on the wrong port. HTTP is
23000, gRPC is 23001 — the polign CLI and curl
talk to the first, the Go client dials the second. |
| Search returns results, but they're bad | Almost always a model mismatch: the query was embedded with a different model (or version) than the documents. Re-embed one side so both use the same model. |
| Keyword or hybrid search returns nothing | The text index needs two things: the server running with -store, and
your searchable text stored in the "text" metadata field. The index is
built in the background, so a just-written record may take a little while to appear
in keyword results — vector search sees it immediately. |
| Filter matches nothing | Metadata values are matched exactly, including case — {"lang": "EN"}
won't match records stored with "en". Fetch one record by id and check
what its metadata actually looks like. |
| My data vanished after a restart | The server was running without -store, which is in-memory by design.
|
Where to go next
- Advanced user guide — give each collection its own bucket, per project, region, or cost center.
- Python SDK and gRPC API — the complete client references.
- Live demo — hybrid search over Wikipedia, running on polign_db.