The Search Problem: Why Adding It Late Always Hurts
Search in a SaaS product is the ability for users to find records across the product's data by entering natural-language or structured queries. It is more complex than it looks because it requires a separate data model, an indexing pipeline, relevance tuning, and a query interface that maps what users type to what the system has stored. Teams that skip the design work ship search that disappoints and then spend months fixing it.
Written by Yashveer Singh, founder of Yashveer Labs.
What you actually need to know
- Search is not a feature you can add cleanly after the fact. The data model, the indexing strategy, and the relevance logic all need to be designed together.
- Postgres full-text search is the right starting point for most SaaS. It handles moderate scale and basic relevance without an extra service.
- Move to a dedicated engine when typo tolerance, faceting, or relevance tuning becomes necessary.
- Multi-tenant search requires tenant scoping baked in at the query or API key level. This is not optional.
- The teams that design search early ship a feature that works. The teams that bolt it on later ship a feature that disappoints and then spend quarters fixing the data model they cannot change without a migration.
| Option | Best fit | Typo tolerance | Multi-tenant support | Operational cost |
|---|---|---|---|---|
| Postgres full-text (tsvector/tsquery) | Early-stage SaaS, simple queries | No | Tenant filter in query | None |
| Typesense | B2B SaaS, multi-tenant | Yes | Scoped API keys | Low |
| Meilisearch | Teams wanting quick setup | Yes | Tenant filter in query | Low |
| Algolia | Teams wanting zero ops | Yes | Index per tenant or filter | Medium to high |
| Elasticsearch / OpenSearch | High scale, complex requirements | Yes | Role-based index access | High |
The core argument
The problem with adding search late is not just the technical work. It is that the data model was never designed with search in mind. Fields were not normalized for indexing. Relationships were not structured for efficient denormalization. The text content users want to search is buried in JSON blobs or distributed across thirty tables with no obvious join path. Retrofitting search onto this requires a migration that touches the core schema, which is one of the most expensive things a team can do.
I have worked through this with three different SaaS teams. The pattern is consistent. The team ships without search. Users ask for it. The team tries to add it. They discover that the data is not in a shape that indexes cleanly. They index what they can, ship something mediocre, and then get escalating complaints about search quality that they cannot fix without the schema migration they were trying to avoid.
The right approach is to design for searchability from the start, even if you do not ship search on day one. That means keeping important text fields as proper text columns rather than JSONB. It means thinking about which fields users will want to search, filter, and sort by. It means understanding which records belong to which tenant so the index can be scoped correctly. None of this is expensive to design early. It is expensive to change late.
The choice of search engine is a separate question from the design question, and it matters less than most teams think at early stage. Postgres full-text search is genuinely good enough for many products through the first fifty thousand records and moderate query complexity. The decision to graduate to a dedicated engine should be driven by real user complaints and real performance numbers, not by premature optimization.
The indexing pipeline problem
Keeping the index in sync
Search indexes are always a derived view of the source data. The source data changes. The index has to change with it. Getting this sync right is where most search implementations develop their first cracks.
The naive approach is a synchronous write: when you write a record to the database, also write to the search index in the same request handler. This works until it does not. The search service goes down. The write succeeds in the database and fails in the index. The record is now invisible to search. This inconsistency compounds over time.
The reliable approach is the outbox pattern. Writes go to the database and to a change log. A background worker reads the change log and updates the search index. If the search service is down, the worker retries. The index eventually catches up. Consistency is guaranteed. This is meaningfully more code than the synchronous approach, and it is worth doing once the search index matters to users.
Initial indexing of existing data
Adding search to an existing product means backfilling the index. The backfill is a migration job that reads records from the database in batches and writes them to the index. The migration has to be designed for restartability: if it fails halfway through, it should resume from where it stopped, not start over. It should also be designed for rate limiting so it does not overwhelm the search service or the database during the build.
How much does it cost
| Option | Engineering setup | Monthly cost at modest scale | Notes |
|---|---|---|---|
| Postgres full-text | A few days | Negligible | Already in your database |
| Typesense Cloud | One to two weeks | 25 to 150 USD | Includes managed hosting |
| Meilisearch Cloud | One to two weeks | 30 to 150 USD | Includes managed hosting |
| Self-hosted Typesense or Meilisearch | Two to three weeks | 20 to 80 USD server cost | Adds operational overhead |
| Algolia | One to two weeks | 50 to 500 USD | Pricing scales with operations |
| Elasticsearch on AWS | Two to four weeks | 100 to 500 USD | High baseline infrastructure cost |
What to look for in a search implementation
- Tenant scoping built in. Every query must be scoped to the requesting tenant. Not enforceable only by application code.
- An indexing pipeline that handles failures gracefully. Synchronous writes that fail silently are not acceptable.
- Typo tolerance and basic synonym handling once the product serves users who type quickly and imprecisely.
- Field weighting that reflects how important different fields are. A document title should rank higher than a body mention.
- Relevance feedback loop. Real user queries should drive tuning decisions, not assumptions.
- A re-indexing path that is tested and known to work. You will need to re-index when the schema changes.
Expert opinion
The search retrofit is one of the more expensive engineering projects I see teams take on. It is expensive not because search is technically hard but because the team is now changing a core part of the data model while the product is live and customers are using it. The teams that avoided this built with searchability in mind early, even when they did not ship search on day one. That forethought costs almost nothing at the beginning and saves weeks or months later.
>
Yashveer Singh, founder of Yashveer Labs
How this played out on a real project
A document management SaaS had been running for two years before adding search. The documents were stored as rich text in a JSONB column with metadata scattered across join tables. The initial attempt to add full-text search indexed only the title and a few metadata fields because the body content was not in a queryable shape. Users found it useless for anything but exact title matches.
The proper fix required extracting the document body to a dedicated text column during a zero-downtime migration, building an outbox pipeline to keep the index in sync, and re-indexing the entire document corpus. That work took six weeks. The original design decision that caused it took about an hour to make and could have been made differently.
For the migration and reliability patterns involved, zero downtime database migrations a step by step guide covers how to execute the schema changes safely, and the outbox pattern a SaaS reliability cheat code covers the indexing pipeline pattern in more depth.
Common mistakes teams make
- Indexing too many fields. Every field that changes requires a re-index. Be selective.
- Synchronous index writes without error handling. Failures create invisible records.
- No tenant scoping at the query level. A missing filter exposes data across tenants.
- Using the main database for full-text search when query load is affecting other operations.
- Ignoring relevance tuning after launch. The default ranking is never the right ranking for your data.
- No re-index strategy. The first schema change breaks the index and there is no plan.
- Building search on a data model that was not designed for it. The retrofit is the most expensive path.
A six week plan to ship search properly
- Week one. Audit the data model. Which fields will users search? Are they queryable? Identify the gaps.
- Week two. Start with Postgres full-text if the model supports it. Wire up basic keyword search on the most important entity type.
- Week three. Add tenant scoping. Every query goes through a scope function that is not bypassable by the caller.
- Week four. Build the outbox pipeline for index sync. Test failure and recovery.
- Week five. Ship to a limited user group. Collect real search queries. Tune field weights against actual data.
- Week six. Evaluate whether Postgres is sufficient or whether a dedicated engine is warranted. Migrate if needed.
For the broader architecture context, the write-heavy workload a different set of tradeoffs covers database design decisions that interact with search indexing, and the read-heavy workload strategies that move the needle covers the query patterns that sit next to search in most products.
Frequently asked
The work I take and why
I take work that compounds. I do not take work that is rework with extra steps. Yashveer Singh, founder of Yashveer Labs. If the topic on this page is what you are dealing with, the question is not whether it can be solved. It can. The question is whether you want to solve it once or four times. I am the person who solves it once.
Posts that line up with this one.
- SaaS Architecture and Scaling
Idempotency in API Design: Why It Matters More Than You Think
An idempotent API is one that handles repeated requests gracefully. Building it in from the start is far cheaper than retrofitting it after your first double-charge incident.
- SaaS Architecture and Scaling
Internal Admin Tools: Build vs Buy vs Retool
Every SaaS needs internal tools. The question is whether to build them, buy a platform like Retool, or use a lighter alternative. Here is the decision framework that saves engineering hours without creating tool debt.
- SaaS Architecture and Scaling
Job Failure Recovery: How Good SaaS Companies Sleep at Night
Every background job will fail eventually. The companies that sleep at night are the ones that built failure recovery into the system from day one, not as an afterthought when something broke in production.
- SaaS Architecture and Scaling
Monolith vs Microservices: Why Most Startups Get It Wrong
Microservices are the architecture that works at Netflix and fails at early-stage startups. Here is why the monolith is the right default, when microservices become rational, and how to make the transition without breaking everything.