Performance profiling approach
The first step was understanding where time was actually being spent. We instrumented the search path to measure:
- Database query execution times
- Data transformation and filtering logic
- Network round trips
- Response serialisation
This revealed that the problem was not any single slow operation, but rather many moderately slow operations compounding together.
Database optimisation details
The biggest wins came from database-level improvements:
Index strategy: We analysed the most common search patterns (date range, location, size) and ensured composite indexes existed for these combinations. A search for "3-bedroom caravans in Cornwall available in August" could now use a single index scan rather than multiple table scans.
Query rewriting: Original queries often retrieved full caravan records when only summary information was needed. By selecting only required columns and using appropriate projections, we significantly reduced data transfer.
Availability pre-computation: Rather than calculating availability for every caravan on every search, we implemented a denormalised availability summary that could be queried efficiently.
Caching architecture
We implemented a tiered caching strategy:
- Application cache: Recently searched result sets cached in memory
- Query cache: Common filter combinations cached with appropriate TTL
- Availability cache: Pre-computed availability windows updated on booking changes
Cache invalidation was carefully designed so that new bookings immediately reflected in search results without requiring full cache clears.
Lessons for similar projects
The key insight from this project: dramatic performance improvements often come from architectural changes rather than code optimisation. The original code was not poorly written - it had simply evolved without performance as a primary design constraint. By stepping back and redesigning the search architecture, we achieved improvements that would have been impossible through incremental code tweaks.