Design Google Drive
Design Google Drive step by step: resumable uploads, immutable file versions, desktop sync, conflict handling, sharing, durability, and multi-region recovery.
Page content
Alice edits plan.pdf on her laptop while it is offline. Her phone still has the old file. When the laptop reconnects, a 2 GB upload should resume from the last successful part, not start over. The phone should learn that a new version exists without scanning every folder. If Alice and Bob edited the same version, neither person’s work should silently disappear.
Those are three separate paths:
Data path large file bytes move between client and object storage
Metadata path names, folders, versions, ownership, and permissions
Sync path devices learn which metadata changed since a cursor
The main design question is:
How do we store large files durably and synchronize many devices without sending bytes through API servers, rescanning the whole drive, or silently overwriting concurrent edits?
We will start with one metadata row and one object. Resumable upload, immutable versions, a per-user change log, conflict detection, and multi-region recovery will appear as the simple design meets real constraints.
1. Clarify the problem
“Design Google Drive” can mean file storage, Google Docs collaboration, enterprise search, photo backup, or all of them. That is too broad for one interview.
I would ask:
- Are we designing Drive-like files and folders, or live collaborative documents too?
- Must desktop clients synchronize offline changes?
- Do we need sharing with users, groups, and public links?
- Do we keep version history and deleted files?
- What is the maximum file size?
- Should duplicate files be deduplicated?
- What scale and durability should we support?
If the interviewer gives no extra constraints, I would state:
Product Personal and shared files/folders
Clients Web, mobile, and desktop sync
Files Immutable versions in object storage
Uploads Direct, resumable multipart
Sync Incremental change feed per user/drive
Conflicts Optimistic version check; preserve both versions
Sharing Owner, editor, viewer; expiring links optional
Retention Trash 30 days; limited version history
Not in scope Google Docs OT/CRDT, OCR, media editing
Deployment One region first, then multi-region
Google Docs is a different core problem. It needs operation-level collaboration through OT or CRDT. Drive sync treats an ordinary file version as an opaque blob.
2. Functional requirements
The system must allow a user to:
- Create, rename, move, list, and delete files and folders.
- Start, resume, and complete a large upload.
- Download the latest version or an older retained version.
- Synchronize changes across several devices.
- Work offline and upload changes after reconnecting.
- Share a file or folder as owner, editor, or viewer.
- Revoke access.
- View recent changes and version history.
- Restore an item from trash.
- Search by filename and basic metadata.
The first version does not include:
- simultaneous character-by-character document editing;
- OCR or full-text indexing inside every binary format;
- antivirus implementation details;
- photo-specific face recognition;
- cross-organization compliance workflows; or
- a custom object-storage engine.
3. Non-functional requirements
| Requirement | Target |
|---|---|
| Metadata latency | p99 below 200 ms |
| Upload | Resumable after network or process failure |
| Download | File bytes served through object storage/CDN, not API pods |
| Durability | At least eleven-nines-style object durability as a product goal |
| Availability | Reads approximately 99.99%; metadata writes approximately 99.9%+ |
| Sync freshness | Online devices see metadata changes within seconds |
| Consistency | Read-your-writes for the writer; no silent lost update |
| Security | Authorization checked before issuing any byte-access URL |
| Scalability | Large folders, shared drives, hot links, and multi-device users |
“Eleven nines” is a target inherited from managed object-storage products, not a claim that our API automatically has the same availability.
Consistency is not one global switch:
New metadata/version strongly committed before acknowledgement
Writer reads own update immediate
Other online devices may lag by seconds through sync
Search index eventual
Quota counters may lag, but upload admission needs a safe bound
Revoked access enforced before issuing a new download URL
CDN/object bytes immutable, addressed by version/object key
Edge cases to keep in mind
- A 2 GB upload fails after part 197.
- The client retries
completeafter the server committed it. - Two devices edit the same base version while offline.
- Alice moves a folder while Bob uploads a child into it.
- A folder contains one million children.
- Alice shares a folder; descendants inherit access.
- Alice revokes a public link while a signed URL is still alive.
- Malware is uploaded and shared immediately.
- The metadata commit succeeds but event publication fails.
- Object storage or the sync stream becomes unavailable.
- A regional outage occurs during an upload.
4. Estimate the scale
Use assumptions to expose bottlenecks, not to claim Google’s private numbers.
Assume:
Registered users 1 billion
Daily active users 100 million
Active devices per user 3
Metadata actions/user/day 20
File uploads/day 20 million
Average uploaded file 10 MB
Average downloads/day 500 million
Average downloaded file 4 MB
Stored logical files 100 billion
Average metadata record 1 KB including indexes
Peak multiplier 5×
Metadata traffic
100M × 20 actions/day
= 2 billion metadata actions/day
Average ≈ 23,000 requests/s
Peak ≈ 115,000 requests/s
Sync polling can exceed explicit user actions. Long polling or push notification should say “changes are available,” then clients pull a cursor page. Do not send every device a full folder tree.
Upload traffic
20M uploads × 10 MB
= 200 TB/day of new logical bytes
Average ingress
≈ 2.3 GB/s
Large files and backup waves make the peak much higher. The application API cannot proxy these bytes.
Download traffic
500M downloads × 4 MB
= 2 PB/day
≈ 23 GB/s average egress
This number motivates object storage, regional transfer endpoints, and a CDN for cacheable or widely shared content.
Metadata and change-log storage
100B file/folder records × 1 KB
≈ 100 TB before replicas and indexes
Metadata is much smaller than file bytes, but it carries the consistency burden. Losing the folder tree while retaining anonymous objects is not a successful recovery.
The first bottlenecks are:
1. File-byte ingress and egress
2. Hot shared files and public links
3. Metadata write and change-log throughput
4. Large-folder listings
5. Virus scanning, preview, and indexing workers
5. The core mental model: metadata is not file content
Suppose Alice has:
/Projects/plan.pdf
The database record contains:
file_id f_123
parent_id folder_projects
name plan.pdf
current_version v_9
owner_id alice
The 2 GB PDF does not belong in that row. Version v_9 points to an object:
drive-objects/f_123/v_9
This split gives each store the job it is good at:
Metadata database transactions, names, folders, versions, permissions
Object storage durable, cheap, large immutable byte objects
Cache hot metadata and permission decisions
Change log ordered notification that metadata changed
Search index derived filename and document metadata lookup
An object key is not authorization. Knowing drive-objects/f_123/v_9 must not grant access.
6. APIs
The API controls metadata and grants temporary access to the data path.
Start an upload
POST /v1/files/uploads
Authorization: Bearer <token>
Idempotency-Key: <uuid>
{
"parent_id": "folder_projects",
"name": "plan.pdf",
"size_bytes": 2147483648,
"content_type": "application/pdf",
"base_version_id": "v_8",
"content_hash": "sha256:..."
}
HTTP 201 Created
{
"upload_id": "up_77",
"file_id": "f_123",
"part_size": 8388608,
"uploaded_parts": [],
"upload_urls": [
{"part": 1, "url": "https://storage.example/..."}
],
"expires_at": "2026-09-03T08:00:00Z"
}
For a new file, base_version_id is absent. For an edit, it is the version the client downloaded. That one field prevents silent overwrite.
Resume an upload
GET /v1/files/uploads/up_77
HTTP 200 OK
{
"upload_id": "up_77",
"status": "UPLOADING",
"uploaded_parts": [1, 2, 3, 5],
"missing_parts": [4, 6, 7]
}
The client uploads missing parts directly to object storage.
Complete an upload
POST /v1/files/uploads/up_77/complete
Idempotency-Key: <uuid>
{
"parts": [
{"part": 1, "etag": "a1"},
{"part": 2, "etag": "b2"}
]
}
HTTP 200 OK
{
"file_id": "f_123",
"version_id": "v_9",
"revision": 42,
"status": "SCANNING"
}
Download a file
GET /v1/files/f_123/download?version_id=v_9
HTTP 200 OK
{
"url": "https://cdn.example/signed/...",
"expires_at": "2026-09-03T07:10:00Z",
"size_bytes": 2147483648,
"content_hash": "sha256:..."
}
The API checks access, then returns a short-lived signed URL. It does not stream 2 GB through a Go or Java service.
List a folder
GET /v1/folders/folder_projects/children?cursor=<opaque>&limit=100
Use cursor pagination by (sort_key, file_id), not offset.
Fetch changes
GET /v1/changes?cursor=chg_9012&limit=500
HTTP 200 OK
{
"changes": [
{
"change_id": "chg_9013",
"resource_id": "f_123",
"kind": "VERSION_CREATED",
"revision": 42
}
],
"next_cursor": "chg_9013",
"has_more": false
}
Other core endpoints:
POST /v1/folders
PATCH /v1/files/{file_id} rename, move, trash
DELETE /v1/files/{file_id} soft delete
POST /v1/files/{file_id}/restore
GET /v1/files/{file_id}/versions
POST /v1/files/{file_id}/permissions
DELETE /v1/files/{file_id}/permissions/{permission_id}
GET /v1/search?q=plan&cursor=...
Every retryable mutation accepts an idempotency key. Metadata updates also carry an expected revision or If-Match value.
7. Basic data model
FileNode
FileNode
--------
file_id
drive_id
parent_id
name
kind FILE | FOLDER
owner_id
current_version_id
revision
status ACTIVE | TRASHED | DELETED
created_at
updated_at
trashed_at
Use a stable file_id. Rename and move change metadata; they do not change object identity or force file bytes to move.
FileVersion
FileVersion
-----------
version_id
file_id
object_key
size_bytes
content_hash
content_type
created_by
base_version_id
scan_status
created_at
Versions are immutable. Updating a file creates a new version and conditionally points current_version_id at it.
UploadSession
UploadSession
-------------
upload_id
file_id
user_id
base_version_id
expected_size
expected_hash
storage_upload_id
status
expires_at
idempotency_key
Permission
Permission
----------
permission_id
resource_id
principal_type USER | GROUP | DOMAIN | LINK
principal_id
role OWNER | EDITOR | VIEWER
expires_at
created_at
revoked_at
Change
Change
------
change_id
drive_id
resource_id
resource_revision
kind
actor_id
created_at
The important distinction:
Source of truth FileNode, FileVersion, Permission, UploadSession
Blob truth Immutable objects in object storage
Derived data Search index, previews, thumbnails
Delivery log Change stream and outbox
Ephemeral Signed URLs, hot caches, online-device presence
8. Start with the simplest viable design
For an early product:
Client
├── metadata JSON ──► API ──► PostgreSQL
└── file bytes ─────► object storage
The upload path:
- Create a metadata row with status
UPLOADING. - Return a presigned object-storage URL.
- Client uploads bytes directly.
- Client calls Complete.
- Server verifies the object and marks the version ready.
The download path:
- Client asks the API for
file_id. - API checks permission.
- API returns a signed object URL.
This is enough for web upload and download. It is not enough for flaky networks, offline edits, device sync, shared folders, or concurrent writes. We will add those one constraint at a time.
9. Large uploads must be resumable
A single 2 GB PUT that fails at 95% wastes bandwidth and battery. Break it into parts:
2 GB file
├── part 1: 8 MB
├── part 2: 8 MB
├── ...
└── part 256
The client may upload several parts in parallel. Object storage records successful part ETags. On reconnect, the client asks which parts exist and sends only the missing ones.
Client Metadata API Object Storage
│ POST /uploads │ │
│─────────────────────────►│ │
│◄── upload id + URLs ─────│ │
│ │
│ PUT part 1 ───────────────────────────────────────►│
│ PUT part 2 ───────────────────────────────────────►│
│ network fails │
│ │
│ GET /uploads/{id} ──────►│ │
│◄── parts 1,2 present ────│ │
│ PUT part 3 ───────────────────────────────────────►│
│ POST /complete ─────────►│── finalize multipart ─►│
The metadata service owns the upload session. Object storage owns the temporary parts. A sweeper aborts expired multipart sessions and reclaims orphaned parts.
Part size balances:
- more parts improve resume granularity;
- too many parts increase request and metadata overhead;
- larger parts waste more work after a failed in-flight request.
Choose it dynamically from file size and the object-store part limit.
10. Complete must publish one immutable version
The byte upload and metadata publication are separate steps. A partially uploaded file must never become the current downloadable version.
Use a state machine:
CREATED → UPLOADING → ASSEMBLED → SCANNING → READY
└──────────────► FAILED
└──────────────► EXPIRED
Completion does:
- Authenticate the session owner.
- Check idempotency.
- Ask object storage to assemble the multipart object.
- Verify size and, when available, checksum.
- Insert an immutable
FileVersioninSCANNING. - Insert a scan-request outbox record in the same metadata transaction.
- Return the existing candidate
version_idif Complete is retried.
After scanning succeeds, a short publication transaction conditionally advances FileNode.current_version_id from base_version_id, marks the version READY, and inserts the user-visible Change plus outbox event. If that compare-and-swap fails, preserve the candidate as a conflict copy. Do not expose the candidate until required security scanning completes. The uploader may see SCANNING, while downloads and sharing remain blocked.
Object finalization cannot participate in the database transaction. Reconciliation handles the gaps:
Object assembled, DB failed retry Complete or sweep unreferenced object
DB says version exists,
object missing mark version unavailable, repair from replica
Prefer an idempotent object key derived from (file_id, version_id), so retrying finalization does not create new logical versions.
11. Download is authorization plus a temporary URL
The data path should be:
Client
│ GET /files/f_123/download
▼
Metadata API
│ authenticate + authorize + select version
│ mint short-lived signed URL
▼
Client ───────────────► CDN / Object Storage
The signed URL should bind:
- object/version key;
- expiry;
- allowed HTTP method;
- optional content disposition and filename;
- optional range restrictions.
HTTP Range requests let a client resume a download or stream a portion without restarting.
Popular public files benefit from CDN caching. Private content can still use a CDN with signed URLs or signed cookies, but cache keys must not leak one user’s token into another response.
Revocation cannot claw back bytes already downloaded. It also cannot instantly invalidate a one-hour URL. Keep private download URLs short-lived and support key/version revocation for urgent cases. This is a security-versus-cacheability trade-off.
12. Why folder rescans do not scale
A naive desktop client periodically runs:
GET /all-my-files
compare every row with local disk
For a user with one million items and three devices, almost every poll transfers unchanged metadata. Shared folders make it worse.
Instead, every committed metadata change appends a small record to an ordered change log:
chg_9011 f_100 RENAMED
chg_9012 f_123 VERSION_CREATED
chg_9013 f_220 TRASHED
Each device stores its last durable cursor. On reconnect:
give me changes after chg_9011
The server returns chg_9012, chg_9013, and a new cursor. The client applies both transactionally to its local metadata database, then saves the cursor.
The cursor is opaque. It may encode a log position, partition, and snapshot generation. Clients must not construct it.
13. Notification says “pull,” it does not carry truth
For online devices, use WebSocket, Server-Sent Events, mobile push, or long polling to send:
changes_available(drive_id)
Then the device calls /changes with its cursor.
Do not make the push channel the source of truth. Push messages can be duplicated, delayed, reordered, or dropped while a phone sleeps. The durable change log closes the gap.
Metadata transaction
├── update FileNode/FileVersion
└── insert Change + Outbox
Outbox relay
└── Kafka / notification workers
└── wake connected devices
Device
└── pull authoritative changes after cursor
Kafka is useful for downstream processors and notification delivery. The database change log is what lets a device recover after being offline for a month.
14. Desktop synchronization flow
The desktop client needs a local database, a filesystem watcher, and a work queue.
Local edit to cloud
Filesystem watcher sees plan.pdf change
↓
Debounce while the application is still writing
↓
Read stable file and compute fingerprint
↓
Create resumable upload with base_version_id=v_8
↓
Upload parts directly to object storage
↓
Complete and receive v_9
↓
Update local file record and cursor
Do not upload on every filesystem event. Editors often write a temporary file, rename it, and emit several changes. Debounce and wait for a stable size/mtime or a closable file handle.
Cloud change to local
Notification or periodic wake
↓
GET /changes?cursor=...
↓
Apply rename/move/delete to local metadata
↓
Download changed version to a temporary file
↓
Verify checksum
↓
Atomic rename over destination
↓
Advance cursor
Download to plan.pdf.tmp, verify it, then atomically rename. A crash must not leave half a PDF under the real name.
The client tracks server file_id, not only a pathname. Paths change; identity should not.
15. Concurrent edits need an explicit policy
Alice’s laptop and Bob’s desktop both downloaded v_8. They edit offline.
Alice completes first:
expected base v_8
current version v_8
→ publish v_9
Bob completes later:
expected base v_8
current version v_9
→ conflict
Last-writer-wins would make Bob silently erase Alice’s work. That is simple and dangerous.
Use optimistic concurrency:
UPDATE file_nodes
SET current_version_id = :new_version, revision = revision + 1
WHERE file_id = :file_id
AND current_version_id = :base_version
Exactly one concurrent update can advance from v_8.
For opaque binary files, preserve Bob’s uploaded bytes as a conflict copy:
plan.pdf
plan (Bob's conflicted copy 2026-09-03).pdf
Notify both users. They can choose or merge manually.
For text formats, the client may offer a three-way merge using:
base v_8 + Alice v_9 + Bob candidate
For Google Docs, use operation-level collaboration. Do not pretend whole-file version conflicts are a CRDT.
16. Metadata conflicts are separate from content conflicts
Content uses base_version_id. Rename, move, trash, and permission updates use FileNode.revision.
Example:
Alice renames plan.pdf → roadmap.pdf at revision 41
Bob moves plan.pdf to /Archive using revision 41
Both changes may be mergeable: one changes name, one changes parent_id. A coarse compare-and-swap rejects the second and asks the client to refetch. A more advanced mutation log can merge changes to independent fields.
Start coarse. Correct rejection is better than a clever accidental overwrite. Introduce field-level merges only when conflict telemetry shows enough user pain.
17. Chunking and deduplication are later optimizations
Multipart upload splits transport. That does not require permanent chunk-level storage.
The simplest durable layout is:
one immutable object per FileVersion
If users repeatedly edit large files, permanent chunks can reduce transfer and storage:
FileVersion
└── ordered manifest [chunk_hash_1, chunk_hash_2, ...]
Chunk Store
chunk_hash → immutable bytes
The client computes hashes and uploads only chunks the server does not already have. Reference counts or a tracing garbage collector reclaim chunks no retained version references.
Fixed-size versus content-defined chunks
Fixed-size chunks are simple, but inserting one byte near the start shifts every later boundary. Content-defined chunking chooses boundaries from the byte stream, so unchanged regions often keep the same hashes.
Deduplication trade-offs
Global dedup saves storage but leaks information: an attacker may learn that a known file already exists from a fast “upload skipped” response. It also complicates per-user encryption and deletion.
Safer choices:
- deduplicate only within one tenant or account;
- use server-side encryption with tightly controlled chunk lookup;
- make existence checks authorization-aware;
- avoid exposing timing or “chunk exists globally” directly to clients.
Do not lead the interview with global dedup. Direct multipart into immutable version objects is already a sound design.
18. Folders are metadata, not object-storage directories
Object storage is a flat key space. A folder is a FileNode with kind=FOLDER; a child points to parent_id.
Main queries:
file_id → one node
(drive_id, parent_id, name) → enforce name policy / find child
(drive_id, parent_id, sort) → list children
Possible relational indexes:
PRIMARY KEY (file_id)
INDEX (drive_id, parent_id, normalized_name, file_id)
INDEX (drive_id, parent_id, updated_at, file_id)
Never load all one million children to return page one. Cursor paginate by the selected order plus file_id as a tie-breaker.
Moving a folder should update one parent_id, not rewrite every descendant path. Compute breadcrumbs by walking parents, or maintain a derived ancestry/path index for fast deep navigation.
Prevent cycles:
move /A under /A/B
The mutation must verify that the destination is not the node itself or a descendant. A materialized ancestry table, closure table, or bounded parent walk can enforce this depending on depth and scale.
19. Sharing and inherited permissions
The authorization question is:
Can principal P perform action A on resource R right now?
A file may have direct permissions and inherited folder permissions.
/Team group:eng = EDITOR
/Plans
plan.pdf bob = VIEWER
Bob’s effective role comes from both the ancestor grant and the direct grant, according to product policy.
Two approaches:
Resolve ancestors on read
- Simple writes.
- Revocation is immediately visible.
- Deep trees make reads expensive.
Materialize effective permissions
- Fast checks.
- A folder share can fan out to millions of descendants.
- Revocation and moves require careful asynchronous recomputation.
A practical hybrid:
- authoritative ACLs live on resources;
- cache effective decisions by
(principal, resource, permission_revision); - shared drives use membership at the drive root;
- high-fan-out inheritance is evaluated through ancestor/drive membership rather than copied to every child.
Permission checks happen before metadata return and before signed URL creation. Search results are filtered by current access, not only by a stale search document.
20. Deletes, trash, and version retention
Delete should first be a metadata state change:
ACTIVE → TRASHED → DELETED
Trash removes the item from normal listings but keeps metadata and versions until retention expires. Restore returns it to an allowed parent; if the parent is gone, restore to a safe root folder.
After retention:
- Mark the node logically deleted.
- Emit deletion events for search, previews, and caches.
- Remove expired
FileVersionreferences. - Garbage-collect objects or chunks that no retained version references.
Physical deletion is asynchronous and idempotent. Object lifecycle policies are a backstop, not the only source of deletion truth.
Legal holds, enterprise retention, and shared ownership can override the normal trash clock. State this as a policy extension; it should not distort the core interview design.
21. High-level architecture
┌──────────────┐
│ Clients │
└──────┬───────┘
│ metadata / auth
┌──────▼───────┐
│ API Gateway │
└───┬────┬─────┘
│ │
┌─────────────┘ └───────────────┐
▼ ▼
┌─────────────┐ ┌────────────┐
│Metadata Svc │ │ Sync Svc │
└──┬─────┬────┘ └─────┬──────┘
│ │ │
▼ ▼ ▼
Metadata DB Redis Change Store
+ Outbox │
│ │
▼ ▼
Kafka ───────────────► Notification Gateway
│
┌───────┼────────────┬─────────────┐
▼ ▼ ▼ ▼
Scan Preview Search Audit/Quota
Workers Workers Indexer Workers
Client ═════════ presigned multipart / signed download ═══════►
Object Storage ──► CDN
Why each component exists
- Metadata Service: transactional file tree, versions, uploads, and permissions.
- Object Storage: durable immutable bytes and multipart support.
- Sync Service: cursor-based access to the change log.
- Notification Gateway: wakes online devices; it is not durable truth.
- Kafka: decouples scanning, preview, search, audit, and notifications.
- Redis: hot metadata and permission cache, never canonical ACL truth.
- CDN: absorbs popular downloads and reduces object-store egress.
- Workers: perform expensive or optional derived processing asynchronously.
22. Upload and download flows
Upload
Authenticate, rate-limit, reserve quota
↓
Create UploadSession and candidate version id
↓
Return presigned multipart URLs
↓
Client uploads bytes directly
↓
Complete: verify parts, size, checksum
↓
Create immutable FileVersion
↓
Scan
↓
Conditionally publish as current version
↓
Commit Change + Outbox
↓
Wake devices, index metadata, build preview
Whether publication waits for scanning is a product policy. For untrusted shared uploads, it should.
Download
Authenticate
↓
Load FileNode and requested version
↓
Resolve current permission
↓
Check scan status and policy
↓
Return short-lived signed CDN/object URL
↓
Client downloads bytes and verifies checksum
Metadata and byte availability should be monitored as one journey.
23. Storage choices
Metadata database
A relational database is a strong starting point because we need:
- atomic
FileNodeandFileVersionupdates; - uniqueness and foreign-key-like invariants;
- optimistic concurrency;
- permissions;
- upload idempotency;
- an outbox in the same transaction.
At larger scale, shard by drive_id or owner/tenant id so a user’s tree and its mutations are colocated.
Shard key: drive_id
This makes common folder and permission transactions local. A shared drive also has one stable drive_id.
Cross-drive moves become copy-plus-delete or a workflow, not a single-row move.
Object storage
Use a managed S3/GCS-style store behind an interface:
- multipart/resumable upload;
- immutable object versions;
- checksums;
- lifecycle rules;
- replication;
- server-side encryption;
- range reads.
Replicating object bytes in the metadata database is not a durability strategy.
Change store
For one-region scale, an append-only table partitioned by drive_id and ordered by sequence is enough:
Partition key: drive_id
Sort key: change_sequence
At very large scale, use a log-friendly distributed store. A cursor must identify a stable continuation even as old change records compact.
If a client cursor is older than retention, return:
410 CURSOR_EXPIRED
The client performs one paginated metadata snapshot, receives a fresh cursor, then returns to incremental sync.
24. Caching
Useful keys:
| Cache | Key | Purpose |
|---|---|---|
| File metadata | file:{file_id}:rev:{revision} | Hot metadata |
| Folder page | children:{parent_id}:{cursor}:{sort} | Repeated listings |
| Permission | acl:{principal}:{resource}:{perm_revision} | Avoid repeated ancestry checks |
| User/drive | drive:{drive_id} | Quota and drive policy |
| Public link | link:{token_hash} | Resolve hot shares |
Immutable version metadata is easy to cache. Mutable FileNode entries need revisioned keys or explicit invalidation.
Hot shared files
A public report can make one metadata row and one object key hot.
- CDN serves bytes.
- Replicate hot metadata cache entries.
- Coalesce concurrent cache misses.
- Jitter TTLs.
- Rate-limit abusive link traffic.
- Use a negative cache for revoked or nonexistent links, with a short TTL.
Redis is acceleration. Permission records and file metadata remain durable elsewhere.
25. Partitioning and horizontal scaling
| Dataset | Partition key | Reason |
|---|---|---|
| File metadata | drive_id | Colocate a tree and common transactions |
| Folder children | (drive_id, parent_id) | Ordered child listing |
| Versions | file_id | Fetch version history |
| Upload sessions | hash(upload_id) | Even write distribution |
| Permissions | resource_id and principal index | Resource and “shared with me” reads |
| Changes | (drive_id, sequence bucket) | Incremental ordered sync |
| Kafka events | drive_id or file_id | Preserve relevant order |
| Objects | object-store-managed hash | Spread byte traffic |
A single enterprise shared drive can become a hot metadata shard. Split very large drives by top-level subtree while routing operations through a drive coordinator, or give that tenant dedicated capacity. Do this for measured outliers, not every user on day one.
Large folders need partitioned child indexes, but pagination order must remain stable. Hash partitioning alone makes alphabetical listing a scatter-gather. Range partition by normalized name or maintain a separate ordered listing index.
26. Idempotency, ordering, and correctness
Retries are normal:
| Retry/race | Correct behavior |
|---|---|
| Start upload repeated | Return same upload_id for idempotency key |
| Same part uploaded twice | Object store replaces/deduplicates that part number |
| Complete repeated | Return same version_id |
| Two completes from same base | One current-version CAS wins; preserve conflict |
| Duplicate change event | Consumer deduplicates by change_id |
| Delete then delayed preview event | Resource revision/tombstone prevents resurrection |
| Rename repeated | Expected revision makes operation idempotent or rejected |
Do not require one global order. Preserve order per drive/resource and attach:
change_id
resource_id
resource_revision
event_type
Consumers keep the highest applied revision per resource. A late revision 41 cannot overwrite revision 42.
Use a transactional outbox so:
metadata committed
event publish failed
becomes a delay, not a permanently unsynchronized file.
27. Backpressure and asynchronous work
Scanning, previews, OCR, search indexing, audit, and notifications do not belong in the upload request.
Separate queues by workload:
Security scan high priority; gates sharing/download
Sync notification latency-sensitive, cheap
Preview medium priority
Search indexing eventual
OCR expensive, low priority
Analytics lowest priority
A burst of 4K video backups must not delay malware scanning for a shared executable.
Workers should:
- claim jobs idempotently;
- use bounded retries with exponential backoff and jitter;
- send poison work to a dead-letter queue;
- scale on oldest-job age and queue lag;
- limit per-tenant concurrency;
- expose a retry or repair operation.
If preview generation is behind, the file remains downloadable. If required malware scanning is behind, show SCANNING and do not fail open.
28. Failure scenarios
Metadata database unavailable
New metadata writes stop. Do not accept raw uploads without a durable session and quota reservation. Existing signed URLs may continue briefly; cached folder reads can be served stale only if authorization remains safe.
Object storage unavailable
Metadata browsing can continue. New uploads pause or use another healthy regional endpoint. Download returns a specific temporary-unavailable response; do not mark the file deleted.
Upload interrupted
Successful parts remain until session expiry. The client resumes missing parts. A lifecycle sweeper removes expired multipart uploads.
Complete times out
The client retries with the same idempotency key. The server returns the previously created version or resumes reconciliation.
Kafka unavailable
The metadata transaction still writes the outbox. Relay retries later. Sync consumers can read the durable change store; previews and search lag.
Notification service unavailable
Devices do not receive an immediate wake-up. Periodic cursor polling catches up. No metadata is lost.
Redis unavailable
Read from the metadata database with admission control and request coalescing. Do not turn one cache failure into a database stampede.
Scanner fails
Keep the original object quarantined, mark scan status failed or retryable, and alert. Never silently publish because the scanner timed out.
One object replica is corrupt
Checksums detect it. Read a healthy replica, repair the bad copy, and record a durability incident. Periodic scrubbing catches latent corruption before a user download does.
Client misses months of changes
If the cursor is retained, paginate the log. If expired, return a snapshot protocol and a fresh cursor. Never silently pretend the cursor is current.
29. Security and abuse prevention
Authentication and authorization
Validate sessions/OAuth at the gateway. Internal services use workload identity or mTLS. Check effective permission:
- before returning metadata;
- before creating upload URLs;
- before creating download URLs;
- before listing children or search results;
- before changing permissions.
Fail closed when authorization data is unavailable.
Signed URLs
- Short expiry for private files.
- HTTPS only.
- Restrict method and object key.
- Do not log full signed URLs.
- Rotate signing keys.
- Consider single-use or session-bound URLs for unusually sensitive content.
Encryption
Encrypt in transit and at rest. Object storage uses per-object data keys protected by a KMS. Enterprise tenants may use tenant-specific keys. Key deletion is not a substitute for metadata and retention correctness, but it can support cryptographic erasure.
Malware and content abuse
Quarantine new bytes until scan policy passes. Rate-limit uploads, shares, downloads, permission churn, and anonymous links by user, tenant, IP, and resource.
Detect:
- zip bombs and decompression limits;
- known-malware hashes;
- phishing through public links;
- storage quota abuse;
- link scraping and hotlinking;
- excessive small-file creation.
Deduplication must not become a file-existence oracle.
Audit
Record actor, resource, action, result, source device, and policy version for downloads, shares, revocations, and destructive actions. Keep audit storage append-only and separate from mutable product metadata.
30. Observability
Monitor user journeys, not only component CPU.
Important metrics
- metadata request rate, errors, p50/p95/p99 latency;
- upload starts, completion rate, resume rate, and abandoned bytes;
- part-upload throughput and object-store errors;
- complete-upload idempotency hits and reconciliation backlog;
- checksum or corruption failures;
- change-log append latency and cursor lag;
- notification delivery and reconnect rates;
- conflict-copy creation rate;
- permission-denied and stale-permission cache rate;
- scanner queue oldest age;
- preview/search lag;
- CDN hit rate and origin egress;
- quota reservation drift;
- outbox backlog and oldest unpublished event;
- regional replication lag.
Two end-to-end metrics matter most:
Upload publish latency
last byte uploaded → version safely visible to authorized clients
Sync visibility lag
metadata commit → another online device can fetch the change
Queue lag alone cannot prove either journey works.
Logs and tracing
Use structured logs with request id, upload id, file id, version id, drive id, change id, region, dependency latency, and retry reason. Never log file bytes, access tokens, raw public-link secrets, or full signed URLs.
Propagate a trace id through the outbox event into scan, notification, preview, and indexing workers.
Alerts
Alert on:
- metadata write failures;
- publish or sync visibility SLO breach;
- growing outbox/change lag;
- scanner backlog above policy;
- checksum mismatch;
- object replication under target;
- permission-service failure;
- hot folder/drive partition;
- CDN origin surge;
- garbage-collection or orphan growth.
31. Multi-region design
Assign each drive a home region for metadata writes.
Global traffic manager
┌───────┴───────┐
│ │
Region A Region B
drive D writer replicated D
│ │
└── async log ──┘
Object storage replicates immutable versions
The upload endpoint should be near the client, but metadata publication routes to the drive’s home region. A practical flow:
- Create upload session in the drive’s home region.
- Return a nearby regional object-storage endpoint.
- Upload bytes locally.
- Replicate or durably copy the assembled object according to policy.
- Publish metadata only after the required durability threshold.
- Replicate metadata changes and wake devices in other regions.
Alice may read metadata from a nearby replica, but read-your-writes routes her to the writer or carries a minimum revision token.
Regional failover
Before another region accepts writes for drive D:
- confirm replicated metadata is caught up enough;
- acquire a new drive ownership epoch/lease;
- fence the old writer;
- mint new upload endpoints;
- reject stale completions from an older epoch or reconcile them explicitly.
Without fencing, both regions can publish different “next” versions from the same base.
File bytes are immutable, so active-active replication is easier than active-active metadata mutation. Keep that distinction explicit.
Data residency may require a tenant’s metadata and object replicas to stay in approved regions. CDN policy must respect the same boundary.
32. How the design evolves
Early product
PostgreSQL + replicas
managed object storage
presigned multipart upload
one change table
polling sync
background scan/preview workers
Large product
Metadata sharded by drive_id
partitioned change store
notification gateway
Kafka + isolated worker queues
CDN and regional transfer endpoints
permission and metadata caches
search index
Global product
home region per drive
fenced regional failover
geo-replicated immutable objects
replicated metadata/change logs
tenant residency controls
dedicated capacity for huge shared drives
Chunk-level dedup, content-defined chunking, OCR, and smart search are optimizations. The core correctness remains: immutable versions, transactional metadata, resumable bytes, explicit conflict policy, and cursor-based sync.
33. Final architecture
UPLOAD
──────
Client
→ Metadata API: create resumable session
→ Object Storage: upload missing parts directly
→ Metadata API: idempotent complete
→ Scan
→ FileVersion + current-version CAS + Change + Outbox
→ Kafka
→ notifications / preview / search / audit
DOWNLOAD
────────
Client
→ Metadata API
→ current permission + version + scan status
→ short-lived signed URL
→ CDN / Object Storage
SYNC
────
Metadata commit
→ durable per-drive Change
→ best-effort device wake
→ GET /changes after cursor
→ local temp download + checksum + atomic rename
The reasoning chain is:
Files are large
↓
Keep bytes out of API and metadata DB
↓
Networks fail
↓
Use resumable multipart upload
↓
Devices go offline
↓
Keep immutable versions and a durable change cursor
↓
Two devices edit the same base
↓
Use optimistic concurrency and preserve conflicts
↓
Sharing changes access
↓
Authorize before every signed byte URL
34. Interview-ready summary
Key decisions to remember
- Separate the metadata, byte, and synchronization paths.
- Store immutable file versions in object storage, never in the metadata row.
- Upload directly with resumable multipart sessions.
- Make upload completion idempotent.
- Publish a version with a compare-and-swap on its base version.
- Preserve a conflict copy instead of silently using last-writer-wins.
- Synchronize from a durable cursor-based change log, not full rescans.
- Treat push as a wake-up; clients pull authoritative changes.
- Model folders as metadata and paginate large child lists.
- Check current permission before issuing a short-lived signed URL.
- Commit metadata and outbox/change intent together.
- Keep scans, previews, and search asynchronous with separate priorities.
Likely interviewer follow-up questions
- How does a 2 GB upload resume after part 197?
- What if Complete times out after creating the version?
- How do two offline devices avoid overwriting each other?
- Why not use last-writer-wins?
- How does a device catch up after a month offline?
- What happens when its cursor expires?
- How do folder permissions inherit without copying ACLs to millions of files?
- How do you list a folder with one million children?
- How do you revoke an already issued download URL?
- How do you garbage-collect old versions and chunks safely?
- What leaks can global deduplication create?
- What happens when Kafka, Redis, object storage, or the scanner is down?
- How does regional failover avoid two current versions?
- How would the design change for Google Docs?
Senior-level points that differentiate the answer
- Explain that multipart transport and permanent chunk dedup are separate choices.
- Distinguish immutable object durability from metadata consistency.
- Include
base_version_idin upload and use conditional publication. - Preserve conflict data; do not call silent overwrite a resolution policy.
- Make the change log durable and push notification disposable.
- Keep stable file identity across rename and move.
- Discuss permission inheritance and stale search/cache authorization.
- Reconcile object-store/database gaps rather than claiming a cross-store transaction.
- Fence the old metadata writer during regional failover.
- Measure last-byte-to-published and commit-to-device visibility.
A 1–2 minute verbal answer
I would scope Google Drive to files, folders, sharing, version history, and offline device sync, excluding live Google Docs collaboration. The central split is metadata versus bytes versus sync. PostgreSQL or a sharded transactional store keeps file nodes, immutable version pointers, upload sessions, and ACLs. Large bytes go directly from clients to object storage through resumable multipart URLs; the API never proxies them.
Completing an upload is idempotent. It verifies the assembled object and creates an immutable candidate FileVersion for scanning. After the scan, publication conditionally advances the file from the client’s base version and writes a per-drive Change plus outbox record. If another device already advanced it, I preserve the candidate as a conflict copy rather than silently losing work.
Each device keeps a cursor and pulls only changes after that cursor. WebSocket or mobile push merely wakes it; the durable change log is the recovery path after disconnects. Downloads first check current permissions and scan status, then return a short-lived signed CDN or object-storage URL.
I would shard metadata and changes by drive id, paginate large folders, cache revisioned metadata and permission decisions, and process scanning, previews, and search asynchronously through isolated queues. If Kafka is down, the outbox waits; if notifications are down, polling catches up; if a cursor expires, the client takes a paginated snapshot. For multi-region, each drive has one fenced metadata writer while immutable objects replicate geographically.
For the broader interview framework around this problem, see the System Design Interview Complete Guide.
