See It Flow
System Design · Trade-offs · ~18 min read

File Storage & Sync: Trade-offs & Decisions

Every architectural decision in this system has alternatives we chose not to take. This section explains each decision, the options considered, and why the chosen approach makes sense for this design's stated requirements and scale — while being explicit about when a different choice would be equally valid.

Mangesh MandlikLast updated:
Trade-off 01

Proxy Upload vs Direct-to-Storage Upload

Should the application server receive file bytes and forward them, or should clients upload directly to object storage?

Proxy upload

Client → Application → Object Storage. The application server receives the entire file body and writes it to storage. Simple to implement, and the application can validate, transform, or inspect bytes in transit — this is a legitimate reason to choose proxying for specific requirements. Trade-off: it consumes application bandwidth and connection/resource capacity proportional to file size, couples data transfer to control-plane availability, and adds horizontal-scaling pressure as upload volume grows.

Direct upload via presigned URL

Client → Application (auth + presigned URL) → Client → Object Storage. The application only orchestrates: it authenticates, creates metadata, generates a presigned URL, and returns it. The client sends bytes directly to storage, which removes byte-proxying load from the application tier. Trade-off: presigned URL management adds complexity (expiration, scoping, security) — see Architecture Deep Dive → Presigned URL Security.

Trade-off 02

Database vs Object Storage for File Bytes

Where should the actual file bytes live?

Metadata vs File Data — two different tools for two different problems💻 UserMetadata Path📋 Metadata Serviceid · name · ver · ACL🐘 Metadata DBFile Data Path🗄️ Object Storagechunks · segments · versionsdurable · cheap/GB · replicated📋 Metadata: name, size, owner, version, hash, permissions — queryable, structured🗄️ File data: binary blobs — never in the relational databaseRule: large binary data and metadata are different workloads — give each the right tool.
Metadata and file bytes have different workload characteristics

Files in the database

Store file bytes as BLOBs alongside metadata. Can be a reasonable choice for small files in some systems — one transaction, strong consistency, no second system to coordinate. For this design's workload (large files, up to tens of GB, at petabyte-per-day ingest), relational databases are not well suited: multi-GB binary objects degrade query performance, bloat backups, and prevent independently scaling byte storage from metadata storage.

Files in object storage

Store bytes in a dedicated object store, purpose-built for large binaries with independent capacity scaling, a durability/cost model suited to bulk storage, direct/range access, and lifecycle policies. Trade-off: two systems to coordinate — metadata and bytes can become inconsistent if not handled carefully (see Architecture Deep Dive → Metadata/Object Consistency).

Trade-off 03

Chunk/Part Size Selection

What is the right chunk size for multipart uploads? There is no single cross-provider production range — it depends on several variables.

Small chunks

Fast retry on failure (little data to re-upload), but higher per-chunk coordination overhead — more presigned URLs, more HTTP round-trips, more part-state tracking. At very small sizes and very large files, coordination overhead can dominate.

Large chunks

Fewer chunks, less coordination overhead, but a failed chunk costs more to re-upload, and client memory/buffer pressure per chunk is higher.

Variables that actually determine the right size

Provider minimum/maximum part-size rules, maximum part-count ceiling, total file size, network conditions, client memory constraints, desired parallelism, and retry cost. Adaptive part sizing — adjusting chunk size based on observed connection quality — is a further optimization some clients implement.

Trade-off 04

Push vs Polling for Sync

How do devices discover that a file changed?

Polling

Each device periodically calls GET /sync/changes. Simple, stateless, works behind any firewall. Trade-off: latency is bounded by the poll interval, and a large device fleet polling frequently generates substantial request volume — most of it returning no new changes.

Push (WebSocket / long-poll)

The sync service maintains a persistent connection to online devices and pushes a change notification when something happens. This can reduce notification latency and wasted empty polling requests. Trade-off: requires infrastructure for persistent connections, reconnection logic, and fallback for devices that can't maintain them (restrictive firewalls, backgrounded mobile apps).

Hybrid approach (chosen)

