TellWorldAI World Archive
Ask中文

Home›Practice›Practice

Practice

Semantic search with Postgres and pgvector

Build semantic search on Postgres with pgvector: install the extension, store embeddings as halfvec, create an HNSW index, and re-embed only the text that changed. Shows the setup behind TellWorld's question-answering feature.

Who this is for and what you will be able to do

This article is for developers who already run Postgres and want to add semantic search, meaning search by meaning rather than by exact words, to their own content. After reading it you should be able to install pgvector, choose a vector type and a distance operator, create an HNSW index, and understand the setup we use for TellWorld's question-answering feature, including how we avoid re-embedding text that has not changed.

What pgvector is

pgvector is an extension that adds vector types and nearest-neighbor search to Postgres. It provides vector, halfvec, bit and sparsevec types. The halfvec type stores half-precision elements. It takes 2 * dimensions + 8 bytes, against 4 * dimensions + 8 bytes for vector, and both support up to 16,000 dimensions.[2]

By default pgvector performs exact nearest-neighbor search, which has perfect recall. Adding an HNSW or IVFFlat index trades some recall for speed.[2] An HNSW index builds a multilayer graph. Its query performance is better than IVFFlat's, but it takes longer to build and uses more memory. It can be created before any data is loaded, because there is no training step.[2] We use HNSW.

Several distance operators are available. <-> is L2 distance, <#> is the negative inner product, <=> is cosine distance, <+> is L1 distance, <~> is Hamming distance for binary vectors and <%> is Jaccard distance for binary vectors. The README explains that <#> returns the negative inner product because Postgres supports only ASC order index scans on operators.[2]

Preparation

The README compiles and installs the extension from source at version v0.8.7. The Linux and Mac source install supports Postgres 13+.[2] The CHANGELOG lists 0.8.7 (2026-10-01) as the latest release and 0.9.0 (unreleased) as the next version.[3] Pin the version you install and read the CHANGELOG before you upgrade.

For a Docker setup, the image pgvector/pgvector:pg18-trixie adds pgvector to the official Postgres image.[2] With Homebrew Postgres, brew install pgvector adds it only to the postgresql@18 and postgresql@17 formulas.[2] The README also documents pgxn install vector for PGXN, sudo apt install postgresql-18-pgvector for APT, sudo yum install pgvector_18 for Yum, apk add postgresql-pgvector for APK, and conda install -c conda-forge pgvector for conda-forge.[2]

For embeddings we use Alibaba Cloud Model Studio, known in Chinese as 阿里云百炼. Its text-embedding-v4 model is a multilingual text vector model trained from Qwen3. Its user-defined dimensions range from 64 to 2048.[4] The Beijing rate limits are 1800 RPM and 1,200,000 TPM.[4] The model is billed by input tokens, and output is not charged.[6] In Beijing the input price is 0.5 yuan per million tokens, Batch calls are half price, and there is a free quota of 1 million tokens valid for 90 days.[5][6]

Step by step

  1. Install pgvector from source. The README's commands are these:[2]
cd /tmp
git clone --branch v0.8.7 https://github.com/pgvector/pgvector.git
cd pgvector
make
make install # may need sudo
  1. Enable the extension once in each database where you want to use it, with CREATE EXTENSION vector;.[2]
  1. Create an HNSW index. The README's example, on a table it calls items, is CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);. The options m and ef_construction default to 16 and 64, and you can set them with WITH (m = 16, ef_construction = 64).[2] HNSW indexes are created per distance function. The README lists vector_ip_ops, vector_cosine_ops, vector_l1_ops, bit_hamming_ops and bit_jaccard_ops as well, and it says to use halfvec_l2_ops for halfvec.[2] We use halfvec_cosine_ops, and the matching operator for the query is <=>.[2]
  1. Query with an ORDER BY on the distance operator, in ascending order, plus a LIMIT. An index can only be used when the query has both.[2] The search candidate list is controlled by hnsw.ef_search, which is 40 by default. Set it per transaction with SET LOCAL:[2]
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT ...
COMMIT;
  1. Choose the dimension and batch size of the embedding model. text-embedding-v4 defaults to 1024 dimensions, and the Beijing table lists 2,048, 1,536, 1,024, 768, 512, 256, 128 and 64 as options.[5] The batch size is 10, so one request can vectorize up to 10 texts, and each text must not exceed 8192 tokens.[5] The Beijing table gives a maximum of 33,000 tokens per batch, while the Singapore table gives 8,192.[5]
  1. Point the client at the OpenAI-compatible endpoint. The Beijing base_url is https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1. The Singapore region uses https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1. The key is read from the DASHSCOPE_API_KEY environment variable.[5]

