---
title: "Cache and Price Freshness in Metasearch"
description: "Design travel-metasearch caching with realistic search load, adaptive TTLs, selective live rechecks, stale-price risk, observability and production trade-offs."
slug: "cache-price-freshness"
translationKey: "learn-cache-price-freshness"
locale: "en"
type: "guide"
category: "architecture"
tags: ["cache","freshness","pricing","latency","metasearch","travel-api"]
publishedAt: "2026-09-20"
updatedAt: "2026-09-20"
reviewedAt: "2026-09-20"
sources:
  - title: "Google Hotels Pricing overview"
    url: "https://developers.google.com/hotels/hotel-prices/dev-guide/updating-prices"
  - title: "Skyscanner Flights Indicative Prices Overview"
    url: "https://developers.skyscanner.net/docs/flights-indicative-prices/overview"
  - title: "Skyscanner Hotels Indicative Prices Overview"
    url: "https://developers.skyscanner.net/docs/hotels-indicative-prices/overview"
---

Caching in travel metasearch is not simply a question of “how many minutes should a price live?” The real design problem is balancing latency, supplier cost, coverage and traveler trust. Calling every provider live for every search can be slow and expensive; keeping offers too long can create stale rates and sold-out clicks.

Consider a system with 150,000 hotels, six suppliers and hundreds of searches per second. Fan-out live calls can push p95 latency into multiple seconds and consume upstream quotas. A fixed 30-minute cache may improve speed while producing unacceptable mismatch rates for near-term, volatile inventory. The useful solution is usually a **context-aware freshness policy**, not one global TTL.

## Why is price freshness a product requirement?

Freshness directly affects trust and conversion. If the result page shows €120 and the provider landing page shows €137, a technically successful cache hit has produced a poor product outcome.

Freshness can be thought about in three layers:

- **technical freshness:** how old is the observation?
- **commercial freshness:** is the amount still valid?
- **transactional freshness:** can the traveler still obtain the same offer now?

Cache age alone cannot answer all three. Two prices with the same age can have very different volatility.

## How should indicative and live prices be separated?

Indicative pricing is useful for exploration; live pricing is more appropriate as the user approaches transaction intent. Skyscanner explicitly distinguishes indicative cached data from live pricing use cases, while Google Hotels documents cached pricing and live pricing queries.

Keep source semantics in the internal model:

```ts
type PriceSource = "indicative" | "cached-live" | "live";

interface PriceObservation {
  providerId: string;
  propertyId: string;
  itineraryKey: string;

  amount: number;
  currency: string;

  observedAt: string;
  expiresAt: string;
  source: PriceSource;

  roomId?: string;
  ratePlanId?: string;
  availabilityCheckedAt?: string;

  confidence: number;
}
```

Even if ranking and UI ultimately display one number, the backend should not erase how that number was obtained.

## How should the cache key be designed?

A bad cache key can be more dangerous than a long TTL. Caching by property ID alone is insufficient for hotel pricing.

A useful key commonly includes:

```text
provider
+ property
+ check-in
+ nights
+ occupancy
+ room count
+ market
+ currency
+ eligibility/rate-rule context
```

If child ages, member rates or country-specific rules affect pricing, those dimensions may also need to be represented.

Otherwise a cache “hit” may belong to another traveler's commercial context.

## Why is one global TTL too simplistic?

Volatility varies by offer. A hotel for tomorrow with limited remaining inventory behaves differently from the same property six months out.

Adaptive TTL can incorporate:

- check-in proximity,
- historical mismatch rate,
- historical price-change frequency,
- supplier,
- property popularity,
- search/click volume,
- remaining-inventory signals,
- events/holidays,
- booking window,
- promotions.

Example policy:

```text
check-in <= 2 days       => base TTL 2 min
check-in <= 7 days       => base TTL 5 min
check-in <= 30 days      => base TTL 15 min
check-in > 30 days       => base TTL 60 min

provider mismatch > 5%   => TTL x 0.50
high click volume        => refresh priority +2
high sold-out risk       => TTL x 0.50
stable supplier/property => TTL x 1.50
```

These values are not universal. They should be calibrated using first-party mismatch, conversion and supplier-latency data.

## How should refresh priority work?

Large catalogs cannot refresh every key equally. Freshness therefore becomes a scheduling problem as well as a TTL problem.

A simple priority model could be:

```text
priority =
  search_volume_weight
+ click_volume_weight
+ checkin_proximity_weight
+ mismatch_history_weight
+ revenue_weight
- recent_refresh_penalty
```

Workers can refresh the highest-value contexts first. This is often more efficient than attempting to keep every long-tail itinerary equally fresh.