Push for online devices as a low-latency hint; polling as a fallback and safety net. Both paths converge on the same durable, cursor-based change log as the source of truth.

Trade-off 05

Conflict Resolution Strategy

When two devices edit the same file offline, what should the system do?

Sync Conflict — concurrent edits to the same file from different devices💻 Laptopedits v3a📱 Phoneedits v3b📄 v2common base⚠ Conflict!v3a ≠ v3bboth from v2📄 v3a (laptop)conflict copy📄 v3b (phone)conflict copy✓ Both preservedNo single correct strategy. Last-write-wins loses data. Conflict copies are safe. Merge works for text.
Both devices produce a new version from the same parent

Last-write-wins

Simple; the most recent write becomes current with no user intervention needed. Trade-off: it silently discards the other write. Whether that's acceptable is a product policy decision, not a distributed-systems requirement — 'first/last arrival becomes main' is a choice the product makes, not something the protocol mandates.

Conflict copy (preserve both)

Both versions are saved; the non-current one is exposed with a distinguishing name for the user to review and merge. Reduces the risk of silently overwriting one branch. Trade-off: requires user action, and ignored conflict copies can accumulate.

Three-way merge

Using the common ancestor version to auto-merge non-overlapping changes — works for some text formats, not for binary files, and is more complex to implement correctly.

Trade-off 06

SQL vs NoSQL for Metadata

What database technology should store file metadata?

SQL (e.g., PostgreSQL)

Transaction support well suited to permission changes and version-precondition checks, native JOINs useful for sharing queries, and mature tooling for schema evolution. Trade-off: horizontal write scaling requires deliberate sharding, which adds operational complexity.

NoSQL (e.g., DynamoDB, Cassandra)

Designed for horizontal write scaling and high throughput. Trade-off: typically no native JOINs, so sharing/relationship queries become application-level fan-out or require secondary indexes; consistency options vary by system and must be chosen deliberately for authorization-sensitive paths.

Trade-off 07

Version Retention Policy

How long should old file versions be kept?

Keep everything

Maximum recoverability — any version can be restored at any time, with a complete audit trail. Trade-off: storage cost grows without bound for frequently-edited files.

Time-based / count-based / quota-aware retention

Expire versions older than a window, or beyond a count, or once a per-owner storage quota is reached. Controllable storage cost. Trade-off: versions outside the retained window are not recoverable, unless a legal/compliance hold exception applies.

Trade-off 08

Whole-File Upload vs Delta Sync

Once a file changes, should the client transfer the entire file again or only the changed bytes?

Whole-file upload

Simplest to implement and reason about. The right default for small files, or any format where binary deltas provide little benefit (compressed/encrypted/binary-dense formats).

Fixed-block delta

Cuts transfer size for large files with in-place edits, but boundary shifts from insertions can defeat the savings.

Rolling/content-defined chunking

Finds matching regions despite shifted offsets, providing the best transfer savings for large files with localized edits — at the cost of CPU and chunk-index metadata.

Trade-off 09

Provider Multipart vs Application-Level Chunk Store

Should uploads use the storage provider's native multipart mechanism, or should the application maintain its own content-addressed chunk store?

Native multipart (chosen for baseline)

Simpler — the provider handles part storage and final object assembly. No additional application-level chunk metadata to maintain.

Application/content chunk store

Can enable cross-version chunk reuse, deduplication, and finer-grained delta semantics, but greatly increases metadata, reference-counting, and garbage-collection complexity — effectively building a mini content-addressed storage system on top of object storage.

Trade-off 10

Path-Based Identity vs Stable File ID

Should a file's identity be its path/name, or a stable identifier independent of both?

Path-based identity

Intuitive for simple systems, but rename/move become identity changes — any reference by path breaks, and reconstructing 'is this the same file after a rename' from path history is fragile.

Stable file ID (chosen)

file_id is assigned once and never changes; filename and parent_folder_id are just metadata fields that can be updated cheaply. This is what allows sync clients to recognize a rename as a metadata change on the same file, not a delete-plus-create.

Trade-off 11

Strong Metadata Mutation vs Eventual Sync Propagation

