Problem RestatementProblem
Google asked: design the ingestion and storage system for Street View imagery. Cars (and backpacks, boats...) with camera rigs capture huge volumes of high-resolution images along with GPS, time and camera pose. The system must ingest the data, process it (stitch images into 360° panoramas, blur faces and license plates for privacy, align positions), index it by location, store petabytes durably and cheaply, and serve panoramas quickly to map users worldwide.
Scale EstimatesScale
- A car captures ~7 cameras × a few frames per second × 8 hours → hundreds of GB to a few TB per car per day.
- A fleet of hundreds of cars → petabytes per month of raw data.
- Serving: millions of panorama views per day, each loading multiple image tiles.
ArchitectureArchitecture
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
CAR["Capture vehicles - raw images + GPS/IMU"] -->|"ship drives / high-speed upload at depots"| ING["Ingestion stations"]
ING --> RAW[("Raw storage - object store, cold tier")]
ING --> Q[("Processing jobs queue")]
Q --> P1["Pose alignment (GPS + IMU + SfM)"]
P1 --> P2["Stitch 360 panoramas"]
P2 --> P3["Privacy: detect + blur faces, plates"]
P3 --> P4["Tile + multi-resolution encode"]
P4 --> PUB[("Published panoramas - tiles")]
P4 --> IDX[("Geo index - location to panorama ids")]
PUB --> CDN["CDN"]
U["Map users"] --> API["Panorama API"]
API --> IDX
U --> CDNDeep Dive — Getting terabytes off the carsDeep dive
Each capture vehicle produces terabytes a day from a camera rig. The transfer is the first bottleneck, and getting it wrong corrupts everything downstream.
Upload over cellular as you drive
Stream captured imagery to the cloud from the vehicle.
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
CAR["Vehicle - terabytes per day"] --> LTE["Cellular uplink - a few Mbps in practice"]
LTE --> MATH["A day's capture needs weeks of uplink"]
LTE --> DROP["Coverage gaps mid-route - partial transfers"]
DROP --> COST["Cellular data cost dwarfs the storage cost"]The arithmetic fails by orders of magnitude: the uplink is thousands of times slower than the capture rate. Coverage gaps also mean transfers are interrupted constantly, so partial and duplicate data is the normal case.
Offload at a depot over a fast network
Drive back and transfer over the depot's high-speed network, or ship physical drives for remote regions.
This matches the transfer medium to the volume — the only approach where the numbers work. What it does not yet address is integrity: a drive can be damaged in transit, a transfer can truncate, and a file can be silently corrupted. Discovering that after the raw capture has been deleted is unrecoverable.
Manifests, checksums, and keep the raw data
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
CAP["Capture session - assigned an ID"] --> MAN["Manifest: file list + checksums"]
MAN --> DEPOT["Depot transfer or shipped drives"]
DEPOT --> VERIFY{"Verify every checksum"}
VERIFY -->|"mismatch"| REDO["Re-transfer that file - the source still has it"]
VERIFY -->|"ok"| COLD[("Object storage - cold tier, raw kept")]
CAP --> META["Catalog DB: session, time, GPS track, camera calibration"]
COLD --> RE["Reprocess later with better algorithms"]- A session id and a manifest make the transfer verifiable. Without a declared file list, a missing file is indistinguishable from a file that was never captured — and nobody notices until a street is missing from the map.
- Verify checksums before deleting anything at the source. The vehicle's copy is the only fallback, so it is released only once the ingested copy is proven intact.
- Keep the raw data on a cold tier. Blur models, stitching and depth estimation all improve, and reprocessing five-year-old capture with a better algorithm is far cheaper than re-driving the street. This is the decision that pays for itself repeatedly.
- Capture metadata alongside — GPS track, timestamps, camera calibration — because the imagery is nearly useless without knowing exactly where and how it was taken, and calibration cannot be recovered afterwards.
Ingestion is deliberately dumb: verify, store, catalogue. Everything expensive happens later in the processing DAG, where it can be retried and re-run without touching the vehicles again.
Processing Pipeline (a DAG of batch jobs)
- Pose alignment: combine GPS, the inertial sensor (IMU) and image matching (structure-from-motion) to get the precise position and orientation of every frame.
- Stitching: merge the camera images into seamless 360° panoramas.
- Privacy blurring: ML models detect faces and license plates, and blur them before anything is published. This is a hard requirement, and it also handles user blur requests (e.g., "blur my house").
- Tiling: cut each panorama into tiles at multiple zoom levels (like map tiles), and encode them efficiently (WebP/AVIF).
- Quality checks: blurry or dark images and failed stitches are flagged and dropped or re-queued.
- Each stage is idempotent and retried on failure, and progress is tracked per capture session, so a failure doesn't restart everything.
- Runs on large batch clusters (thousands of machines), prioritizing fresh areas or high-demand cities.
Storage and Indexing
- Tiers: raw (cold, cheapest, keep for reprocessing), processed panoramas (warm), and popular tiles cached at the CDN (hot).
- Geo index: panorama IDs indexed by location (S2 cells or geohash), plus the capture date, so "the nearest panorama to this point" and "older imagery (time travel)" are fast lookups.
- Graph of links: each panorama stores its neighbors, so users can "walk" along the street.
Serving
- The client asks the API for the panorama near a location → gets metadata and tile URLs → loads visible tiles from the CDN at the needed zoom (low resolution first, then sharper).
- Tiles are immutable (versioned URLs), which makes them ideal for long CDN caching.
Wrap-UpWrap-up
Offload capture data at depots or via shipped drives, verify it and keep raw data in cold object storage with a metadata catalog. Run an idempotent, retryable batch DAG for pose alignment, stitching, mandatory face and plate blurring, quality checks and multi-resolution tiling. Publish immutable tiles behind a CDN, index panoramas by S2/geohash cell and date with neighbor links, tier storage by temperature, and reprocess from raw data when algorithms improve.