惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网
NISL@THU
NISL@THU
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
博客园_首页
P
Privacy & Cybersecurity Law Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Know Your Adversary
Know Your Adversary
小众软件
小众软件
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
博客园 - 聂微东
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tor Project blog
C
Cybersecurity and Infrastructure Security Agency CISA
V
V2EX
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
T
The Blog of Author Tim Ferriss
美团技术团队
Jina AI
Jina AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
Cloudbric
Cloudbric
Webroot Blog
Webroot Blog
V2EX - 技术
V2EX - 技术
爱范儿
爱范儿
S
Securelist
MyScale Blog
MyScale Blog
B
Blog
AI
AI
L
LINUX DO - 热门话题
Security Archives - TechRepublic
Security Archives - TechRepublic
The Register - Security
The Register - Security
I
Intezer
有赞技术团队
有赞技术团队
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
H
Help Net Security
D
DataBreaches.Net
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
酷 壳 – CoolShell
酷 壳 – CoolShell

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
System Design - 24. Geospatial Indexing: How Uber Finds the Nearest Driver Among Millions in Milliseconds
Rajkiran · 2026-06-15 · via DEV Community

Geospatial Indexing: How Uber Finds the Nearest Driver Among Millions in Milliseconds

Series: System Design Mastery — Day 8 of 15
Reading time: 11 min
Covers: Quadtrees, Geohash, Google S2, Uber H3, Real-Time Index Updates


The Query That SQL Was Never Built For

You open Uber. The app needs to answer: "Which drivers are within 2km of this exact latitude/longitude, right now, out of the millions of drivers active worldwide?"

A naive SQL approach:

SELECT driver_id, latitude, longitude
FROM drivers
WHERE 
  SQRT(POW(latitude - :user_lat, 2) + POW(longitude - :user_lng, 2)) < 0.02

This computes the distance for every single row in the table — every active driver on Earth — for every search. At Uber's scale (millions of drivers, location updates every 3-5 seconds), this is computationally impossible to run in real time.

The fundamental problem: standard database indexes (B+ trees, hash indexes) are designed for 1-dimensional data — sort by a single value (a timestamp, an ID, a name). Latitude and longitude are 2-dimensional. A B+ tree index on latitude alone, or longitude alone, doesn't help you efficiently find "nearby" points — nearby in 2D space doesn't mean nearby in either dimension individually.

Geospatial indexing solves this by transforming 2D space into something that CAN be indexed efficiently in 1D — and there are three major approaches.


Approach 1: Quadtrees — Recursive Spatial Division

A Quadtree recursively divides 2D space into four equal quadrants, continuing to subdivide quadrants that contain "too much" data.

Start: entire map (1 region)

┌─────────────┬─────────────┐
│             │             │
│   NW        │    NE       │
│             │             │
├─────────────┼─────────────┤
│             │             │
│   SW        │    SE       │
│             │             │
└─────────────┴─────────────┘

If NE quadrant has too many points (e.g., dense city center), 
subdivide it further:

┌─────────────┬──────┬──────┐
│             │ NE-NW│ NE-NE│
│   NW        ├──────┼──────┤
│             │ NE-SW│ NE-SE│
├─────────────┼──────┴──────┤
│             │             │
│   SW        │    SE       │
│             │             │
└─────────────┴─────────────┘

This creates a tree structure where densely populated areas (city centers, with thousands of drivers per square km) have many small leaf nodes, while sparsely populated areas (rural regions, with few drivers per 100 square km) have few large leaf nodes.

Finding nearby drivers:

1. Locate the leaf node(s) containing the user's location
2. Drivers in that leaf node are candidates
3. If not enough candidates, check neighboring leaf nodes
   (a leaf representing a small area in a dense city center might
   need to check 1-2 neighbors; a leaf representing a large rural 
   area might already have all nearby drivers)

Advantages:

  • Naturally adapts to data density — dense areas get fine-grained subdivision automatically
  • Conceptually simple — a tree structure most engineers already understand

Disadvantages:

  • Tree traversal for neighbor lookups can be complex — finding "all leaves adjacent to this leaf" isn't trivial when leaves are different sizes
  • Rebalancing as data shifts (rush hour moves driver density from residential areas to business districts) requires tree restructuring

Used by: Many GIS (Geographic Information Systems) applications, MongoDB's older geospatial indexes (2dsphere indexes use a similar recursive subdivision).


Approach 2: Geohash — Encoding Location as a String

Geohash takes a completely different approach: encode a latitude/longitude pair into a single string, where the key property is — the longer the shared prefix between two geohashes, the closer the locations are.

How Geohash Encoding Works

Latitude/Longitude: (37.7749, -122.4194)  ← San Francisco

The encoding interleaves bits from latitude and longitude ranges,
recursively halving the search space:

Step 1: Is longitude in [-180, 0] or [0, 180]? 
        -122.4194 is in [-180, 0] → bit = 0
Step 2: Is latitude in [-90, 0] or [0, 90]?
        37.7749 is in [0, 90] → bit = 1
Step 3: Continue alternating longitude/latitude, halving each time...

