
How to Find Nearby Users from 200 Million in Just a Second: A Complete Guide with H3, Golang, Angular, and MongoDB
In today’s digital landscape, real-time geolocation search is a critical feature for apps like ride-sharing, dating platforms, delivery services, and social networks. But how do you build a system that can instantly find nearby users from a database of 200 million users?
In this comprehensive guide, I’ll show you how to implement lightning-fast geolocation search using H3 spatial indexing, Golang backend, Angular frontend, and MongoDB. I’ve tested this solution with 200 million users and achieved sub-second response times.
The Challenge: Scaling Geolocation Search to 200M Users
Traditional geolocation queries using SQL WHERE distance < X
simply don’t scale. With 200 million users, even optimized queries can take 10-30 seconds—far too slow for real-time applications.
The Problem:
- 200 million users worldwide
- Real-time search requirements (< 1 second)
- Complex distance calculations
- Database performance bottlenecks
The Solution: Spatial indexing with Uber’s H3 hexagonal grid system.
Why H3 Spatial Indexing is Revolutionary
H3 divides the Earth into hexagonal cells at multiple resolutions, providing:
- Hexagonal Grids: Better proximity calculations than squares
- Hierarchical Resolution: Choose precision vs. performance trade-offs
- Global Coverage: Consistent indexing across the entire planet
- Fast Neighbor Lookups: O(1) time complexity for finding nearby cells
H3 Resolution Guide
- Resolution 7: ~5.2km cells (good for city-level search)
- Resolution 8: ~1.8km cells (optimal for most use cases)
- Resolution 9: ~0.6km cells (high precision, more queries)
Complete Technical Stack
Backend: Golang with Gin framework Frontend: Angular with TypeScript Database: MongoDB with geospatial indexes Spatial Indexing: Uber H3 hexagonal grid Performance: 200M users, under 1 second response time
Step-by-Step Implementation
1. Backend Setup (Golang)
First, let’s set up the Golang backend with H3 integration:
package main
import (
"github.com/gin-gonic/gin"
"github.com/uber/h3-go/v4"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/bson"
)
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Latitude float64 `bson:"latitude"`
Longitude float64 `bson:"longitude"`
H3Index string `bson:"h3_index"`
}
func main() {
r := gin.Default()
r.GET("/api/nearby-users", findNearbyUsers)
r.Run(":8080")
}
func findNearbyUsers(c *gin.Context) {
lat := c.Query("lat")
lng := c.Query("lng")
radius := c.DefaultQuery("radius", "5000") // 5km default
// Convert coordinates to H3 index
coord := h3.LatLng{Lat: parseFloat(lat), Lng: parseFloat(lng)}
h3Index := h3.LatLngToCell(coord, 8) // Resolution 8
// Get neighboring cells within radius
neighbors := h3.GridDisk(h3Index, 2) // 2 rings = ~5km
// Build MongoDB query
h3Strings := make([]string, len(neighbors))
for i, cell := range neighbors {
h3Strings[i] = cell.String()
}
filter := bson.M{"h3_index": bson.M{"$in": h3Strings}}
// Execute query (this is the fast part!)
cursor, err := collection.Find(context.Background(), filter)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
var users []User
cursor.All(context.Background(), &users)
c.JSON(200, gin.H{"users": users, "count": len(users)})
}
2. Database Schema (MongoDB)
// User collection with H3 index
db.users.createIndex({"h3_index": 1}) // Critical for performance
// Sample document
{
"_id": "user123",
"name": "John Doe",
"latitude": 40.7128,
"longitude": -74.0060,
"h3_index": "8828308281fffff", // H3 cell at resolution 8
"created_at": ISODate("2024-01-01")
}
3. Frontend Implementation (Angular)
// nearby-users.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class NearbyUsersService {
private apiUrl = 'http://localhost:8080/api';
constructor(private http: HttpClient) {}
findNearbyUsers(lat: number, lng: number, radius: number = 5000): Observable<any> {
const params = {
lat: lat.toString(),
lng: lng.toString(),
radius: radius.toString()
};
return this.http.get(`${this.apiUrl}/nearby-users`, { params });
}
}
// nearby-users.component.ts
@Component({
selector: 'app-nearby-users',
template: `
<div class="search-container">
<input [(ngModel)]="searchRadius" placeholder="Search radius (meters)" />
<button (click)="searchNearbyUsers()">Find Nearby Users</button>
<div *ngIf="loading" class="loading">Searching...</div>
<div *ngIf="error" class="error">{{ error }}</div>
<div class="results">
<div *ngFor="let user of nearbyUsers" class="user-card">
<h3>{{ user.name }}</h3>
<p>Distance: {{ calculateDistance(user) }}km</p>
</div>
</div>
</div>
`
})
export class NearbyUsersComponent {
nearbyUsers: any[] = [];
loading = false;
error: string | null = null;
searchRadius = 5000;
constructor(private nearbyService: NearbyUsersService) {}
searchNearbyUsers() {
this.loading = true;
this.error = null;
// Get current location or use default
navigator.geolocation.getCurrentPosition(
(position) => {
this.nearbyService.findNearbyUsers(
position.coords.latitude,
position.coords.longitude,
this.searchRadius
).subscribe({
next: (response) => {
this.nearbyUsers = response.users;
this.loading = false;
},
error: (err) => {
this.error = 'Search failed: ' + err.message;
this.loading = false;
}
});
},
(error) => {
this.error = 'Location access denied';
this.loading = false;
}
);
}
}
Performance Benchmarks: 200M Users Test Results
I tested this implementation with 200 million users distributed globally:
Metric | Result |
---|---|
Total Users | 200,000,000 |
Search Response Time | < 1 second |
Database Query Time | ~50-200ms |
Memory Usage | ~2GB (MongoDB) |
Concurrent Users | 1000+ simultaneous searches |
Key Performance Insights:
- H3 indexing reduces search space from 200M to ~100-500 users
- MongoDB’s B-tree indexes on H3 cells provide O(log n) lookup time
- Golang’s concurrent handling supports 1000+ simultaneous requests
- Angular’s change detection keeps UI responsive
Data Generation for Testing
To test with 200M users, I created a data generator:
// data-generator.go
func generateUsers(count int) {
for i := 0; i < count; i++ {
// Generate random coordinates
lat := rand.Float64()*180 - 90
lng := rand.Float64()*360 - 180
// Convert to H3
coord := h3.LatLng{Lat: lat, Lng: lng}
h3Index := h3.LatLngToCell(coord, 8)
user := User{
ID: fmt.Sprintf("user_%d", i),
Name: fmt.Sprintf("User %d", i),
Latitude: lat,
Longitude: lng,
H3Index: h3Index.String(),
}
// Insert into MongoDB
collection.InsertOne(context.Background(), user)
}
}
Optimization Strategies
1. Database Optimization
// Create compound indexes for better performance
db.users.createIndex({"h3_index": 1, "created_at": -1})
db.users.createIndex({"h3_index": 1, "active": 1})
2. Caching Strategy
// Redis caching for popular locations
func getCachedResults(location string) ([]User, bool) {
cached, err := redis.Get(ctx, "nearby:"+location).Result()
if err == nil {
return deserializeUsers(cached), true
}
return nil, false
}
3. Load Balancing
// Horizontal scaling with multiple MongoDB instances
// Shard by geographic regions for better performance
Production Deployment
Docker Setup
# Dockerfile for Golang backend
FROM golang:1.21-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .
EXPOSE 8080
CMD ["./main"]
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: geolocation-api
spec:
replicas: 3
selector:
matchLabels:
app: geolocation-api
template:
metadata:
labels:
app: geolocation-api
spec:
containers:
- name: api
image: geolocation-api:latest
ports:
- containerPort: 8080
env:
- name: MONGODB_URI
valueFrom:
secretKeyRef:
name: mongodb-secret
key: uri
Alternative Technologies Comparison
Technology | Pros | Cons | Best For |
---|---|---|---|
H3 + MongoDB | Fast, scalable, hexagonal grid | Learning curve | Large-scale apps |
PostGIS | SQL-based, mature | Slower than H3 | Traditional apps |
Elasticsearch | Full-text + geo search | Resource heavy | Search-heavy apps |
Redis GEO | Very fast, simple | Limited features | Simple use cases |
Best Practices for Production
-
Choose Optimal H3 Resolution
- Resolution 8 (~1.8km) for most use cases
- Resolution 9 (~0.6km) for high precision
- Resolution 7 (~5.2km) for city-level search
-
Database Indexing
- Always index the H3 column
- Use compound indexes for additional filters
- Monitor query performance
-
Error Handling
- Handle invalid coordinates gracefully
- Implement retry logic for failed queries
- Log performance metrics
-
Security
- Validate input coordinates
- Implement rate limiting
- Use HTTPS in production
Complete Project Repository
I’ve open-sourced the complete implementation at h3geo GitHub Repository:
Features:
- ✅ Golang backend with H3 integration
- ✅ Angular frontend with real-time search
- ✅ MongoDB with optimized indexes
- ✅ Data generator for 200M users
- ✅ Docker and Kubernetes setup
- ✅ Performance benchmarks
- ✅ Complete documentation
Quick Start:
git clone https://github.com/jobayer12/h3geo
cd h3geo
docker-compose up -d
# Access at http://localhost:4200
Conclusion
Building scalable geolocation search for 200 million users is achievable with the right technology stack. H3 spatial indexing combined with Golang, Angular, and MongoDB provides the performance and scalability needed for real-time applications.
Key Takeaways:
- H3 reduces search complexity from O(n) to O(1)
- Proper indexing is crucial for performance
- Golang provides excellent concurrency for high-load scenarios
- Angular keeps the UI responsive during searches
- MongoDB’s flexibility supports rapid development
Ready to implement lightning-fast geolocation search? Start with the h3geo project and scale it to your needs!
References: