DrawLintDrawLint.ai

Dropbox / File Storage — system design by AgileViper46

Hire

Reviewed by 6 specialized AI reviewers. Explore the diagram and the full per-section feedback below.

Loading diagram…

1. Upload path - Users will be calling the file service to upload a new file - The file service will create a metadata in the sql with the inital version 1 - based on the file size the chuking would be defined and the presigned uri will be created for each chunk. The complete information will be returned back to the client - The client app will perform the chunking and chunk by chunk it will upload the chunks to the blob store. - For each chunk uploaded the client will call the api to mark the completion of the chunk upload and once all the chunks are uploaded the status of the file will be marked too. - Status propogation : Created | uploading | completed | failed ​ 2. Download Path -For a file to be downloaded first the permissions will be checked. - then the chunks will be downloaded one by one after the inital call to the file service which will created the presigned url to download -Once all the chunks are downloaded it will be stiched at the client side to generate the file. ​ 3. Sync the file -When a new version of the file is to be uploaded first the chunking would happen. Then the chunks would be verfied accross the existing version. - The chunks which can be reused will be used as it is and the new chunks will be uploaded - The chunk verficiation can be done using the hash where the content of the chunk and the chunk at the server if the hash matches we can use the chunk as it is. - Once the new version upload is complete using the CDC - outbox pattern we will push the event in the redis pub/sub that a new version for this file is available. The sync service will send SSE event to all the client which want notification for this file. - The client app will also perform passive polling every 30 secs to see which files have changed and pull that files ​ 4. Sharing the files -The owner of the file or others with the edit permission can call the share endpoint and add/remove users from the share list. -Any operation on the file upload/download the file service will first validate the RBAC and then only proceed with the operation ​ 5. Rollback -Rollback is straightforward during the rollback to a specific version we will just copy the metadata of that version as the new version metadata. We will never overwrite the current verion. -version will stay as kind of audit history always increasing. ​ 6 NFR, Failure handling and improvements - To improve the latency the CDN layer can be added where the clients are syncing the chunks directly from the CDN instead of going to blob. This also reduces the request coming to the blob and costs. - We can have a scheduled cron job to clean up the file history and chunks not in use for the files history older than 30 days and well as failed uploaded file version with partial uploads. Capacity backed answers: We have 1B files each metadata will be around 100B for filemetadata, 1 file version will be of 100B and 1 File chunk will be of 40B. Considering each file have 10 avg versions and each file be of 100MB so 20 Chunks each file metadata will be of = 100B (main table) + 10*(100B) (version) + 20*(40B) (chunk table) ~ 2000B = 2KB 1B * 2KB = 2TB of data. We can store this in a single Postgres SQL but going forward we can shared this data on fileId. For improving the reliability and not cause the SPOF for the postgres we can add the read replica where the sync and read path can be served from the read replica and since eventual consistency is ok this is a great boost for latency, scalability and availability For relability when ever a chunk upload is complete the Blob can send out a notification which will be used by the worker to store the etag of that chunk. The client can call the verify chunk endpoint with the etag to make sure that the file is durably stored before the file service marks that chunk status as uploaded.

Hire SignalLean No Hire

The candidate demonstrates good instincts on the overall architecture and some important design patterns, but the design falls short of senior-level expectations on core scalability and interface rigor. The biggest concerns are the lack of a credible metadata scaling plan, fragile sync delivery design, and incomplete API/entity modeling for key product flows.

✅ Good

Clear prioritization of availability over consistency

The candidate explicitly states that availability matters more than consistency and that eventual consistency across devices/users is acceptable. That is the right kind of NFR framing for a file sync system, and it aligns with later choices like serving reads from replicas and tolerating propagation delay.

✅ Good

Latency target is concrete and tied to sync behavior

The design gives a specific sync propagation goal of under 2 seconds and backs it with push-based notifications plus passive polling. Even if the exact mechanism could be refined, having a concrete target is much better than a vague 'low latency' claim.

✅ Good

Durability is called out as a first-class requirement

The candidate does not stop at availability and latency; they explicitly identify durability for stored files and add a verification step around chunk persistence before marking upload completion. That shows awareness that file systems fail primarily when durability assumptions are wrong.

warning

Scalability target is too vague for the stated assumptions

