Hospitality SaaS2023

Dynamic Pricing Engine

Replacing a static pricing model without a single minute of downtime.

18%/avg
Revenue lift
$2.4M
Pricing decisions automated
6 weeks
Migration time
0ms
Downtime
1,400
Contracts affected
0
Support tickets

Problem

A hospitality SaaS product priced every room identically regardless of occupancy, season, or local events. Competitors offering dynamic pricing were winning deals. The product needed to think.

Context

The existing system was a ten-year-old monolith. Pricing happened inside a stored procedure that three people were afraid to touch. There were 1,400 active contracts all referencing the old pricing model. Any migration that broke a single invoice was a support crisis.

Constraint

Zero downtime. No contract renegotiation. The business could not tell clients that pricing was changing — it had to simply improve. And it had to ship in six weeks.

The decision

What we chose and why.

Instead of replacing the pricing model, I introduced an event-sourced pricing layer alongside it. The monolith continued to emit pricing requests; a new engine answered them. The engine's decisions were idempotent and fully auditable. The old system gradually received less traffic until it was inert.

Tradeoffs

Event sourcingoverDirect database mutation

Auditability was a hard requirement. When a pricing decision is wrong, we need to replay exactly what the engine saw.

Feature flag migrationoverBig-bang cutover

The cost of a mistake was too high. Gradual rollout let us catch race conditions under 2% traffic before they affected 100%.

Competitor rates as signal, not ruleoverRule-based competitor matching

Rules encode yesterday's market. A signal lets the engine's decision function evolve without redeployment.

Architecture

Service
Data Store
Client
Booking APIPricing RouterLegacy PricingPricing EngineSignal StoreAudit LogInvoice Ledger

Click any node to inspect

The failure

A race condition in concurrent booking scenarios caused price drift. If two bookings were priced within 50ms of each other and occupancy crossed a threshold between them, the second booking used stale occupancy data.

DISCOVERED — Discovered during load testing at 3× normal booking volume. Caught before production.

IMPACT — Estimated drift of ~3% on affected bookings. In testing only — zero customer impact.

Iteration

Added optimistic locking with version vectors on the occupancy store. Each pricing call now includes the occupancy snapshot version it read from. If another call has advanced the version, the second call retries. Latency increased by 8ms at the 99th percentile — a fair trade.

Outcome

$2.4M in automated pricing decisions. Zero customer complaints.

The product team gained a competitive differentiator that took a year to develop but six weeks to deploy safely. The pricing engine now handles seasonal peaks without human intervention. Three competitors are still using static pricing.

Lessons

01

When correctness matters more than speed, make state transitions explicit and auditable.

02

Gradual migration is always slower and always worth it.

03

A race condition under 2× load is a production incident waiting to happen. Test at 5×.

04

Event sourcing adds complexity upfront. It removes all the complexity later.