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.

Durability in one flag

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.

TermWhat it means
VectorA 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 modelThe 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.
CollectionA named group of vectors, like a table in a regular database. Created automatically the first time you write to it.
MetadataPlain key–value labels you attach to each vector. The metadata is returned with every search hit, and usable for filtering.
Distance / scoreHow results are ranked. Vector search returns a distance (smaller = closer match); keyword and hybrid search return a score (larger = better match).
Crux of search

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"}}
  ]}'
Put the searchable text in a "text" metadata field

If 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

SymptomLikely 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