# pg_vectorize: semantic, full text and hybrid search on Postgres

> pg_vectorize wraps pgvector, pgmq and SentenceTransformers into either an HTTP service or a Postgres extension, so a table of text becomes a searchable index without you writing the embedding pipeline. The trade-off is that the project ships two deployment modes and only one of them fits a managed database.

**ChuckHend/pg_vectorize** — Full-text and semantic search on any Postgres

- Repository: https://github.com/ChuckHend/pg_vectorize
- Website: https://chuckhend.github.io/pg_vectorize/
- Stars: 832 · Forks: 41
- Language: Rust
- License: not declared
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/chuckhend-pg-vectorize

## The problem pg_vectorize removes from your application code

Adding vector search to a Postgres application normally means four separate jobs: chunk or concatenate the source text columns, call an embedding model, store the vectors in a pgvector column, and keep that column in sync as rows change. The README describes pg_vectorize as automating "the transformation and orchestration of text to embeddings", which is precisely that middle layer. You point it at a table, name the text columns, and it creates the embedding column and keeps it current.

The audience is narrow but real. It is for teams that already keep their data in Postgres and do not want a second datastore for retrieval. It is not for teams that need to tune chunking strategy per document type, because the interface takes a list of source columns, not a chunker configuration. It is also not for anyone who wants a hosted vector database with its own query language. The project's own framing is that it provides hooks into LLM providers and orchestrates the pipeline; the retrieval math underneath is pgvector's.

## Two deployment modes and how the pieces fit together

The README states the project offers two ways to add search: an HTTP server, described as recommended for managed databases, and a Postgres extension that exposes SQL functions such as `vectorize.table()` and `vectorize.search()`. The extension requires filesystem access to Postgres, which is why the managed path exists.

The architecture visible in the repository is a Rust workspace with four members: `server`, `core`, `worker` and `proxy`, with `extension` deliberately excluded from the workspace. The `docker-compose.yml` shows the runtime shape of the server mode: a `postgres` service based on the `pgvector/pgvector:0.8.2-pg18` image on port 5432, a `server` service on port 8080, and a `vector-serve` service on port 3000 that serves embeddings. The server container reads `DATABASE_URL`, `EMBEDDING_SVC_URL`, and provider keys including `OPENAI_API_KEY`, `CO_API_KEY`, `VOYAGE_API_KEY` and `HF_API_KEY`. The README credits pgmq for "orchestration in background workers", which is the mechanism behind continuous updates: registering a table creates a job, and the worker watches for new or changed rows rather than requiring a manual reindex.

## Installing pg_vectorize with docker compose and running a first search

The README's quick start brings up Postgres, the embeddings server and the management API together. Run this from the repository root, where `docker-compose.yml` lives:

```bash
docker compose up -d
```

The compose file starts three containers and waits on a `pg_isready` healthcheck before launching the server. The README then offers an optional example dataset:

```bash
psql postgres://postgres:postgres@localhost:5432/postgres -f server/sql/example.sql
```

The README shows the expected output as `CREATE TABLE` followed by `INSERT 0 40`, so a successful load means forty rows in the example table. Next, register the table as an embedding job. This is the step that creates embeddings for existing rows and keeps watching the table afterwards:

```bash
curl -X POST http://localhost:8080/api/v1/table -d '{
		"job_name": "my_job",
		"src_table": "my_products",
		"src_schema": "public",
		"src_columns": ["product_name", "description"],
		"primary_key": "product_id",
		"update_time_col": "updated_at",
		"model": "sentence-transformers/all-MiniLM-L6-v2"
	}' -H "Content-Type: application/json"
```

The README shows a JSON response containing an `id`, which is the job identifier. Note the two keys that make the watcher work: `primary_key` and `update_time_col`. If your table has no reliable update timestamp, the continuous sync has nothing to compare against. Then query it:

```bash
curl -G \
  "http://localhost:8080/api/v1/search" \
  --data-urlencode "job_name=my_job" \
  --data-urlencode "query=camping backpack" \
  --data-urlencode "limit=1" \
  | jq .
```

The README's sample response for that query is a single row with `product_name` of `Backpack`, a `similarity_score` of 0.6296013593673706, and both `fts_rank` and `semantic_rank` set to 1, plus an `rrf_score`. Those fields are the clearest evidence of what the search endpoint actually does: it runs a full text ranking and a vector ranking and fuses them, which is the hybrid mode the README advertises. For the SQL path, the README points to `./extension/README.md` and states it requires filesystem access to Postgres.

## Where pg_vectorize is the wrong choice