Our practice

Every entry on TellWorld is split into chunks, stored in a cms_chunk table. Each row holds the text, a token count, a content hash, the name of the model and the embedding.[1] The embedding column is halfvec(1024), indexed with HNSW using halfvec_cosine_ops.[1] The 1024 matches the default dimension of text-embedding-v4.[5] The same table has a trigram GIN index on the text, using gin_trgm_ops, for keyword matching.[1]

Embeddings come from an OpenAI-compatible /embeddings endpoint, with text-embedding-v4 as the default model.[1] Only chunks whose content changed are re-embedded. The content hash is what makes that possible. On 13 November 2026 a run covered 5,342 documents and 34,228 chunks, and it embedded 94 chunks using 7,531 tokens.[1] The question-answering feature on the site retrieves from these chunks.[1]

Recording the model name in each row matters when the model changes, because it tells you which model produced each vector.[1] This is our design for our content. The number of chunks and the tokens used come from our own run, and another site will see different numbers.

Limits and common problems

  • With an approximate index, the filter is applied after the index is scanned. If a condition matches 10% of rows, then with HNSW and the default hnsw.ef_search of 40, only about 4 rows will match on average.[2]
  • For HNSW, a query can return fewer rows than you asked for when a filter removes too many candidates. The README documents iterative index scans, introduced in 0.8.0, which you enable with SET hnsw.iterative_scan = strict_order;.[2]
  • The README recommends CREATE INDEX CONCURRENTLY in production to avoid blocking writes.[2]
  • NULL vectors are not indexed, and neither are zero vectors for cosine distance.[2]
  • The HNSW limits are 2,000 dimensions for vector, 4,000 for halfvec, 64,000 for bit and 1,000 non-zero elements for sparsevec.[2]
  • IVFFlat is the other option. You should create the index after the table has some data. Choose lists at rows / 1000 for up to 1M rows and sqrt(rows) for more than 1M rows, and start probes at sqrt(lists). The README sets probes with SET ivfflat.probes = 10;.[2]
  • Batch calls to the embedding API cost half as much, but the batch limits still apply. The embedding documentation recommends text-embedding-v4 with OpenAI-compatible Batch calls for large-scale non-real-time text, to lower cost significantly.[5]
  • The halfvec index limit of 4,000 dimensions and our 1024 dimensions are well within the HNSW limits.[2]

Next steps

Read the pgvector README[2] and the CHANGELOG.[3] For the embedding model, read the Model Studio embedding documentation[5] and the billing page.[6] Before you ship, run one re-embedding pass over a small set of documents and compare the token counts with the billing page.

Sources

  1. 1
  2. 2
    pgvector README (master)pgvector (official GitHub repository)
  3. 3
    pgvector CHANGELOG (master)pgvector (official GitHub repository)
  4. 4
    text-embedding-v4 模型信息阿里云百炼 (Alibaba Cloud Model Studio)
  5. 5
    向量化(Embedding)阿里云百炼 (Alibaba Cloud Model Studio)
  6. 6
    模型计费 (billing-for-model-studio)阿里云百炼 (Alibaba Cloud Model Studio)

Cite this page: Semantic search with Postgres and pgvector. TellWorld, https://www.tellworld.net/practice/practice-pgvector-search. Updated 2026-11-14

Related news

View all