You mention 'scalability for 10M DAU', but what happens when those 10M users are active during peak sync windows? The NFR section does not translate the assumptions into concrete throughput or concurrency targets for uploads, downloads, sync fanout, or metadata reads/writes. Without peak numbers, it is hard to judge whether the 2-second sync goal is realistic under load. You could improve this by stating expected peak QPS, concurrent uploads, and event fanout volume derived from the 10M DAU assumption.

warning

Consistency model is only partially justified

You say eventual consistency is acceptable between devices and users, but have you considered which operations cannot tolerate stale reads? For example, what happens if a user changes sharing permissions or rolls back a file version and another device still sees stale metadata from a replica? The design should separate 'eventual consistency is fine for sync propagation' from cases where stronger consistency may be needed for permission checks, version visibility, or rollback correctness.

warning

Availability and durability lack measurable targets

The NFRs identify availability and durability, but what happens when a storage zone fails or metadata DB is unavailable? Without concrete targets such as uptime objectives or durability expectations, these requirements do not strongly drive design decisions. You could improve this by defining explicit goals like service availability and expected data loss tolerance, then showing how the chosen mechanisms satisfy them.

info

Numbers connect to storage size, but not to latency SLOs

The capacity section estimates metadata size well enough at a high level, but the NFRs do not connect those numbers to the stated '<2 sec' sync objective. You could strengthen the design by explaining whether replica lag, outbox processing delay, Redis fanout, and client polling intervals still keep most updates within the target under the assumed scale.

✅ Good

Core nouns for the main file lifecycle are present

The design identifies the main domain entities needed for the stated features: User, File, FileMetadata, Share, and FileVersion. That covers ownership, storage-facing metadata, sharing, and version history for rollback.

✅ Good

Versioning is modeled as a first-class entity

Treating FileVersion separately from File is a solid choice because sync and rollback both operate on historical versions, not just the latest file state. The explanation also makes it clear that rollback creates a new version rather than mutating history, which keeps the relationship between current state and audit history clean.

warning

Chunk-level entity is implied but not modeled

Have you considered what happens when sync and resumable upload need to reason about which chunks already exist, which chunks belong to which version, and which partial uploads can be cleaned up? The explanation relies heavily on chunk hashes, chunk completion, and chunk reuse, but there is no explicit chunk/chunk-mapping entity in the core model. Without that relationship, the happy path for dedup, resume, and garbage collection is underspecified. A Chunk entity plus a FileVersion-to-Chunk mapping would make these flows much clearer.

warning

File vs FileMetadata relationship is not clearly defined

What happens when you need to answer basic questions like 'what is the current version of this file?', 'who owns it?', or 'which metadata is immutable per version versus mutable at the file level'? Right now File and FileMetadata are both listed, but their boundary is unclear. If FileMetadata is just attributes of File, splitting them adds ambiguity; if it represents per-version metadata, that should be tied explicitly to FileVersion. Clarifying whether this is 1:1 with File or 1:N across versions would avoid confusion in the core model.

warning

Sharing relationships are only partially expressed

Have you considered how permissions compose when multiple users can edit and reshare a file? The explanation says owner or editors can add/remove users, but the entity model only lists Share without defining the relationship shape. For this feature, you want the model to clearly express that a File can have many Share entries, each connecting a file to a target user with a permission level. Otherwise the access-control path is left implicit rather than modeled.

info

Make device sync state explicit if sync is per device

You could improve this by deciding whether sync is purely event-driven from FileVersion changes or whether the system also tracks per-device sync progress. The requirements mention keeping files synced across multiple devices, and the explanation talks about notifying clients and polling, but there is no Device or SyncCursor-style entity. If the intended model is stateless client pull, say that explicitly; if the server needs to know what each device has seen, model that relationship.

✅ Good

Some metadata sizing and storage estimation is present

The candidate at least attempts a back-of-the-envelope estimate for metadata volume using file count, versions, and chunks per file. That shows the right instinct to translate product entities into storage footprint rather than stopping at a purely conceptual design.

warning

User scale never gets translated into traffic

You state 10M DAU and 1B files, but what happens at peak upload/download/sync time? Without even rough QPS or peak concurrency estimates, it is hard to tell whether the file service, sync path, database, and blob-store interactions can handle the expected load. A stronger answer would convert DAU into daily uploads/downloads, then into peak API QPS, chunk request rates, and concurrent connections.