Does every part of this system need the same consistency model, or can different boundaries have different guarantees?

Committing a single file operation (e.g., 'accept this new version if the base version still matches') benefits from strong/conditional correctness at the metadata layer — this is where compare-and-set and version preconditions live. Propagating that committed change out to other devices, however, can be asynchronous and eventually consistent without weakening the core correctness guarantee, because each device reconciles against its own durable cursor rather than needing to observe the change instantly.

Trade-off 12

Owner Sharding vs File-ID/Tenant Sharding

How should metadata be partitioned as the system scales?

Owner/user sharding

Good locality for 'my files' queries. Weakness: a single very large or very active user/tenant can become a disproportionately hot or oversized shard, and 'shared with me' queries cross ownership boundaries.

Tenant sharding

Natural fit for multi-tenant products; same large-tenant hotspot risk as user sharding.

Hash/range by file_id

Better load distribution across shards, at the cost of weaker locality for 'list my files' queries, which then need a secondary index.

Trade-off 13

Direct Signed Download vs Application Proxy

Should downloads go directly from client to object storage/CDN, or through the application server?

Direct signed download (chosen for the general case)

Scales well — the application only issues a short-lived scoped URL after an authorization check; it does not carry byte-transfer load.

Application proxy download

Gives the application fine-grained real-time control — it can enforce immediate revocation, audit every byte served, or transform content on the way out. Trade-off: reintroduces the bandwidth/scaling cost that direct upload was designed to avoid.

Trade-off 14

Deduplication vs Simplicity/Isolation

Is content deduplication worth its complexity for this design?

Deduplication trades storage savings against: hashing CPU cost on every upload, reference-counting complexity for shared objects, potential privacy leakage if a 'does this content already exist' check is designed carelessly, weaker tenant isolation (shared underlying objects across owners), and meaningfully more complex garbage collection (an object can only be deleted once no owner references it).

Trade-off 15

Single Region vs Cross-Region Replication

Should the system operate in a single region, or replicate metadata and/or objects across regions?

Single region

Lower cost and operational complexity; metadata consistency reasoning stays simple. Trade-off: a regional outage affects the whole system, with recovery bounded by backup/restore rather than live failover.

Cross-region replication

Improves regional resilience and can tighten RPO/RTO, at meaningfully higher cost and operational complexity — replicated change logs, cross-region metadata consistency handling, and care to prevent split-brain writes during a partition.

Decision summary

DecisionDefaultEscalate when
Upload pathDirect-to-storage via presigned URLsProxy when inline inspection is mandatory, clients cannot reach storage, or a strict network boundary applies
File bytesObject storage; relational DB for metadataDB blobs only for small files where one transaction matters more than scale
Chunk sizeDerived from provider limits, file size, network, memory and retry costThere is no universal 5–10 MB rule; adapt to observed connection quality
Sync discoveryPush as a hint plus polling fallback over one cursor-based logPolling alone when persistent connections are not possible
ConflictsConflict copyThree-way merge for supported text formats; last-write-wins only as an explicit product policy
Metadata storeSQL (transactions, JOINs for sharing)NoSQL when write scaling dominates, with consistency chosen deliberately for authorization paths
Version retentionBounded, configurable policy with soft-delete graceKeep everything, or legal holds, where recoverability outweighs cost
Transfer on editWhole-file for small or binary-dense filesFixed-block or content-defined delta for large files with localized edits
Part storageNative provider multipartApplication chunk store when delta or dedup savings justify the GC and ref-count complexity
File identityStable file IDPath identity only for very simple systems without rename-safe sync
Consistency boundariesStrong conditional commit; eventual propagation to devicesDo not treat sync propagation delay as a correctness bug
Metadata shardingChosen from tenant-size distribution and dominant query patternHash by file_id for even load, with a secondary index for listing
Download pathDirect signed downloadProxy for immediate revocation, byte-level audit or inline transformation
DeduplicationOptional, later optimizationGlobal dedup only after a privacy-leak review
RegionsSingle region with a bounded DR planCross-region replication for specific availability or compliance requirements
Defaults and escalation conditions drawn from the fifteen decisions above