From 8-Second Load Times to Instant: How We Rebuilt a Proptech Analytics Dashboard Handling 2 Million Property Records — A Vietnam Offshore Case Study

(Case Studies) - A B2B proptech platform in Seattle was drowning in 8-second dashboard loads, daily PostgreSQL deadlocks, and a legacy Ruby on Rails monolith that couldn't scale. We rebuilt the entire analytics layer in 10 weeks with a Vietnamese team using ClickHouse and Go. Here's the exact architecture and every painful lesson we learned.

From 8-Second Load Times to Instant: How We Rebuilt a Proptech Analytics Dashboard Handling 2 Million Property Records — A Vietnam Offshore Case Study

The CEO called me on a Tuesday afternoon. “Our biggest client just threatened to churn,” he said. “Their property management team waits 8 seconds for a dashboard to load. They’re watching Zillow load faster than their own data.”

I knew the numbers. The platform tracked 2.1 million property records across 14 US metro areas. The legacy Rails monolith and PostgreSQL-backed analytics couldn’t keep up. P95 query times hit 8.4 seconds. Monthly ingestion jobs timed out regularly. And every time a power user refreshed their portfolio view, the database nearly fell over.

How We Integrated AI Coding Tools Into Our CI/CD Pipeline (And Why Your Team Should Too)

How We Integrated AI Coding Tools Into Our CI/CD Pipeline (And Why Your Team Should Too)

How We Integrated AI Coding Tools Into Our CI/CD Pipeline (And Why Your Team Should Too) Let’s be… ...

We had a choice: throw more hardware at it, or rebuild the analytics layer from scratch. We chose the latter — and we chose to do it with a Vietnamese team from ECOA AI.

Here’s exactly what we did, what we used, and what broke along the way.

Generative Engine Optimization (GEO): How to Optimize Your Brand for AI Search Engines

Generative Engine Optimization (GEO): How to Optimize Your Brand for AI Search Engines

Generative Engine Optimization (GEO) is reshaping how brands get discovered in the AI era. This guide explains how… ...

The Mess We Inherited

The existing system was a textbook example of “it worked at 100k records, it died at 2 million.”

The stack (if you can call it that):

  • Ruby on Rails monolith (Rails 5.2, Ruby 2.6)
  • PostgreSQL 12 running on a single r5.4xlarge instance
  • A nightly cron job that ran 14 aggregations across property listings
  • Redis-backed job queue that regularly OOM’d

The daily ingestion pipeline pulled data from 22 MLS APIs. Each night, a Sidekiq worker processed 80,000–120,000 new or updated property records. That job routinely took 6 to 9 hours. When it failed — and it failed often — the entire morning’s analytics were stale.

But the real pain was the portfolio dashboard. Property managers would load 500+ properties with 24 metrics each. The Rails controller made 18 separate queries, nested with N+1 patterns. Some queries scanned entire tables.

I profiled one session. A single dashboard page generated 2.4 GB of temporary data in PostgreSQL’s temp tablespace.

We were renting a dumpster for every page load.

Why We Didn’t Just “Fix PostgreSQL”

Honestly, we could have optimized. Better indexes. Materialized views. Maybe shard the database.

But here’s the thing — the client wanted sub-second query response on any dimension: by city, by property type, by price range, by days on market, by listing agent. That meant pre-aggregating across too many combinations. PostgreSQL materialized views would have exploded our storage costs, and refreshing them would still take hours.

We needed a columnar store designed for real-time analytics.

We chose ClickHouse.

The Team: Why Vietnam, Why ECOA AI

We didn’t have the bandwidth in-house. Our US-based team was buried in feature work for the next quarter. We needed a dedicated squad that could own the full analytics rebuild — from schema design to deployment.

We considered Eastern Europe (too expensive for a 6-person team), India (too many bad experiences with communication gaps), and finally landed on Vietnam.

