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:

  1. Create, rename, move, list, and delete files and folders.
  2. Start, resume, and complete a large upload.
  3. Download the latest version or an older retained version.
  4. Synchronize changes across several devices.
  5. Work offline and upload changes after reconnecting.
  6. Share a file or folder as owner, editor, or viewer.
  7. Revoke access.
  8. View recent changes and version history.
  9. Restore an item from trash.
  10. 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

RequirementTarget
Metadata latencyp99 below 200 ms
UploadResumable after network or process failure
DownloadFile bytes served through object storage/CDN, not API pods
DurabilityAt least eleven-nines-style object durability as a product goal
AvailabilityReads approximately 99.99%; metadata writes approximately 99.9%+
Sync freshnessOnline devices see metadata changes within seconds
ConsistencyRead-your-writes for the writer; no silent lost update
SecurityAuthorization checked before issuing any byte-access URL
ScalabilityLarge 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 complete after 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:

  1. Create a metadata row with status UPLOADING.
  2. Return a presigned object-storage URL.
  3. Client uploads bytes directly.
  4. Client calls Complete.
  5. Server verifies the object and marks the version ready.

The download path:

  1. Client asks the API for file_id.
  2. API checks permission.
  3. 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:

  1. Authenticate the session owner.
  2. Check idempotency.
  3. Ask object storage to assemble the multipart object.
  4. Verify size and, when available, checksum.
  5. Insert an immutable FileVersion in SCANNING.
  6. Insert a scan-request outbox record in the same metadata transaction.
  7. Return the existing candidate version_id if 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:

  1. Mark the node logically deleted.
  2. Emit deletion events for search, previews, and caches.
  3. Remove expired FileVersion references.
  4. 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 FileNode and FileVersion updates;
  • 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:

CacheKeyPurpose
File metadatafile:{file_id}:rev:{revision}Hot metadata
Folder pagechildren:{parent_id}:{cursor}:{sort}Repeated listings
Permissionacl:{principal}:{resource}:{perm_revision}Avoid repeated ancestry checks
User/drivedrive:{drive_id}Quota and drive policy
Public linklink:{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

DatasetPartition keyReason
File metadatadrive_idColocate a tree and common transactions
Folder children(drive_id, parent_id)Ordered child listing
Versionsfile_idFetch version history
Upload sessionshash(upload_id)Even write distribution
Permissionsresource_id and principal indexResource and “shared with me” reads
Changes(drive_id, sequence bucket)Incremental ordered sync
Kafka eventsdrive_id or file_idPreserve relevant order
Objectsobject-store-managed hashSpread 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/raceCorrect behavior
Start upload repeatedReturn same upload_id for idempotency key
Same part uploaded twiceObject store replaces/deduplicates that part number
Complete repeatedReturn same version_id
Two completes from same baseOne current-version CAS wins; preserve conflict
Duplicate change eventConsumer deduplicates by change_id
Delete then delayed preview eventResource revision/tombstone prevents resurrection
Rename repeatedExpected 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:

  1. Create upload session in the drive’s home region.
  2. Return a nearby regional object-storage endpoint.
  3. Upload bytes locally.
  4. Replicate or durably copy the assembled object according to policy.
  5. Publish metadata only after the required durability threshold.
  6. 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

  1. Separate the metadata, byte, and synchronization paths.
  2. Store immutable file versions in object storage, never in the metadata row.
  3. Upload directly with resumable multipart sessions.
  4. Make upload completion idempotent.
  5. Publish a version with a compare-and-swap on its base version.
  6. Preserve a conflict copy instead of silently using last-writer-wins.
  7. Synchronize from a durable cursor-based change log, not full rescans.
  8. Treat push as a wake-up; clients pull authoritative changes.
  9. Model folders as metadata and paginate large child lists.
  10. Check current permission before issuing a short-lived signed URL.
  11. Commit metadata and outbox/change intent together.
  12. 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_id in 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.