After enough bits, group into base32 characters:
Result: "9q8yyk8ytpxr" ← this is the Geohash for San Francisco

The Prefix Property

"9q8yyk8ytpxr"  → San Francisco (precise location)
"9q8yyk8ytpx"   → San Francisco (slightly larger area, ~1m precision)
"9q8yyk8yt"     → San Francisco area (~150m precision)
"9q8yyk"        → San Francisco region (~5km precision)
"9q8y"          → Bay Area (~80km precision)
"9q"            → California / Nevada region (~1700km precision)

Each character you remove from the end roughly multiplies the represented area by 32 (since base32 has 32 possible characters per position).

Finding Nearby Points with Geohash

-- Find all locations with geohash starting with "9q8yyk" 
-- (same ~5km grid cell as our target location)
SELECT * FROM drivers WHERE geohash LIKE '9q8yyk%'

This is a prefix match — something B+ tree indexes (Topic 25) handle extremely efficiently! You've converted a 2D "nearby" query into a 1D "string prefix" query that any standard database index can answer fast.

The Edge Case: Boundary Problem

Two points that are physically VERY close to each other can have 
COMPLETELY DIFFERENT geohash prefixes if they're on opposite sides 
of a grid cell boundary:

Point A: geohash "9q8yyk..." (just inside one grid cell)
Point B: geohash "9q8yym..." (just across the boundary, 
         physically 10 meters from Point A)

A prefix search for "9q8yyk%" would MISS Point B entirely, 
even though it's very close.

The fix: When searching, also check the 8 neighboring grid cells (the cells surrounding the cell containing the query point) — not just the cell containing the point itself. Most Geohash implementations include a "neighbors" function for exactly this purpose.


Approach 3: Google S2 and Uber H3 — Cell-Based Indexing at Scale

Geohash has a subtle issue: its grid cells are rectangular, and rectangles on a sphere (the Earth) have wildly varying actual sizes depending on latitude — a "1-degree by 1-degree" cell near the equator covers a much larger physical area than the same cell near the poles. This distortion complicates distance calculations.

Google S2: Projecting the Sphere onto a Cube

S2 projects the Earth's surface onto the 6 faces of a cube, then recursively subdivides each face into a hierarchy of cells — similar in spirit to a Quadtree, but designed specifically to minimize the distortion of spherical geometry.

Earth (sphere) → projected onto cube faces → each face recursively 
subdivided into a hierarchy of cells (S2 cells)

Each S2 cell has a unique 64-bit ID.
Cells at the same "level" have roughly similar areas, 
regardless of where on Earth they are — solving the 
rectangular-distortion problem of simple lat/lng grids.

Used by: Google Maps internally for spatial indexing and proximity queries at global scale.

Uber H3: Hexagonal Grid System

Uber open-sourced H3, which divides the Earth into a hierarchical grid of hexagonal cells.

Why hexagons (and not squares/rectangles)?

Square grid:
┌───┬───┬───┐
│   │   │   │   Each cell has 8 neighbors, but 4 are "diagonal" 
├───┼───┼───┤   (share only a corner) and 4 are "adjacent" 
│   │ X │   │   (share an edge) — DIFFERENT distances to 
├───┼───┼───┤   center, creating asymmetry
│   │   │   │
└───┴───┴───┘

Hexagonal grid:
  ╱ ╲ ╱ ╲ ╱ ╲
 │   │ X │   │    Each cell has exactly 6 neighbors, 
  ╲ ╱ ╲ ╱ ╲ ╱     ALL at the SAME distance from the center.
   ╲ ╱ ╲ ╱ ╲      Uniform adjacency — no diagonal/edge distinction.

Why uniform adjacency matters for ride-hailing: When Uber's matching algorithm asks "find drivers in cells near this rider," with hexagons, "near" has a consistent, uniform meaning in every direction. With square grids, a driver in a "diagonal" cell is actually farther away than a driver in an "edge-adjacent" cell — even though both appear as "1 cell away" — creating subtle biases in matching.

H3's hierarchical resolution:

H3 has 16 resolution levels:
  Resolution 0: ~4,250,000 km² per cell (continent-scale)
  Resolution 7: ~5.2 km² per cell (city neighborhood-scale)
  Resolution 9: ~0.1 km² per cell (city block-scale)
  Resolution 15: ~0.9 m² per cell (single building-scale)

Uber typically uses resolution 7-9 for driver matching — 
city-block to neighborhood granularity.

Real Uber matching flow using H3:

1. Rider requests a trip at location (lat, lng)
2. Convert to H3 cell at resolution 9: cell_id = "8928308280fffff"
3. Query: "which drivers are in THIS cell, or its 6 neighbors?"
   (a simple lookup — drivers' current cell_ids are indexed)
4. Rank candidate drivers by actual road-network distance/ETA
5. Dispatch the best match

The H3 cell lookup (step 3) is essentially O(1) — a hash lookup by cell ID, returning a small set of candidates. The expensive part (step 4 — actual routing/ETA calculation) only runs on this small candidate set, not on every driver in the city.


Comparing the Three Approaches

Quadtree Geohash H3 / S2
Shape Rectangular, variable size (adaptive) Rectangular, fixed grid Hexagonal (H3) / roughly square (S2), fixed hierarchical grid
Adapts to density? Yes — subdivides dense areas No — fixed grid regardless of density No — fixed grid, but multiple resolution levels available
Neighbor queries Complex (variable-size neighbors) Simple but has boundary issues Simple, uniform (especially H3's hexagons)
Distortion N/A (works in 2D plane) High at extreme latitudes Low (designed for spherical geometry)
Used by GIS systems, older spatial databases General-purpose location encoding Uber (H3), Google Maps (S2)

The practical guidance: For most system design interviews, Geohash is the easiest to explain and implement (prefix matching on a string — leverages existing database indexes). H3/S2 are the "I know what real companies use at scale" answer — mentioning them, especially H3's hexagonal uniform-adjacency property, signals deeper knowledge.


Handling Real-Time Updates: The Hard Part

Static geospatial indexing (indexing restaurant locations, which rarely move) is one problem. Indexing millions of moving objects, each updating position every few seconds, is a much harder operational challenge.

The challenge:
  - 1 million active drivers
  - Each sends a GPS update every 3-5 seconds
  - = roughly 200,000-300,000 location updates PER SECOND globally

For each update:
  1. Remove driver from their OLD geo-index cell
  2. Add driver to their NEW geo-index cell
  (most updates: old cell == new cell, since drivers move slowly 
   relative to update frequency — but the system must still process it)

Why this rules out traditional databases for the "live" index:
A SQL database with a spatial index, receiving 250,000 writes/second to update positions, would be overwhelmed — especially since most "writes" are actually small UPDATEs to existing rows, which still require index maintenance.

The production pattern:

Live driver locations → Redis (in-memory, ephemeral)
  - Redis GEOADD / GEORADIUS commands provide built-in geospatial 
    indexing using geohash internally
  - In-memory writes handle 250K updates/sec easily
  - TTL on location keys — if a driver stops sending updates 
    (app crashed, phone died), they automatically disappear 
    from the index after a short timeout (e.g., 30 seconds)

Historical location data (for analytics, ETAs, surge calculation)
  → Cassandra — write-heavy, time-series friendly

Redis GEO commands (built on Geohash internally):

GEOADD drivers:active -122.4194 37.7749 "driver_12345"
GEORADIUS drivers:active -122.4194 37.7749 2 km
  → returns all drivers within 2km, computed using geohash 
    prefix matching under the hood

This is why "Redis for live location, Cassandra for history" is such a common pattern in ride-hailing and delivery system designs — it directly addresses the write-throughput requirement that a traditional spatial database can't meet.


Interview Scenario: "Design 'Find Nearby Drivers'"

The structured answer:

"I'd use a geospatial index based on Geohash or H3 — I'll go with H3 for this since it's what Uber actually uses, and its uniform hexagonal adjacency avoids the corner-case distance distortions of square grids.

Each driver's current location gets converted to an H3 cell ID at a resolution appropriate for city-block granularity — roughly resolution 8 or 9. This cell ID becomes part of the key when storing the driver's location.

For live location data, I'd use Redis — drivers send GPS updates every few seconds, and at scale that's hundreds of thousands of writes per second, which Redis handles in-memory easily. Each driver's entry has a short TTL, so a driver who stops sending updates (crashed app, dead phone) automatically disappears from the active index.

To find nearby drivers: convert the rider's location to its H3 cell, then query that cell plus its 6 neighboring cells — a simple set of lookups. This gives a small candidate set, typically tens of drivers even in a dense city, not thousands.

Only THEN do I run the expensive operation — actual routing/ETA calculation via a routing service — on this small candidate set, not on every driver in the city. The geo-index's job is to cheaply narrow millions of drivers down to dozens; the routing service's job is to rank those dozens accurately."


Key Takeaways

  • Standard B+ tree indexes can't efficiently answer 2D "nearby" queries — geospatial indexing transforms 2D proximity into something indexable.
  • Quadtree: recursive spatial subdivision, adapts to data density, but complex neighbor queries.
  • Geohash: encodes lat/lng as a string where shared prefixes = proximity — leverages standard database prefix indexes, but has a boundary problem (check neighboring cells too).
  • Google S2 projects the sphere onto a cube to minimize distortion; Uber H3 uses hexagonal cells for uniform adjacency in all directions.
  • Real-time location updates (hundreds of thousands/second) require in-memory storage — Redis GEO commands (built on Geohash) are the standard production pattern.
  • The architectural pattern: geo-index narrows millions of candidates to dozens cheaply; expensive ranking (routing/ETA) only runs on that small set.

What's Next

Topic 25 — with a grab-bag of essential data structures: Skip Lists (how Redis sorted sets work), HyperLogLog (counting a billion unique visitors with almost no memory), Tries (autocomplete), and LSM Trees vs B+ Trees (the fundamental write-optimized vs read-optimized database internals choice).


Topic 24 of the System Design Mastery series.


Tags: system-design geospatial uber data-structures backend algorithms interview-prep