Why? Three reasons:

  1. Technical competence. The senior engineers we interviewed from Ho Chi Minh City and Can Tho had deep experience with Go, ClickHouse, and high-throughput systems. We didn’t have to teach anyone the fundamentals.
  2. Time zone overlap. Vietnam is UTC+7, which is a solid 12-hour overlap with US Pacific time in the morning. We set standups at 8 AM PT / 11 PM Vietnam time. It worked.
  3. Cost efficiency. We got 2 senior engineers, 2 middle engineers, and 1 DevOps person for what we’d pay 2 senior engineers in the US. The ECOA AI Platform ACP orchestration layer also helped them work 3–5x faster on boilerplate and testing.

The team was based in Can Tho — a smaller tech hub in the Mekong Delta with growing engineering talent.

The Architecture We Built

We didn’t rebuild the whole app. We carved out the analytics layer as a separate Go service that reads from ClickHouse and serves a JSON API. The Rails app talks to it via gRPC.

Here’s the exact architecture:


[MLS APIs] → [Go ingestion worker] → [Kafka topic: property_events]
                                           ↓
                                   [ClickHouse shard cluster (3 nodes)]
                                           ↓
                                    [Go analytics API (gRPC)]
                                           ↓
                                    [Rails app → frontend]

Key decisions:

  • Go for everything new. The Vietnamese team was strong in Go. No overhead, fast cold starts, easy to deploy.
  • ClickHouse with 3 shards and 2 replicas. Each shard handles ~700k properties. Replication ensures we don’t lose data if a node dies.
  • Kafka for decoupling ingestion from querying. Property events flow into Kafka, a consumer writes to ClickHouse in micro-batches of 10,000 records every 5 seconds.
  • Pre-aggregated materialized views in ClickHouse. We built 6 materialized views covering the most common query patterns. The raw table stores all properties. The aggregated tables store pre-computed metrics by city, by property type, and by month.

The Vietnamese team designed and implemented the ClickHouse schema in week 2. That’s fast. They’d done this before — multiple times.

The Migration: What Actually Happened

Week 1-2: Schema design and proof of concept. The team built a small pipeline that ingested data from 2 MLS APIs into ClickHouse. Query times went from 8 seconds to 40 milliseconds. That was the moment the CEO said “go.”

Week 3-5: Full ingestion pipeline. The team wrote the Go ingestion service, set up Kafka, and wired 22 MLS APIs into the pipeline. This is where things got real.

Pain point #1: Data inconsistency across MLS APIs. Some APIs sent coordinates as (lat, lng), others as (lng, lat). One API sent square footage as a string like “1,234 sq ft”. The team wrote a normalization layer that handled 22 different data formats. It was tedious, but they shipped it without complaining.

Week 6-8: The analytics API and frontend integration. The team built the gRPC API in Go and worked with our in-house frontend devs to wire it up.

Pain point #2: The old API returned data in a different shape. The Rails app expected nested JSON objects with specific key names. The new Go API returned cleaner, flatter structures. We had to write a compatibility layer that transformed responses. Not the team’s fault — it was scope creep. They handled it in 3 days.

Week 9-10: Cutover and load testing. We ran both systems in parallel for a week. Every query hit both PostgreSQL and ClickHouse. We compared results, latency, and error rates.

The results:

  • P95 query latency: 8.4 seconds → 94 milliseconds (a 98.9% reduction)
  • Daily ingestion time: 6–9 hours → 14 minutes
  • Concurrent dashboard users: capped at 40 → unlimited (tested up to 500)
  • Database costs: $1,800/month for PostgreSQL → $620/month for ClickHouse + Kafka

The CEO’s client noticed immediately. They said the dashboard “felt like a different product.”

What Broke (And How We Fixed It)

I won’t pretend this was smooth. Three things went wrong:

1. The ClickHouse merge tree caused query slowdowns during ingestion. When we bulk-inserted data while queries ran, ClickHouse’s merge process occasionally spiked CPU to 90%. Solution: we throttled ingestion to a max of 50,000 inserts per second and scheduled heavy merges during off-hours.

