A hotel meta search engine sends one search to forty suppliers. Thirty-eight answer within 400 ms, one takes three seconds, one times out, and the traveler is still looking at a spinner.
That wait is the central problem of metasearch. The engine owns no index, so its engineering goes into deadlines, into merging lists that were never meant to be compared, and into deciding how stale an answer may be.
What a meta search engine is, in engineering terms
A meta search engine accepts one query, sends it to other search systems and merges what comes back into one ranked list. For the business side (who runs metasearch, how it earns money, when building one pays off) start with our business introduction to meta search engines; this post is the technical deep dive that pairs with it.
The defining property is the missing index.
A web search engine crawls documents and answers from its own inverted index. A meta search engine answers from other people's systems, at query time or from a short-lived copy of their answers. Real products sit at different points between those two:
- Pure query-time fan-out. SearXNG sends each query to the engines an instance has enabled and merges the lists; nothing about the query is kept afterwards.
- Fan-out with a cache in front. Travel metasearch such as Kayak and Skyscanner calls airlines and online travel agencies live for a specific trip, but answers exploratory searches from cached prices.
- Feed-built aggregators. PriceRunner and Indeed collect retailer prices and job postings into an index of their own, so their query path looks like a normal search engine and the metasearch problems move to ingestion.
The rest of this post is about the first two, where a search waits on systems you do not control.
Meta search engine architecture, stage by stage
Whatever the domain, the request path has the same six stages. Two stores feed it from outside the request: a cache of earlier answers and a mapping of which records from different sources describe the same thing. For a step-by-step build of the travel variant, see our guide on how to build a travel search engine.
1. Parse the query and pick the sources
Not every query should reach every source. A router decides which sources can answer (a supplier with no rooms in Lisbon has nothing to say about Lisbon), which are worth their cost for this query, and which are healthy right now. Source selection is the cheapest optimization in the system: a call you never make has no latency, no fee and no failure mode.
The router can be a rules table keyed on market, product and supplier coverage, or a classifier. System1's Dogpile Fetch, a web search API launched in July 2026, classifies every query and routes it to the backend it judges best, according to the company's announcement.
2. Fan out in parallel, with deadlines and partial results
The orchestrator calls the selected sources in parallel and stops waiting at a deadline. Two timeouts are in play: a per-source timeout, set from that source's latency distribution, and a global budget for the whole search, set from what the product promises the user. Whatever has arrived when the budget runs out is the answer.
Public systems show the range of settings. SearXNG gives each engine a 3.0 second request timeout unless the instance overrides it. Google's hotel price integration sends partners live pricing queries whose response limit is typically up to 4,000 milliseconds; a partner that misses it misses that opportunity, and Google still recommends sending the price so it can be cached.
Cutting off slow sources has a price. Skyscanner's partner documentation says the best itineraries often come from the supply partners that take longest to respond, which is why its Flights Live Prices API is split in two: /create returns an incomplete, cached subset at once, and /poll returns the rest as partners answer. The user sees results immediately and watches the list fill in.
In Java the pattern fits in a few lines with CompletableFuture and virtual threads:
ExecutorService io = Executors.newVirtualThreadPerTaskExecutor();
List<CompletableFuture<SourceResult>> calls = sources.stream()
.map(s -> CompletableFuture
.supplyAsync(() -> SourceResult.ok(s, s.search(query)), io)
.completeOnTimeout(SourceResult.timeout(s), s.timeoutMillis(), MILLISECONDS)
.exceptionally(e -> SourceResult.error(s, e)))
.toList();
// Stop waiting at the global budget: whatever is done by then is the answer.
CompletableFuture.allOf(calls.toArray(CompletableFuture[]::new))
.completeOnTimeout(null, budgetMillis, MILLISECONDS)
.join();
List<SourceResult> answered = calls.stream()
.filter(CompletableFuture::isDone)
.map(CompletableFuture::join)
.toList();
completeOnTimeout releases the caller but does not stop the HTTP call underneath, so the client still needs its own connect and read timeouts, or abandoned calls pile up as open connections. A timeout should also be recorded apart from an empty answer: an empty answer is data, a timeout is an incident.
A circuit breaker per source stops calling a supplier whose error or timeout rate crosses a threshold, so a failing supplier costs nothing instead of a full timeout on every search, and probes it again after a cool-down. Hedged requests, described in Google's The Tail at Scale, send a second copy of a slow call after a delay near the source's p95 and take whichever answer comes first. They suit sources that are idempotent and free to call twice, which rules out most paid supplier APIs.
3. Normalize every answer into one schema
Each source answers in its own format: XML from one supplier, JSON from another, prices with or without taxes, room types as free text, dates in the hotel's time zone. An adapter per source converts the answer into one canonical schema before anything else touches it. A minimal hotel offer record:
{
"source": "supplier-b",
"source_property_id": "88412",
"property_id": null,
"check_in": "2026-11-12",
"nights": 2,
"occupancy": {"adults": 2, "child_ages": []},
"room": {"name_raw": "Dbl Superior City View", "type": "double", "refundable": true},
"price": {"amount_minor": 31840, "currency": "GBP", "taxes_included": true, "fees_at_property_minor": 0},
"fetched_at": "2026-10-06T09:41:07Z",
"deep_link": "https://supplier-b.example/book?rate=..."
}
Money goes in minor units with an explicit currency, converted at display time with a dated rate. Whether taxes and property fees are included is stored per offer, because “cheapest” means nothing across prices that differ on that point. The raw fields stay next to the normalized ones, since the matching stage needs them, and so does every support ticket. property_id stays empty until matching fills it.
Sources change their responses without notice. Each adapter needs contract tests against recorded responses and an alert on the share of records that fail to parse.
4. De-duplicate results and match entities across sources
The same hotel arrives from twelve suppliers under twelve IDs, as “Riverside Grand London” and “Riverside Grand Hotel, London”, with coordinates 80 meters apart. Showing it twelve times is a bug every user notices.
For web results, de-duplication is mostly URL canonicalization: lowercase the host, strip tracking parameters and fragments, resolve redirects, then merge results that share a key. SearXNG merges duplicates across engines and keeps every position each engine gave the result, which its ranking uses.
For travel and shopping it is entity matching, done offline and stored as a mapping table:
- Blocking: compare only candidates that could be the same entity, such as properties within a few hundred meters of each other, or products of the same brand in the same category.
- Scoring: combine normalized name similarity, address, phone, distance and shared codes (GTINs for products, chain codes for hotels) into a match score, with rules or a trained classifier.
- Decision: accept above one threshold, reject below another, and send the band between them to a review queue.
The request path only looks the mapping up. Flights are easier, because carrier, flight numbers and times identify an itinerary; the hard part there is fares, where one itinerary carries different conditions. When identical offers collide, the engine still has to choose which provider to feature. Kayak's help center says it shows every provider of an identical car rental deal but highlights one, based on factors like customer popularity or ratings.
5. Merge and rank: rank fusion and score normalization
Sources rank by their own criteria and report scores on their own scales, when they report scores at all. A BM25 score from one engine and a relevance probability from another cannot be added. Three approaches cover most systems.
Reciprocal rank fusion
Reciprocal rank fusion (RRF) ignores scores and uses positions. Each source contributes 1 / (k + rank) for every item it lists, and an item's fused score is the sum. Cormack, Clarke and Büttcher introduced it at SIGIR 2009 with k fixed at 60 (the paper), and Elasticsearch's RRF retriever still defaults rank_constant to 60. A larger k narrows the gap between the top positions and the rest.
RRF needs no training data and no score calibration, which makes it the right first merge for web metasearch. Its weakness is the other side of the same property: it cannot tell a confident first place from a marginal one.
Score normalization, CombSUM and CombMNZ
Where sources return scores, normalize each list and then combine. OpenSearch's normalization processor documents the usual choices: min-max scaling to [0, 1], L2 normalization (each score divided by the Euclidean norm of its list) and z-score, combined by an arithmetic, geometric or harmonic mean. CombSUM adds the normalized scores; CombMNZ multiplies the sum by the number of sources that returned the item, which rewards agreement. SearXNG uses a variant of the second idea: a result's weight is the product of its engines' configured weights times the number of engines that returned it, and its score adds that weight divided by each position (source code).
Ranking on normalized attributes
Vertical metasearch mostly sets the source rankings aside. Once offers are normalized and matched, the engine ranks on the attributes themselves: price, duration, stops, rating, distance. Kayak describes its default flight ranking as a balance of price and convenience across duration, price, distance, number of travelers and how the search started, with dynamic weighting that changes per search and diversity logic that keeps a range of airlines and departure times in the list. Built in-house, this becomes a learning-to-rank model with clicks and bookings as labels, the same machinery as a recommendation engine's ranking stage.
6. Caching and freshness, decided per data type
The cache policy decides perceived speed more than hardware does, and it is set per data type:
- Static content (descriptions, photos, airport lists): hours to days, refreshed from bulk feeds.
- Exploratory prices, such as the cheapest month to fly somewhere: Skyscanner's Indicative Prices API serves cached prices that can be up to 4 days old for low-intent searches without fixed dates.
- Bookable prices for one trip: seconds to minutes, refreshed by live calls and checked again at click-out.
Keys matter as much as TTLs. A hotel price key holds property, dates, occupancy, market and currency; leave one out and users see prices meant for someone else. Two patterns keep a cache fresh without making users wait: stale-while-revalidate (serve the cached answer, refresh it in the background) and writing late answers back, so the supplier that missed this search's deadline improves the next one. Google's hotel integration works the same way: live pricing responses are cached to avoid repeat requests for the same price.
Freshness has to be measured. Sample cached prices, re-query them live and track the share that still match. Google checks price accuracy on its hotel partners; a meta search engine should run the same check on its own cache.
7. Rate limits, quotas and the cost of every call
Every search becomes N outbound calls, and each call counts against a quota, a rate limit or an invoice. Dogpile Fetch, for example, starts at $2 per 1,000 calls (vendor list price); a search that fans out to eight APIs priced like that costs eight times as much before it earns anything.
Travel suppliers add the look-to-book ratio: the number of searches allowed per booking, written into the contract, with fees or throttling above it. In the orchestrator that means:
- a token bucket per source and per credential, so one busy market cannot spend another market's quota;
- request coalescing, so identical concurrent searches share one outbound call;
- cache-first answers for low-intent traffic (crawlers, exploratory pages, price alerts), keeping live calls for users close to booking;
- cost and quota per source on the same dashboard as latency.
Sources also disappear. Microsoft retired the Bing Search APIs on 11 August 2025 and pointed customers to Grounding with Bing Search in Azure AI Agents. Google's Custom Search JSON API is closed to new customers, and existing customers have until 1 January 2027 to move. Engines built on either API had to replace a source, which argues for adapters behind one interface and against relying on a single source for coverage.
Scraping fills gaps where no API exists, and it is the most fragile source type. It breaks on markup changes, it has to respect robots.txt and the site's terms, and a supplier contract may forbid it outright. A scraped source gets its own adapter, budget and kill switch.
8. Latency budgets
Write the budget down before choosing the architecture. Start from the user-facing target and subtract everything that is not the fan-out; what remains sets the per-source timeouts. For a complete results page at a p95 of 1.5 seconds, a first draft could look like this:
| Stage | Budget at p95 | What usually breaks it |
|---|---|---|
| Edge, TLS and request parsing | 50 ms | Cold connections, no keep-alive |
| Routing and cache lookup | 30 ms | A remote cache call per lookup instead of a near cache |
| Fan-out to sources | 1,000 ms | One slow source without its own timeout |
| Normalize, match and rank | 120 ms | A database lookup per offer during matching |
| Render and transfer | 100 ms | Sending every offer to the client instead of one page |
| Headroom | 200 ms | Garbage collection pauses, retries |
These are starting values we would write into a scope, not measurements from a particular system.
The tail compounds with every source. If a search waits for ten sources and each is slow 1% of the time, about 10% of searches meet at least one slow source (one minus 0.99 to the tenth power, 0.096). That is why the deadline exists, and why sources that rarely change the top results are the first candidates for a shorter timeout. Our notes on low latency Java applications cover the JVM side of the budget: allocation, GC pauses and thread handoffs.
9. Observability when the slow part runs on someone else's servers
Most incidents in a meta search engine start as a question about one source. Instrument for that question:
- One trace per search with a span per source call, tagged with source, market and outcome (answered, empty, timeout, error, circuit open). OpenTelemetry's HTTP client instrumentation produces these spans.
- Per source: p50, p95 and p99 latency, and the timeout, error, empty-answer and parse-failure rates.
- Per search: coverage, the share of selected sources that answered before the deadline.
- Per source, business signals: click-out share, booking share and price accuracy, so a source that is fast but never wins can be dropped.
Alert on coverage. A search that returns in 300 ms because half the sources failed looks healthy on a latency dashboard.
Meta search engines vs search engines: how they differ
Both return a ranked list for a query, and the systems behind them have little else in common. A search engine with its own index (Google, Bing, Brave Search, Mojeek) does most of its work before the query arrives: crawling, parsing, indexing. A meta search engine does it during the query. Our guide on how to build a search engine covers the crawl, index and rank side.
| Search engine with its own index | Meta search engine | |
|---|---|---|
| Data | Its own index of crawled documents | No document index; other engines, APIs and feeds, plus a short-lived cache of their answers |
| Acquisition | Crawler: scheduling, politeness, robots.txt, near-duplicate detection | One adapter per source: contracts, credentials, quotas; scraping only where allowed |
| Freshness | As fresh as the last crawl of each page | As fresh as the source's answer, or the cache TTL |
| Query latency set by | Its own index servers, under its control | The slowest source it decides to wait for |
| Ranking | One scoring function over one index, with comparable scores | Merging lists with incomparable scores: rank fusion, score normalization or re-ranking on attributes |
| Duplicates | Removed at index time | Entity matching across sources with different IDs |
| Cost model | Mostly fixed: crawling, storage, serving; an extra query costs little | Variable: per-call fees, quotas, look-to-book limits; every query has a marginal cost |
| Scaling | Shard and replicate the index | Outbound calls grow with sources times queries; caching and source selection cut them |
| Typical failures | Stale or missing pages, crawler traps, index corruption | Source timeouts, schema changes, exhausted quotas, a source shut down |
| Coverage | What it chooses to crawl | The union of its sources, with all their gaps |
| Examples | Google, Bing, Brave Search, Mojeek | SearXNG, Dogpile, Startpage, Kayak, Skyscanner |
The union works in metasearch's favor too. System1 reported in July 2026 that when its team ran real AI agent queries through seven search APIs, any single index surfaced only about half of what the seven found together. System1 sells a metasearch API, so treat that as vendor research.
Meta search engines comparison: sources, APIs, privacy and ranking
The table compares engines in use in 2026 by how they are built rather than by consumer features. Each was checked against its own site or documentation in October 2026. Travel businesses choosing where to list inventory will find the commercial view in our post on travel meta search engines.
| Engine | Domain | Sources and how they are reached | API access | Privacy model | Ranking |
|---|---|---|---|---|---|
| SearXNG (open source) | Web | Up to 261 search services, queried at search time; per-engine timeout, 3.0 s by default | Search API on each instance; JSON output is off by default and enabled per instance | No tracking or profiling; self-hosted, usable over Tor | Engine weights times the number of engines returning a result, summed over 1 / position |
| Dogpile (System1) | Web | Third-party web search backends; each query classified and routed | Dogpile Fetch API and MCP server since July 2026, from $2 per 1,000 calls | Ad-supported consumer portal | Merged, de-duplicated results; a Deep mode splits hard queries into several searches |
| Startpage | Web | Forwards an anonymized query to its search and content providers | No public search API | IP address removed, searches not logged; Anonymous View proxy for opening results; ads from platforms such as Google AdSense | The providers' ranking, unprofiled: every user gets the same results |
| Kayak (Booking Holdings) | Flights, hotels, cars, packages | Hundreds of travel sites (airlines, hotels, online travel agencies, car rental firms) scanned per search | No open search API; Ask AI, a natural-language search, on kayak.com | Consumer accounts, saved trips, price alerts | Price and convenience with weights adjusted per search, plus diversity logic; identical deals grouped under one highlighted provider |
| Skyscanner | Flights, hotels, car hire | Supply partners queried live; exploratory searches from cached prices up to 4 days old | Travel APIs for partners: Live Prices (create, then poll), Indicative Prices, an MCP server | Consumer accounts, price alerts | Sorting on normalized itineraries; API filters by carrier and agent |
| Google Hotels and Google Flights | Hotels, flights | Hotels: partner price feeds by push (ARI), pull or changed pricing, plus live pricing queries at search time | No public search API; partners integrate inbound through Hotel Center | Google account and advertising model | Hotels: paid ads alongside free booking links |
| PriceRunner (Klarna) | Shopping | Prices from partner and non-partner retailers, collected into its own product index | No public search API | Part of the Klarna Group since 2022; also inside the Klarna app | Offers per matched product; no retailer can pay for a higher position |
| Indeed | Jobs | Own index of postings from employers and applicant tracking systems | Partner APIs for posting jobs and managing candidates, not for search | Job seeker accounts | Relevance plus sponsored job placements |
Three recent changes show in the table. Web metasearch lost two common backends with the Bing Search API retirement and the Custom Search JSON API closing to new customers. Skyscanner and Dogpile both publish MCP servers, so AI agents are now API customers of metasearch. And Kayak has put a natural-language layer, Ask AI, in front of the same fan-out.
What a UK hotel metasearch engine taught us about memory, latency and cost
Our own lesson came from the other end of the request path. A major UK hotel bookings aggregator ran its availability search on a cluster of dozens of SQL Server machines, at about 200 million queries a day, 99% of them read-only. The cluster had stopped scaling: each added machine raised infrastructure cost by about 2% and throughput by about 0.1%.
We moved availability search into an in-memory data grid that holds the complete data set needed to answer a hotel availability query, so the request path never touches the database. A custom fast loader fills the grid from SQL Server, a message queue pipeline carries intraday updates, and SQL Server stayed the system of record.
The engine now serves 300 million queries a day, 50% more traffic than before, with response time down 60% and 95% of queries answered in under a second. Infrastructure cost fell 80%, mostly from removing persistent database load, and adding nodes now adds throughput almost linearly, at about 70% scaling efficiency (case study). Three lessons from that system apply to any meta search engine.
Read-mostly traffic belongs in memory. A workload that is 99% reads should not pay a database round trip per query. Partitioning the full answer set across grid nodes turned a cost curve that had gone flat into one that grows with the cluster. Our explainer on what Hazelcast is used for shows how such grids partition and replicate data.
Staleness needs a mechanism. The grid is a copy, and the intraday message queue is what makes the copy safe to answer from. Decide per data type how far the copy may lag, then build the update path that keeps it within that limit.
Cost follows repeated work. Most of the 80% came from no longer asking a database the same questions millions of times a day. Supplier calls follow the same arithmetic: every call a cache or a mapping table absorbs saves latency, quota and money.
If adding machines to your engine no longer adds throughput, or supplier calls cost more than the searches earn, our meta search engine development team starts with a scoping call and a written ballpark with its assumptions. With modest traffic and a handful of suppliers, the patterns above are enough, and we will tell you so.