warning

Storage planning stops at metadata and ignores the dominant data volume

Have you considered the actual blob storage footprint? With 1B files at 10MB average, raw file data is on the order of exabytes, and version retention for 30 days can materially increase that depending on update frequency and chunk reuse. Without sizing the object store, replication overhead, and growth rate, the capacity plan is incomplete for this problem.

warning

The metadata estimate is not connected to the stated assumptions

What happens if the average file size is really 10MB as given, but the calculation later assumes 100MB and 20 chunks per file? That breaks the chain from assumptions to infrastructure. The exact numbers do not need to be perfect, but the methodology should stay internally consistent so the resulting database and chunk-table sizing is trustworthy.

warning

Single-Postgres conclusion is not justified by throughput or operational load

You conclude that ~2TB of metadata can fit in a single Postgres instance, but what happens when 10M DAU generate uploads, version writes, share updates, sync polling, and chunk-completion updates concurrently? Capacity is not just about bytes at rest; it is also about write amplification, index size, hot rows, and read/write throughput. You should justify the database choice with expected request rates, not only storage size.

info

Include bandwidth and fan-out estimates for sync

You could improve this by estimating how many clients maintain SSE connections, how many file-change events are emitted per second, and how much passive polling every 30 seconds adds to backend QPS. That would make it clearer whether Redis pub/sub and the sync service fit the stated scale.

info

Account for replication and retention overhead explicitly

You could strengthen the plan by adding simple multipliers for database replicas, object-store redundancy, indexes, and 30-day version retention. Even rough factors would show that you are thinking beyond raw logical data size to real deployed capacity.

✅ Good

Direct upload via presigned URLs fits large-file transfer

Using the API to create metadata and return presigned URLs keeps the control plane separate from the data plane, which is the right protocol shape for 50GB files. It avoids forcing the application service to proxy large uploads and makes chunked upload feasible.

✅ Good

Candidate thought through multipart upload lifecycle

The explanation goes beyond a single upload call and describes chunk completion tracking plus file states like created/uploading/completed/failed. That shows awareness that large uploads need an explicit lifecycle rather than a one-shot request.

warning

Core download, sync, and rollback APIs are not actually defined

How does a client download a file, fetch version history, roll back to a prior version, or ask 'what changed since my last sync'? The explanation mentions these flows, but the route list does not expose concrete endpoints for them. Without APIs for download URL generation, version listing, rollback, and change polling, the core functional requirements cannot be exercised cleanly by a client.

warning

Chunk upload protocol is underspecified

You mention chunked upload, per-chunk completion, and server-side verification, but the API surface only shows POST /files and PUT /presignedUrl. What happens when chunk 17 of 200 fails, the client retries, or the client reconnects after losing state? At this scale, clients need explicit APIs or message contracts for initiating multipart upload, listing pending chunks, marking chunk completion idempotently, finalizing the upload, and resuming interrupted uploads.

warning

Resource design is inconsistent and hard to reason about

The routes mix resource-oriented paths and action-like paths in a way that makes ownership unclear: POST /files, PUT /files/:id, PUT /presignedUrl, POST /files/:id/share, PUT /files/share. What is the resource behind PUT /files/share, and which file does it modify? A cleaner shape would make file versions, shares, and upload sessions explicit resources so clients can understand what they are creating or updating.

warning

HTTP verb semantics are muddy for create-vs-update flows

What happens when a client calls PUT /files/:id? Is that replacing metadata, uploading a new version, or resuming an incomplete upload? For file storage systems, creating a new version is usually a distinct operation from updating metadata. Overloading one PUT route for multiple meanings makes retries and client behavior ambiguous.

warning

No API for listing versions or shared files, so pagination concerns are unaddressed

A user may have many files, many shared items, and many versions within the 30-day retention window. If you add the necessary list endpoints, what happens when a folder or account has thousands of items? At this scale, clients need paginated list APIs, ideally cursor-based, for files, shares, versions, and sync changes.

warning

Error model and retry guidance are missing

What does the client see when permission checks fail, a presigned URL expires mid-upload, a chunk hash does not match, or finalization happens before all chunks are durable? Returning just OK/ERROR is too vague for a sync client. The API should define status codes and structured errors so clients know when to retry, refresh a URL, restart a chunk, or surface a permanent failure.