2. gRPC connection pooling issues. The Rails app’s gRPC client leaked connections under load. We had to implement proper connection pooling with a max of 20 concurrent connections per process. The Vietnamese team identified this with a simple `netstat` dump during load testing.

3. Time zone hell. Property dates came in 4 different time zones. MLS APIs don’t care about your sanity. We had to build a normalization layer that converted everything to UTC before storing, then converted back for display based on the property’s metro area.

The Real Metric: Developer Velocity

The Vietnamese team using the ECOA AI Platform ACP achieved 3.2x output compared to what we’d historically seen from offshore teams. The ACP platform handled boilerplate code generation, automated test scaffolding, and orchestrated multi-step data pipeline tasks.

For example, writing a new ClickHouse materialized view typically takes a senior engineer 2–3 hours including testing. The team was shipping them in 45 minutes using AI-augmented code generation through the platform.

That’s not a flex. It’s a measurable productivity gain.

Here’s a breakdown of velocity:

Task Estimated time (manual) Actual time (with ECOA AI Platform ACP)
ClickHouse schema + materialized views 5 days 2 days
Go ingestion service (22 API connectors) 15 days 6 days
gRPC API with 18 endpoints 8 days 3 days
Integration + load testing 7 days 4 days
Total 35 days 15 days

More importantly, the team didn’t cut corners. Code review quality was on par with our internal team. The senior engineers in Can Tho had real production experience — one had spent 4 years building real-time analytics for a Vietnamese e-commerce company doing 1 million orders per day.

Would I Do It Again? Here’s the Honest Answer

Yes. But not blindly.

The rebuild worked because we isolated the analytics layer from the rest of the monolith. We didn’t try to rewrite everything. We found the biggest pain point (dashboard latency), carved it out, and rebuilt it with the right stack.

The Vietnamese team worked because we invested in the relationship. We had daily standups. We shared dashboards. We treated them as core team members, not contractors. That matters more than any technology choice.

Would this work for every project? No. If your codebase is tightly coupled and your team isn’t willing to change how they work, an offshore team will struggle.

But if you have a clear pain point, a defined scope, and the willingness to let a capable team run with the right tools — it’s almost unfair how fast things can move.

The CEO’s client? They signed a 2-year renewal. The CEO now asks when we’re rebuilding the rest of the platform.

Frequently Asked Questions

Why did you choose ClickHouse over TimescaleDB or Apache Druid?

We evaluated both. TimescaleDB is solid for time-series workloads, but our queries were heavily dimensional (filtering by city, property type, price range, etc.). ClickHouse’s columnar storage and materialized views give us sub-100ms query times on those patterns without pre-computing every possible combination. Druid was overkill for 2 million records — it’s designed for billions of events per day. ClickHouse hit the sweet spot.

How did you handle data consistency between PostgreSQL and ClickHouse during the cutover period?

We ran a dual-write pattern for one week. Every new property record was written to both PostgreSQL (legacy) and Kafka → ClickHouse (new). A reconciliation job ran every 6 hours, comparing record counts and flagging mismatches. We found fewer than 0.02% discrepancies, all caused by ClickHouse’s asynchronous merges. The Go team resolved those with a `SELECT … FINAL` query on the reconciliation pass.

What’s the cost breakdown for running ClickHouse on 3 nodes?

We run 3 Hetzner dedicated servers at €89/month each (AX42 nodes: 8 vCPU, 32GB RAM, 2x 2TB NVMe). Total infrastructure cost: ~$310/month for the ClickHouse cluster. Kafka runs on a separate €49/month node. That’s roughly $620/month total

Related reading: Why Smart CTOs Hire Vietnamese Developers: A Data-Driven Guide to Offshore Engineering

Related reading: Vietnam Outsourcing: Why Smart CTOs Are Ditching India for Southeast Asia’s Tech Hub

Leave a Comment

Your email address will not be published. Required fields are marked *

Ready to Build with AI-Powered Developers?

Hire Vietnamese engineers augmented by ECOA AI Platform + Claude Code. 5x faster, 40% cheaper.