Case Study: Building a High-Performance Real Estate Platform
The Challenge: Performance at Scale
When A real estate platform was experiencing performance issues due to growth. With thousands of active listings and significant monthly traffic, page load times had increased substantially, affecting conversion rates.
The Numbers That Mattered
The Technical Deep Dive
Initial Architecture Analysis
The existing system was a classic monolithic PHP application with several critical bottlenecks:
// Legacy search query - the performance killer
public function searchProperties($filters) {
$query = "SELECT * FROM properties p
LEFT JOIN images i ON p.id = i.property_id
LEFT JOIN locations l ON p.location_id = l.id
WHERE 1=1";
if ($filters['price_min']) {
$query .= " AND p.price >= " . $filters['price_min'];
}
if ($filters['price_max']) {
$query .= " AND p.price <= " . $filters['price_max'];
}
// ... 15 more conditions without proper indexing
$result = mysql_query($query); // No pagination, no caching
return mysql_fetch_all($result);
}
*Problems identified:*
1. No database indexing strategy
2. N+1 query problems with image loading
3. No caching layer
4. Synchronous image processing
5. Monolithic frontend with no code splitting
The New Architecture: Performance-First Design
We designed a microservices architecture with performance as the primary constraint:
// Property Search Service - Optimized
interface SearchFilters {
location?: string;
priceRange?: [number, number];
propertyType?: string;
features?: string[];
page?: number;
limit?: number;
}
class PropertySearchService {
private readonly redis: Redis;
private readonly elasticsearch: Client;
private readonly db: PostgreSQLClient;
async searchProperties(filters: SearchFilters): Promise {
const cacheKey = this.generateCacheKey(filters);
// Check Redis cache first
const cached = await this.redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Elasticsearch for complex search queries
const searchQuery = this.buildElasticsearchQuery(filters);
const results = await this.elasticsearch.search({
index: 'properties',
body: searchQuery,
size: filters.limit || 20,
from: ((filters.page || 1) - 1) * (filters.limit || 20)
});
// Transform and cache results
const searchResult = this.transformResults(results);
await this.redis.setex(cacheKey, 300, JSON.stringify(searchResult)); // 5min cache
return searchResult;
}
private buildElasticsearchQuery(filters: SearchFilters) {
const must: any[] = [];
if (filters.location) {
must.push({
match: {
'location.name': {
query: filters.location,
fuzziness: 'AUTO'
}
}
});
}
if (filters.priceRange) {
must.push({
range: {
price: {
gte: filters.priceRange[0],
lte: filters.priceRange[1]
}
}
});
}
return {
query: {
bool: { must }
},
sort: [
{ featured: { order: 'desc' } },
{ updated_at: { order: 'desc' } }
]
};
}
}
Database Optimization Strategy
The old MySQL setup was replaced with a optimized PostgreSQL configuration:
-- Strategic indexing for property searches
CREATE INDEX CONCURRENTLY idx_properties_location_price
ON properties(location_id, price)
WHERE status = 'active';
CREATE INDEX CONCURRENTLY idx_properties_type_features
ON properties USING GIN(property_type, features)
WHERE status = 'active';
CREATE INDEX CONCURRENTLY idx_properties_geo
ON properties USING GIST(location_point);
-- Partial index for featured properties
CREATE INDEX CONCURRENTLY idx_properties_featured
ON properties(featured, updated_at DESC)
WHERE featured = true AND status = 'active';
-- Query optimization example
EXPLAIN ANALYZE
SELECT p.id, p.title, p.price, p.location_point,
array_agg(DISTINCT i.url) as images
FROM properties p
LEFT JOIN property_images i ON p.id = i.property_id
WHERE p.price BETWEEN $1 AND $2
AND p.location_id = $3
AND p.status = 'active'
GROUP BY p.id, p.title, p.price, p.location_point
ORDER BY p.featured DESC, p.updated_at DESC
LIMIT 20;
Frontend Performance Revolution
We rebuilt the frontend using Next.js with aggressive optimization:
// Optimized Property Listing Component
import { memo, useMemo } from 'react';
import Image from 'next/image';
import { useVirtualizer } from '@tanstack/react-virtual';
interface PropertyListProps {
properties: Property[];
onPropertyClick: (id: string) => void;
}
const PropertyList = memo(({ properties, onPropertyClick }: PropertyListProps) => {
const parentRef = useRef(null);
// Virtual scrolling for large lists
const virtualizer = useVirtualizer({
count: properties.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 320, // Estimated property card height
overscan: 5
});
const virtualItems = virtualizer.getVirtualItems();
return (
ref={parentRef}
className="property-list"
style={{ height: '600px', overflow: 'auto' }}
>
style={{
height: ${virtualizer.getTotalSize()}px
,
width: '100%',
position: 'relative'
}}
>
{virtualItems.map((virtualItem) => {
const property = properties[virtualItem.index];
return (
key={property.id}
property={property}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: ${virtualItem.size}px
,
transform: translateY(${virtualItem.start}px)
}}
onClick={() => onPropertyClick(property.id)}
/>
);
})}
);
});
// Optimized Property Card with lazy loading
const PropertyCard = memo(({ property, style, onClick }: PropertyCardProps) => {
const [imageLoaded, setImageLoaded] = useState(false);
return (
src={property.thumbnail}
alt={property.title}
width={300}
height={200}
loading="lazy"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
onLoadingComplete={() => setImageLoaded(true)}
className={imageLoaded ? 'loaded' : 'loading'}
/>
{property.featured && (
Featured
)}
{property.title}
{formatPrice(property.price)}
{property.location}
{property.features.slice(0, 3).map(feature => (
{feature}
))}
);
});
Caching Strategy: Multi-Layer Approach
// Comprehensive caching strategy
class CacheManager {
private redis: Redis;
private cdnInvalidator: CDNInvalidator;
// Application-level caching
async cachePropertyData(propertyId: string, data: Property): Promise {
const cacheKeys = [
property:${propertyId}
,
property:search:${data.location_id}:${data.property_type}
,
property:featured
];
const pipeline = this.redis.pipeline();
// Cache individual property (24h)
pipeline.setex(property:${propertyId}
, 86400, JSON.stringify(data));
// Cache search results (5min)
pipeline.setex(search:location:${data.location_id}
, 300, JSON.stringify(data));
// Update featured properties list
if (data.featured) {
pipeline.zadd('featured:properties', Date.now(), propertyId);
}
await pipeline.exec();
}
// CDN cache management
async invalidatePropertyCache(propertyId: string): Promise {
// Invalidate CDN cache
await this.cdnInvalidator.purge([
/property/${propertyId}
,
/api/properties/${propertyId}
,
'/api/properties/search*'
]);
// Clear Redis cache
const pattern = *${propertyId}*
;
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
}
The Results: Performance Transformation
Before vs After Metrics
| Metric | Before | After | Improvement |
|--------|--------|--------|-------------|
| Page Load Time | 8.2s | 1.3s | 84% faster |
| Time to Interactive | 12.1s | 2.1s | 83% faster |
| Search Response | 3.2s | 180ms | 94% faster |
| Mobile Performance | 23/100 | 91/100 | 295% improvement |
| Bounce Rate | 67% | 28% | 58% reduction |
Business Impact
The performance improvements translated directly to business results:
Technical Achievements
// Performance monitoring implementation
class PerformanceMonitor {
async trackPageLoad(route: string, loadTime: number): Promise {
// Real User Monitoring (RUM)
await this.analytics.track('page.load', {
route,
loadTime,
userAgent: navigator.userAgent,
connection: (navigator as any).connection?.effectiveType,
timestamp: Date.now()
});
// Alert if performance degrades
if (loadTime > 2000) {
await this.alerting.send({
level: 'warning',
message: Page load time exceeded threshold: ${loadTime}ms for ${route}
,
metadata: { route, loadTime }
});
}
}
async trackSearchPerformance(query: string, resultCount: number, responseTime: number): Promise {
await this.analytics.track('search.performance', {
query: this.hashQuery(query), // Privacy-conscious logging
resultCount,
responseTime,
timestamp: Date.now()
});
}
}
Key Lessons Learned
1. Performance is a Feature
User experience directly correlates with business metrics. A 1-second delay can cost 7% in conversions.
2. Database Optimization is Critical
Proper indexing and query optimization provided 10x performance improvements with minimal infrastructure cost.
3. Caching Strategy Matters
Multi-layer caching (CDN, Redis, Application) reduced database load by 89% and improved response times dramatically.
4. Monitor Everything
Real-time performance monitoring allowed us to catch regressions before they impacted users.
The Architecture in Production
Our final architecture handled:
// Production deployment configuration
const productionConfig = {
database: {
host: process.env.DB_HOST,
pool: {
min: 10,
max: 30,
acquireTimeoutMillis: 60000,
idleTimeoutMillis: 600000
},
ssl: { rejectUnauthorized: false }
},
redis: {
cluster: true,
nodes: [
{ host: 'redis-1.prod', port: 6379 },
{ host: 'redis-2.prod', port: 6379 },
{ host: 'redis-3.prod', port: 6379 }
]
},
elasticsearch: {
nodes: ['https://es-1.prod:9200', 'https://es-2.prod:9200'],
maxRetries: 3,
requestTimeout: 60000
}
};
Conclusion
This project demonstrated that systematic performance optimization can transform both user experience and business outcomes. By focusing on database optimization, implementing intelligent caching, and rebuilding the frontend with performance-first principles, we achieved a 10x improvement in key metrics.
*Key Takeaways:*
---
Building a high-performance platform for your business? [Let's discuss](/contact) how LaNuit Tech can optimize your system for scale.