info

Sync notification protocol needs a clearer contract

You mention SSE plus passive polling, which is a reasonable combination, but the client-visible contract is missing. You could improve this by defining the SSE event types and a polling endpoint such as 'changes since cursor/timestamp' so reconnects, missed events, and duplicate delivery are handled deterministically.

info

Sharing API would benefit from explicit add/remove/read operations

Right now POST /files/:id/share with a shareList and PUT /files/share leave it unclear how a client reads current permissions, removes one user, or updates one principal safely. You could improve this by making shares a sub-resource with clear create/update/delete semantics so concurrent permission edits are easier to reason about.

✅ Good

Direct-to-object-store upload/download keeps the app tier off the data path

Routing large file transfers through presigned URLs to blob storage/CDN is the right high-level shape for this workload. It prevents the File service from becoming the bandwidth bottleneck and makes the design much more scalable for 10M DAU and large files.

✅ Good

Chunk-based upload design supports resumability and delta sync

The candidate thought through chunking, per-chunk status, and chunk reuse across versions. That is a sensible architecture for resumable uploads and multi-device sync because retries and partial failures can be isolated to individual chunks instead of restarting whole-file transfers.

✅ Good

Asynchronous change propagation is aligned with eventual consistency

Using an outbox/CDC-style flow to publish file version changes and then notify clients through a separate Sync service is a solid fit for the stated requirement that availability matters more than strict consistency. It decouples write completion from fanout to devices.

critical

Postgres is the first major bottleneck at the stated scale

What happens when metadata, versions, shares, and chunk rows grow into the multi-terabyte range and every upload/download/share/sync check hits a single Postgres writer? The system will bottleneck on one database for both throughput and storage management. The explanation says sharding can be done later, but at 1B files this is not a minor optimization. You should define the partitioning/sharding strategy up front, such as sharding by fileId or ownerId and separating hot metadata from chunk/version tables.

warning

Redis pub/sub is fragile for sync fanout

Have you considered what happens if the Sync service is disconnected or restarted while Redis pub/sub messages are being published? Native pub/sub is lossy and does not retain events, so clients can miss updates and rely entirely on polling. For a core sync feature, a durable stream or queue would be safer for server-side consumption, with Redis used only for transient fanout if needed.

warning

Single points of failure are only partially addressed

What happens when the File service instance, Sync service instance, Redis node, Kafka broker, or cleanup worker dies? The design mentions API gateway/load balancer and a Postgres read replica, but HA is not described for the rest of the critical path. Read replicas help reads, but they do not remove the single writer failure risk. You should call out multi-instance stateless services behind the LB and managed HA setups for Redis/Kafka/Postgres failover.

warning

Upload completion flow is split across client callbacks and blob events

Have you considered what happens if the client says a chunk upload completed but the blob event is delayed, duplicated, or never arrives? Right now chunk state seems to depend on both client-side marking and asynchronous storage notifications, which can diverge. A clearer source of truth is needed: either finalize from storage-side completion only, or make the client call a complete-upload API that verifies all expected parts exist before committing the version.

warning

Sync service connection model is underspecified for multi-device scale

What happens when millions of clients maintain long-lived SSE connections for near-real-time sync? The design names a Sync service but does not explain how connections are partitioned, how file-to-subscriber mappings are stored, or how fanout is distributed across instances. Without that, the Sync service itself may become the next bottleneck. You could improve this by describing horizontal scaling of connection servers and how subscriptions are routed or rebalanced.

info

CDN usage is helpful for downloads but unclear for uploads

You could improve this by being explicit about where the CDN sits in the request path. The diagram labels CDN with direct upload and direct download, but many CDN setups are primarily beneficial for download acceleration while uploads still go directly to object storage. Clarifying that trade-off would make the end-to-end flow easier to reason about.

info

Cleanup cron may not scale as a single scheduled worker

You could improve this by making retention cleanup an incremental distributed job instead of one cron process scanning Postgres and blob storage. With 1B files and 30-day version retention, a single cron-driven sweeper can become slow, expensive, and operationally risky.

Want this kind of feedback on your own design?

Draw your architecture for Dropbox / File Storage and get an instant hire/no-hire signal from 6 specialized AI reviewers — free to start.