❯ Real Estate · Marketplace · 2026
De-Duke Garden Care
A real-estate marketplace connecting guests with verified hosts for commercial and shortlet listings — geospatial search, escrow payments, load-tested to 50K users.
The problem
Real-estate marketplaces die on two things: listings nobody can verify and bookings that double-sell. Both are trust problems, and both surface the moment the platform grows past a friendly-owners phase. Because revenue is two-sided commission, the money path has to be correct before it can be fast.
The approach
De-Duke connects guests with verified hosts for commercial (sale/lease) and shortlet (rental) listings. The core decision was to make verification and payment correctness the architecture rather than the polish.
Hosts pass type-specific verification — Owner, Agent, Company, Lawyer, Architect, Surveyor — with manual staff review, and professional-host listings auto-approve. Payments run through one escrow spine: checkout, holds, idempotency, signature-verified webhooks, and admin-only release.
Search had to scale from day one, so the stack was chosen for it: async-first FastAPI on stateless Fargate containers behind an ALB with target-tracking autoscaling and a connection pooler, PostGIS for "near me," pgvector for semantic search, and read replicas from launch.
Architecture
- Flutter App
- Admin Console (Next.js)
- FastAPI (Fargate + ALB)
- PostgreSQL + PostGIS + pgvector
- Redis Cache
- Escrow & Payouts
- Support Chat (Firestore)
FLOWS
- Flutter AppAPIFastAPI (Fargate + ALB)
- Admin Console (Next.js)admin actionsFastAPI (Fargate + ALB)
- FastAPI (Fargate + ALB)data / near-mePostgreSQL + PostGIS + pgvector
- FastAPI (Fargate + ALB)cache / fallbackRedis Cache
- FastAPI (Fargate + ALB)paymentsEscrow & Payouts
- Flutter Appreal-timeSupport Chat (Firestore)
- FastAPI (Fargate + ALB)oversightSupport Chat (Firestore)
Search: geospatial "near me" on PostGIS plus semantic search via pgvector embeddings, cursor-based pagination, Redis caching, and timeout/circuit-breaker fallbacks to keyword search — a slow vector query degrades to keywords instead of failing the page.
Payments: Paystack checkout, 15-minute booking holds with auto-expiry, client idempotency keys, signature-verified webhooks, and admin-only escrow release into host/agency wallets, with automated Paystack Transfer withdrawals and failed-transfer reversals. Money moves only when a human confirms the booking was fulfilled.
Trust and operations: the Next.js admin console owns the moderation queue, verification review, dispute and refund workflows, chat oversight, commission-rate configuration, the Release Funds queue, and ops/revenue analytics, with every sensitive action audit-logged. The three-way support chat (client, host/agency, staff) runs in Cloud Firestore with document security rules and Firebase custom claims.
Infrastructure: Terraform for networking, Fargate, RDS, Redis, S3 + CDN, WAF, and Secrets, with split-path GitHub Actions CI/CD — ECR image builds, rolling deploys, functional and performance smoke tests with auto-rollback. Data follows NDPR-aligned retention and deletion, the API is versioned at /v1, and schema changes use expand-contract migrations.
Load testing
The k6 suite targets 50,000 concurrent users, 2,000 req/s, and a 5M-listing catalog, with a hard gate: zero double-bookings and zero duplicate charges under load. That gate is the product — an inventory system that cannot prove it will not double-sell is not an inventory system. The suite runs in CI after every deploy, so a regression in a booking path fails the pipeline before it reaches production.
Key decisions
PostGIS + pgvector side by side
Geospatial proximity and semantic search are different queries; one database, two index strategies, one query path.
Escrow is admin-only
Funds never move automatically at booking time — a human releases after verification, which is what makes disputes rare.
Load-tested to the booking gate
The k6 suite asserts zero double-bookings under 50K users because that invariant is the product.
Read replicas from launch
The first load test should not be the thing that discovers the primary can't keep up.
What I'd do differently
The read replica and connection pooler landed after the first load test humbled the launch configuration; they should have been in the initial Terraform. The admin console also grew feature by feature as operations discovered what they needed — a proper requirements review of the moderation and release queues before building would have saved several rounds.