Replacing Algolia with Postgres FTS and pgvector
Algolia's per-record pricing hits hard at scale. Here's when Postgres full-text search and pgvector cover the same ground for a fraction of the cost.
By Andrii Votiakov on
Algolia is excellent search infrastructure. It's also priced in a way that punishes you for growing. At 1 million records and 10 million searches a month you're looking at $500-800/month. At 10 million records, that scales fast. Most teams reach a point where they're paying Algolia more than their database bill, for a search function that doesn't need sub-10ms global CDN delivery.
Quick answer
Algolia makes sense when you need instant-as-you-type global search with zero infrastructure. Postgres full-text search plus pgvector covers 80% of use cases at roughly 10-20% of the cost once you have more than 500k records. The switch requires 1-2 weeks of engineering, not months. The crossover point is typically a $400-600/month Algolia bill.
How Algolia actually prices
Algolia's current pricing (as of early 2026) is built on records and search operations:
- Records: billed per 1,000 records stored in the index
- Search operations: billed per 1,000 searches
- Premium plan features: NeuralSearch (semantic), personalisation, A/B testing — significantly more expensive
A product catalogue with 2 million records at the standard tier costs roughly $400-600/month before factoring in search volume. Add semantic/NeuralSearch capabilities and you're at $1,000-2,000/month.
The pain point isn't usually the base plan — it's when your record count grows and your search volume grows in parallel. Both dials turn at once.
When Postgres full-text search is enough
Postgres full-text search (FTS) via tsvector and tsquery handles:
- Keyword search with stemming and stop-word removal
- Multi-field weighted search (title ranks higher than body text)
- Trigram similarity for fuzzy matching and typo tolerance
- Phrase search and proximity operators
- Fast filtering with standard Postgres indexes alongside the text index
Here's the core pattern:
-- Add a generated tsvector column
ALTER TABLE products ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(name, '')), 'A') ||
setweight(to_tsvector('english', coalesce(description, '')), 'B') ||
setweight(to_tsvector('english', coalesce(category, '')), 'C')
) STORED;
-- GIN index for fast search
CREATE INDEX idx_products_search ON products USING gin(search_vector);
-- Query
SELECT id, name, ts_rank(search_vector, query) AS rank
FROM products, to_tsquery('english', 'waterproof & jacket') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;
For typo tolerance (Algolia's autocomplete behaviour), add a trigram index:
CREATE EXTENSION pg_trgm;
CREATE INDEX idx_products_name_trgm ON products USING gin(name gin_trgm_ops);
-- Similarity search
SELECT name, similarity(name, 'jaket') AS sim
FROM products
WHERE name % 'jaket'
ORDER BY sim DESC
LIMIT 10;
This covers the vast majority of what most e-commerce, content, and internal tool search needs.
Where pgvector adds semantic search
pgvector adds vector similarity search to Postgres. This is the Algolia NeuralSearch equivalent — finding results by meaning, not just keyword overlap.
The pattern: embed your documents with an embedding model (OpenAI text-embedding-3-small at $0.02/million tokens, or a self-hosted model like nomic-embed-text), store the vector in Postgres, query by cosine similarity.
CREATE EXTENSION vector;
-- Add embedding column
ALTER TABLE products ADD COLUMN embedding vector(1536);
-- Store an embedding (from your application)
UPDATE products SET embedding = '[0.12, 0.34, ...]'::vector WHERE id = 1;
-- HNSW index for fast approximate search
CREATE INDEX idx_products_embedding ON products
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Semantic search query
SELECT id, name, 1 - (embedding <=> '[0.15, 0.29, ...]'::vector) AS score
FROM products
ORDER BY embedding <=> '[0.15, 0.29, ...]'::vector
LIMIT 20;
Hybrid search: combining FTS and pgvector
The best results usually come from combining both: keyword search finds exact matches, vector search surfaces semantically related results, and a simple RRF (Reciprocal Rank Fusion) merge gives you ranked results from both.
WITH keyword_results AS (
SELECT id, ts_rank(search_vector, query) AS kw_score, ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, query) DESC) AS kw_rank
FROM products, to_tsquery('english', 'waterproof jacket') query
WHERE search_vector @@ query
LIMIT 50
),
vector_results AS (
SELECT id, 1 - (embedding <=> '[0.15, 0.29, ...]'::vector) AS vec_score, ROW_NUMBER() OVER (ORDER BY embedding <=> '[0.15, 0.29, ...]'::vector) AS vec_rank
FROM products
LIMIT 50
)
SELECT
COALESCE(k.id, v.id) AS id,
(1.0 / (60 + COALESCE(k.kw_rank, 100))) + (1.0 / (60 + COALESCE(v.vec_rank, 100))) AS rrf_score
FROM keyword_results k
FULL OUTER JOIN vector_results v ON k.id = v.id
ORDER BY rrf_score DESC
LIMIT 20;
This is genuinely competitive with Algolia NeuralSearch for most search use cases.
What you don't get with Postgres
Be honest with yourself about the gaps:
- Global CDN-edge delivery: Algolia returns results from a nearby PoP. Postgres queries cross your data centre. For a search box where users expect < 100ms response, your Postgres location matters.
- Analytics and A/B testing: Algolia's dashboard tracks click-through rates, popular queries, no-result queries. You'd need to build that.
- Managed index sync: Algolia has a push API with retry logic, batch indexing, and indexing pipelines. With Postgres, you own that logic.
- Autocomplete / Query Suggestions: Algolia builds these automatically. With Postgres you implement them using trigrams and query logs.
If any of these are critical to your use case, the engineering cost to replace them may not be worth it. If they're nice-to-have features you've barely used, they're not worth $800/month.
The actual replacement process
I've done this migration a few times. The steps:
- Add
tsvectorcolumn and GIN index to your existing table. No downtime. - Backfill embeddings in batches (rate-limited to your embedding provider). For 1 million records at 8k tokens each, OpenAI
text-embedding-3-smallcosts ~$160 one-time. - Run both Algolia and Postgres search in parallel on production traffic for 1-2 weeks. Compare result quality.
- If quality is acceptable (it usually is for structured catalogues), flip the switch.
- Cancel Algolia after 30 days of clean operation.
The total engineering time: 5-10 days for a senior engineer who knows Postgres.
Realistic numbers
A client running Algolia at $1,100/month (3.5 million records, ~8 million searches/month):
- Migrated to Postgres FTS + pgvector hybrid search
- Embedding generation one-time cost: $280
- Ongoing embedding cost for new records: ~$15/month
- Additional Postgres instance cost (r6g.xlarge for dedicated search queries): $180/month
- Total ongoing cost: ~$195/month
Savings: $905/month, ~82% reduction. Migration took 9 working days.
See also: /blog/replacing-pinecone-with-pgvector for how this extends to pure vector search workloads.
If you want me to assess whether your Algolia setup is worth migrating on a pay-for-savings basis, book a call.