Google Photos#
Problem statement (interviewer prompt)
Design Google Photos: backup photos + videos from phones, multi-resolution serving, ML-powered search (objects, faces, scenes, OCR), albums, memories, and shared albums. Handle 100B+ photos across 1B+ users, with face clustering done per-user-only.
flowchart LR
C([Phone])
UP[Upload]
S3[(Originals)]
TR[Thumbs / Resize]
CDN
ML([ML tagging: face / OCR / object])
IDX[(Search index)]
ALB[Albums / Memories]
C --> UP --> S3 --> TR --> CDN
S3 --> ML --> IDX
C --> ALB --> IDX
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class C client;
class UP,TR,ALB service;
class S3,IDX datastore;
class ML compute;
flowchart TB
subgraph Client[Clients]
PH([Phone])
WEB([Web])
DESK[Desktop sync]
end
subgraph Upload[Upload Pipeline]
PRE[Pre-signed URL]
CHUNK[Chunked + resumable upload]
HASH[SHA-256 + perceptual hash]
DEDUP([Dedup against user library])
ENC[Encrypt at rest]
end
subgraph Storage[Storage Tiers]
HOT[(Hot tier - SSD)]
COLD[(Cold tier - HDD/Tape)]
ORIG[(Originals)]
DERIV[(Derivatives<br/>thumbs 320/720/1080/4K)]
CDN[Photos CDN]
end
subgraph Process[Processing]
THUMB[Thumbnailer]
EXIF([EXIF parser<br/>date, GPS, camera])
LIVE[Live photo / motion]
HEVC([HEIC → JPEG/WebP transcoder])
VIDEO([Video transcoder ladder])
end
subgraph ML[ML Tagging]
FACE([Face clustering<br/>per-user-only])
OBJ[Object detection<br/>cat, dog, beach, food]
OCR([OCR + scene text])
LANDMARK[Landmark recognition]
SAFE([NSFW / CSAM scan])
EMB([Image embeddings])
end
subgraph Search[Search]
INV[(Inverted index<br/>per user)]
NLQ["Natural language query<br/>beach 2019 to labels + date"]
PEOPLE[People search]
end
subgraph Albums[Albums & Memories]
ALB[Album service]
MEM([Memories generator<br/>this day, smart highlights])
SHARE[Shared albums]
PARTNER([Partner sharing])
end
subgraph Sync[Sync & Backup]
SYNCSVC([Sync engine])
NOTIF[Realtime notify]
OFFLINE[[Offline queue]]
end
subgraph Meta
META[(Metadata DB)]
AUDIT[(Audit log)]
QUOTA[Quota / Plan]
end
Client --> PRE --> CHUNK --> ORIG
CHUNK --> HASH --> DEDUP
CHUNK --> ENC
ORIG --> THUMB --> DERIV --> CDN
ORIG --> EXIF --> META
ORIG --> HEVC --> DERIV
ORIG --> VIDEO --> DERIV
ORIG --> ML
ML --> EMB --> Search
ML --> META
Client --> Search
Search --> INV
Client --> Albums
Albums --> META
Sync --- Client
Sync --- ORIG
Sync --- META
ORIG -. age out .-> COLD
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class PH,WEB,DEDUP,FACE,PARTNER client;
class CDN edge;
class DESK,PRE,CHUNK,HASH,ENC,THUMB,LIVE,OBJ,LANDMARK,NLQ,PEOPLE,ALB,SHARE,NOTIF,QUOTA service;
class HOT,COLD,ORIG,DERIV,INV,META,AUDIT datastore;
class OFFLINE queue;
class EXIF,HEVC,VIDEO,OCR,SAFE,EMB,MEM,SYNCSVC compute;
Storage tiering#
- Originals always retained (per quota).
- Derivatives regenerated lazily if missing - cheaper than always storing every size × format.
- Cold tier on long-term storage for old photos with low access prob.
Face clustering (privacy-aware)#
- Faces detected and grouped only within the user's library - never cross-user.
- User confirms or merges clusters; not used for cross-user identification.
Search#
- Combine inverted index over labels (cat, beach) with structured filters (date, location).
- Image embedding search: text query → image embedding via CLIP-like model → ANN.
Sharing#
- Per-album share token (capability URL); access control on view.
- Partner sharing: bi-directional auto-share of selected albums.
Capacity#
- Many billions of photos; ~5 MB / photo avg ≈ exabytes total.
- Dedup saves significant cost when users re-upload same files across devices.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
CDN | edge caching for static assets | cdn |
HLD |
Search internals | inverted index, BM25, embeddings, ANN | search-internals |
LLD |
REST API design | verbs, statuses, pagination, errors | rest-api-design |
Quick reference#
Functional#
- Backup photos + videos from phones / desktops.
- Multi-resolution display + downloads.
- ML-driven search (objects, faces, text, locations).
- Albums, memories, partner sharing.
- Live / motion photos, RAW handling.
Non-functional#
- 11×9s durability.
- p99 thumbnail load < 300 ms (CDN).
- Search response < 500 ms.
- 1B+ users; many EB of storage.
Capacity#
- 100B+ photos. Avg original 4 MB; full ladder 6× = ~25 MB per item.
- Hot tier holds last ~30 days; rest cold.
- Search index per user ~MB, fits sharded easily.
Schema#
media(id, owner_id, type, ts, original_ref, ladders[], hash, exif)albums(id, owner, [media_ids])labels(media_id, label, score, source)faces(user_id, face_id, embedding, cluster_id)
Trade-offs#
- Original retention vs storage cost: cold-tier old + dedup.
- Server-side ML vs client-side: cloud is much more capable; offload to keep client battery.
- Cross-user dedup: huge savings but ownership/privacy hazards - Google avoids it for non-identical content.
- Live photos = paired image + short video; transcode strategy diverges.
Refs#
- Google Photos engineering posts (face clustering, search architecture).
- Apple iCloud Photos talks (different storage approach).
- Image dedup papers; CLIP / vision embeddings for search.
- ByteByteGo "Design Google Photos".
FAQ#
How does Google Photos back up 100 billion photos across users?#
Originals land in distributed object storage while a parallel pipeline produces multiple thumbnail sizes. Metadata, EXIF, and ML labels go to a sharded index optimized for search and timeline scans.
How does face grouping work in Google Photos?#
Face embeddings are computed per photo and clustered per user only, so identities never cross account boundaries. The user can name a cluster, which becomes the label for future matches.
What ML features power photo search?#
Object detection, scene classification, OCR for text in images, and face clustering produce vectors and labels. A search index lets queries like beach 2022 mom return ranked results.
How does Google Photos avoid duplicate uploads?#
Clients hash each photo before upload. If the hash already exists in the user's library, the upload is skipped. Block level dedup also kicks in when many users save the same shared image.
How are thumbnails generated and delivered?#
On upload, a transcoder produces a fixed set of resolutions for grid views, zoom, and shares. Thumbnails are CDN cached aggressively while originals live in colder storage tiers.