Design File Storage
By the Syncfusion DevTutorials team · Last updated
Store QuickBite's menu photos and receipts: object storage for bytes, a metadata database, presigned uploads, CDN downloads and background thumbnails.
Every QuickBite restaurant has menu photos. Every order creates a receipt PDF. Riders upload a photo after delivering an order. Together, this creates millions of files. During dinner time, customers download about 20,000 photos every second.
Files are different from normal database records. A photo may be 5 MB, while a database row may only be a few hundred bytes. People do not search inside image files. They only upload, download or delete them.
The main idea: store file bytes in object storage, store file information in a metadata database, upload directly to storage and serve downloads through a CDN.
What You Will Learn
In this case study, you will:
- Write the requirements for file storage.
- Estimate storage and bandwidth.
- Separate file content from file metadata.
- Upload files using presigned URLs.
- Serve files through a CDN.
- Generate thumbnails in the background.
- Build and test a scalable file storage system.
This builds on Lesson 3 (Latency & Distance), Lesson 5 (Caching), Lesson 6 (Databases) and Lesson 9 (Queues & Workers).
Step 1: Requirements
What It Must Do (Functional)
- Upload photos and PDFs up to 10 MB.
- Download files using a link.
- Support full-size and thumbnail images.
- Delete files when no longer needed.
- Keep receipts private.
- Store information about each file.
How Well It Must Do It (Non-Functional)
- Downloads should start within 100 ms.
- Files should survive machine or data center failures.
- Storage should remain affordable.
- Support many downloads at the same time.
- Uploads should stay fast during peak traffic.
Step 2: Estimate
QuickBite's storage numbers:
| What | Per day | Size each | Total per day |
|---|---|---|---|
| Photos (menus, delivery proof, profiles) | 150,000 | 5 MB + 0.5 MB thumbnails | 825 GB |
| Receipt PDFs | 1,000,000 | 60 KB | 60 GB |
| All new files | 885 GB a day, about 26.6 TB a month |
What to notice:
- Storage grows by tens of terabytes every month.
- A normal database is not a good place for this.
- One server is not enough.
- We need distributed file storage.
Downloads at dinner time: 20,000 photo views a second × 300 KB each (phones get a resized photo, not the 5 MB original) × 8 bits = 48 Gbit/s of outgoing bandwidth.
What to notice:
- 48 Gbit/s is very large.
- Application servers cannot handle all downloads.
- We need a CDN.
Step 3: Bytes and Details Live Apart
Split every file into two parts:
- File bytes go to object storage.
- File details go to the metadata database.
Object storage stores:
- Menu photos
- Receipt PDFs
- Delivery photos
- Profile pictures
The metadata database stores:
- File ID
- Storage key
- Owner
- File type
- Upload time
- Thumbnail information
One menu photo: a 140-byte metadata row in the database, and 5.2 MB of bytes in object storage, about 37,449 times bigger.
Do not store large files inside a database. Backups become larger, replication becomes slower and costs increase.
Step 4: Upload Straight to Storage
A simple design looks like this:
- Phone uploads file to app server.
- App server receives file.
- App server uploads file to storage.
This works at first but does not scale well.
A better solution is a presigned URL.
- Phone requests an upload URL.
- App server checks permissions.
- App server generates a presigned URL.
- Phone uploads directly to storage.
- Metadata is saved after upload.
The URL works for one file, expires after a short time (here 5 minutes), and lets the phone upload straight to storage. Step through one upload:
Here is the same idea in code:
A presigned link only works for a short time
const issuedAt = 1_000; // seconds
const validFor = 300; // 5 minutes
function linkStillValid(now) {
return now <= issuedAt + validFor;
}
for (const now of [1_060, 1_299, 1_301]) {
console.log(`at ${now} s: ${linkStillValid(now) ? "upload allowed" : "link expired"}`);
}
Expected output:
at 1060 s: upload allowed
at 1299 s: upload allowed
at 1301 s: link expired
What to notice:
- App servers do not handle large file uploads.
- Uploads become faster.
- Storage handles the file transfer.
After upload:
- Save metadata.
- Add a thumbnail job to a queue.
- Workers generate thumbnails.
Step 5: Download From a CDN
A CDN stores copies of files close to users.
A customer in Bengaluru downloads from a nearby server instead of a distant storage region.
Menu photos are ideal for a CDN because:
- Many users request the same images.
- Images rarely change.
- Cache hit rates are high.
The CDN's hit ratio decides how much traffic still reaches object storage. Step through three hit ratios:
Private files can still use a CDN through signed URLs.
If a photo changes, upload it with a new filename instead of replacing the old file.
Step 6: Thumbnails in the Background
Customers do not need the full-size image all the time.
Instead, create:
- Small thumbnail
- Medium thumbnail
- Original image
When an upload finishes, a job goes on a queue and workers make the thumbnails:
Generate thumbnails in background workers. Do not generate them during the upload request.
Step 7: Build It on the Canvas and Test It
The starting design contains:
- Load balancer
- 4 photo API servers
- Object storage
- Metadata database
There is no CDN.
| Check | Passes when |
|---|---|
| Peak load | p99 ≤ 100 ms and thumbnails ready within 2 seconds |
| Machine failure | Errors under 1% and p99 under 250 ms |
| Cost | Under $1,500/month |
| Requirements | Queue-based thumbnail generation |
Serve QuickBite's menu photos at dinner time
Run the starting design first.
Then:
- Add a CDN.
- Switch uploads to presigned URLs.
- Add a queue.
- Add thumbnail workers.
What you will probably find:
- Without a CDN, downloads overload the system.
- Uploading through app servers creates bottlenecks.
- Presigned URLs fix upload bottlenecks.
- One thumbnail worker is a single point of failure.
A design that passes:
- 2 load balancers
- 2 photo API servers
- Object storage
- CDN
- 2 database copies
- Queue
- 2 thumbnail workers
Common Mistakes
| Mistake | Wrong | Correct |
|---|---|---|
| Files in database | Store images as BLOBs | Store bytes in object storage |
| Proxy uploads | Upload through app servers | Upload directly to storage |
| No CDN | Serve all downloads from storage | Use a CDN |
| Overwriting files | Reuse the same filename | Use new filenames |
| Inline thumbnails | Resize during upload | Use queues and workers |
Practice
What to Remember
- Store file bytes in object storage.
- Store metadata in a database.
- Use presigned URLs for uploads.
- Use a CDN for downloads.
- Generate thumbnails with queues and workers.
- Give updated files new names.
- Bandwidth is often the biggest scaling challenge.
Further Reading
These are the sources this lesson was checked against:
- Amazon S3: Uploading objects with presigned URLs: how short-lived upload links work.
- Cloudflare: What is a CDN?: edge servers, cache hits and why distance matters.
- Google Cloud Storage: Storage classes: how object storage trades price for how often files are read.
Next: Search autocomplete, where QuickBite suggests dishes and restaurants as you type.