Back to Blog
January 20, 2024
12 min

Case Study: Building a High-Performance Real Estate Platform

How we architected and delivered a high-performance real estate platform with optimized response times.

Case StudyArchitecturePerformanceReal Estate

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

  • **Current Performance**: 8.2s average page load
  • **User Bounce Rate**: 67% (industry average: 32%)
  • **Mobile Performance**: Virtually unusable
  • **Search Queries**: Timing out at peak hours
  • **Revenue Impact**: 23% decline in lead generation

  • 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:


  • **Lead Generation**: +47% increase in contact form submissions
  • **User Engagement**: +125% increase in average session duration
  • **Mobile Conversion**: +89% increase in mobile leads
  • **SEO Rankings**: Average 23 position improvement for target keywords
  • **Revenue**: +31% increase in subscription renewals

  • 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:

  • **50,000+ concurrent users** during peak hours
  • **10M+ API requests** per month
  • **99.97% uptime** over 12 months
  • **Sub-200ms** average API response times
  • **CDN cache hit rate**: 94%

  • // 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:*

  • Start with database optimization for maximum impact
  • Implement multi-layer caching strategically
  • Use modern frontend techniques (virtual scrolling, lazy loading)
  • Monitor performance continuously
  • Performance improvements directly drive business results

  • ---


    Building a high-performance platform for your business? [Let's discuss](/contact) how LaNuit Tech can optimize your system for scale.


    Catherina Al Skaff

    Founder of LaNuit Tech