The extension mode is the sharpest constraint. The README says plainly that it requires filesystem access to Postgres, which rules out RDS, Cloud SQL and most managed offerings. The HTTP server mode works around that, but it is a separate service you now operate: a container holding provider API keys, a second container serving embeddings, and a network path between them and your database. That is a real operational surface, and the README does not document a rollback path for a job once it has written an embedding column into your table.

The second limitation is model coupling. The `model` field in the job registration is a SentenceTransformers identifier in the README example, and the compose file also wires OpenAI, Cohere and Voyage keys through to the server. The README does not describe what happens when you change the model on an existing job, and it does not describe re-embedding an entire table after such a change. If your retrieval quality depends on switching embedding models as better ones appear, that is an unverified path here. Finally, the repository has no licence text in what is published; the root listing shows a `LICENSE` file but its contents and identifier are not given, so you must read it yourself before shipping anything.

## How this differs from pgvector alone and from Timescale Vector

pgvector is the comparison that matters most, because pg_vectorize depends on it. pgvector gives you the `vector` column type, distance operators and index types. It does not call an embedding model, does not create the column for you, and does not watch a table for changes. With pgvector alone, your application code owns the embedding call and the sync logic. pg_vectorize owns both, at the cost of running extra services and accepting its opinion about how a table maps to an embedding job.

Against Timescale Vector, the difference is packaging rather than retrieval. Timescale Vector is a Postgres distribution with vector support built in, so you get vector search by choosing that platform; pg_vectorize is something you add to a Postgres you already run, in two different shapes. The README's own guidance is the practical version of this: pick the HTTP server when your Postgres is managed, pick the extension when you self-host and can install extensions. If you are already on a platform that ships vector search, adding pg_vectorize means adding a service, not gaining a capability.

## Maintenance cost, release cadence and licence

The most recent release is v0.27.0, dated 2026-07-24, and the last push to the default branch carries the same timestamp. Before that, v0.26.2 landed on 2026-04-27 and v0.26.1 on 2026-04-06. The gap between the April releases and the July release is roughly three months, and the gap from July to now is longer than that, so treat the project as one that ships in bursts rather than continuously. Nothing published indicates the repository is archived.

Upgrade cost has two components. The server and worker ship as container images, `ghcr.io/chuckhend/vectorize-server:latest` and `ghcr.io/chuckhend/vector-serve:latest`, and the compose file pins the database image to `pgvector/pgvector:0.8.2-pg18` but not the application images, so `latest` will move under you unless you pin by digest. The extension path is worse to upgrade, because it is installed into Postgres and the workspace `Cargo.toml` excludes `extension` from the Rust workspace, meaning it is built and versioned separately from the server. On licensing, the repository root contains a `LICENSE` file but no identifier is stated in what is published, so verify the terms before redistribution.

## Conclusion

Adopt the HTTP server mode if your Postgres is managed, you can run a container next to it, and you want embeddings maintained by a background worker rather than by application code. Do not adopt it if you need an in-database experience on a managed instance, or if you cannot supply an embedding model or an external embeddings service, because the extension path requires filesystem access to Postgres itself. Before committing, verify that pgvector is installed in the target database, that the embedding service URL responds, and that your primary key and update timestamp column behave the way the job registration assumes, since the watcher depends on them.

## FAQ

### What does pg_vectorize actually do?

It automates turning text columns into embeddings and keeps them in sync, then exposes semantic, full text and hybrid search over the result. The README describes it as a Postgres server and extension that builds on pgvector for similarity search and pgmq for background worker orchestration.

### How do I set up pg_vectorize?

The README's quick start runs `docker compose up -d` to start Postgres, the embeddings server and the management API, then registers a table through `POST /api/v1/table`. The alternative path installs the Postgres extension, which the README says requires filesystem access to Postgres.

### Does pg_vectorize work on managed Postgres like RDS or Cloud SQL?

The README recommends the HTTP server mode for managed databases, since the extension mode requires filesystem access to Postgres. The server mode still requires that pgvector is available in the database.

### What does the search endpoint return?

The README's example response includes the source row fields plus `similarity_score`, `fts_rank`, `semantic_rank` and `rrf_score`. The presence of both a full text rank and a semantic rank shows the endpoint fuses the two rankings.

## Sources

- [ChuckHend/pg_vectorize on GitHub](https://github.com/ChuckHend/pg_vectorize)
- [Issues](https://github.com/ChuckHend/pg_vectorize/issues)
- [Project website](https://chuckhend.github.io/pg_vectorize/)
- [README](https://github.com/ChuckHend/pg_vectorize/blob/main/README.md)
- [Releases](https://github.com/ChuckHend/pg_vectorize/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/chuckhend-pg-vectorize