## Where does stale-while-revalidate make sense?

Browse and inspiration experiences may safely show a recent cached value while refreshing asynchronously. Transaction-oriented moments need stricter rules.

For example:

```text
Destination page       -> indicative/cached acceptable
Search result          -> fresh cached preferred
Offer detail           -> stricter freshness
Provider click         -> optional live recheck
Booking ownership      -> transaction-time validation
```

Using one policy across the entire funnel ignores user intent.

## When should click-time live revalidation happen?

Revalidating every click increases latency and supplier cost. It can be selectively triggered when:

- check-in is close,
- provider mismatch history is poor,
- cached age exceeds a threshold,
- booking value is high,
- inventory looks constrained,
- the offer is promotional or conditional.

The product must also define what happens when revalidation returns a different price:

1. show the new amount,
2. re-rank the offer,
3. show alternatives,
4. block handoff for severe mismatch.

Silently redirecting after a material price change is itself a product decision and should not happen accidentally.

## What failure modes matter?

### Cache stampede

A popular key expires and hundreds of requests hit the same supplier. Use single-flight, locking or background refresh.

### Poisoned cache

An incorrectly normalized occupancy or tax rule is cached and amplified by a high hit rate.

### Stale data during provider outage

Using stale cache during an outage can preserve coverage but increase trust risk. Stale-if-error needs explicit thresholds.

### Timestamp errors

Timezone or provider-timestamp mistakes can make old data appear fresh.

### Silent fallback

A live request times out and cached data is returned without recording the fallback. Dashboards then overstate live coverage.

### Hot-key starvation

Priority refresh keeps popular properties fresh while long-tail inventory ages indefinitely. Introduce minimum coverage or fairness rules.

## How should mismatch feedback affect caching?

Mismatch should modify future behavior, not just populate a report.

A useful loop is:

```text
Displayed cached price
        |
        v
Provider landing check
        |
        +--> matched
        |      |
        |      +--> confidence up
        |
        +--> mismatch
               |
               +--> classify reason
               +--> volatility up
               +--> effective TTL down
               +--> refresh priority up
```

Google's price-accuracy resources include mismatch reasons such as tax mismatch, room unavailable, delayed price feed and wrong itinerary, illustrating why a reason taxonomy is more actionable than one global mismatch percentage.

## Which metrics should be monitored?

### Cache health

- cache hit ratio,
- miss ratio,
- stale-while-revalidate use,
- refresh queue depth,
- refresh age p50/p95,
- hot-key rate.

### Freshness

- offer age p50/p95,
- live/cached/indicative ratio,
- threshold-overrun rate,
- provider/property freshness distribution.

### Business quality

- price mismatch rate,
- unavailable-after-click,
- mismatch by cache-age bucket,
- conversion by freshness bucket,
- revenue by freshness bucket,
- click abandonment after reprice.

### Supplier cost

- live requests per search,
- API quota usage,
- provider timeout rate,
- refresh cost per successful booking.

Do not optimize hit ratio in isolation. A 95% hit rate with a 12% mismatch rate may be a bad system.

## What are the trade-offs?

| Policy | Latency | Supplier cost | Freshness risk |
|---|---:|---:|---:|
| Always live | High | High | Low |
| Long fixed TTL | Low | Low | High |
| Adaptive cache + selective live | Medium/Low | Medium | Controlled |

The adaptive approach is often attractive, but it adds complexity. It only works when observability exists; a system cannot tune for volatility it does not measure.

## When should caching be avoided?

A live-first approach can be appropriate when:

- supplier responses are fast and cheap,
- inventory is extremely scarce,
- offer tokens are very short-lived,
- you own the booking transaction,
- contracts or regulation require transaction-time validation.

Conversely, inspiration and broad-date discovery often do not justify full-live architecture.

## Production checklist

- Does the cache key represent the full commercial search context?
- Does every price carry observedAt and source?
- Are live and indicative observations distinct?
- Can TTL adapt by provider and check-in proximity?
- Is cache-stampede protection in place?
- Is stale-if-error policy explicit?
- Is live-to-cache fallback observable?
- Are click-time validation triggers documented?
- Do mismatch reasons feed future policy?
- Is conversion measured by freshness bucket?
- Is long-tail starvation controlled?
- Can supplier cost and traveler trust be reviewed together?

## Meta Search takeaway

Good cache design does not maximize hit ratio. It delivers sufficiently fresh offers for the current user intent at acceptable latency and supplier cost. When freshness metadata, adaptive TTL, priority refresh and mismatch feedback work together, caching becomes a product-quality mechanism rather than just an infrastructure optimization.
