5.9.5. Object Storage
Table of Contents
Why Object Storage Matters for Backends
When your backend needs to store user files, images, videos, logs, or backups, you usually have two broad choices:
- Store files on the local filesystem.
- Store files in an external object storage service.
Object storage is the standard choice for modern production backends. It scales easily, is relatively cheap, and can be shared by many servers.
You will build on this chapter in the later “S3-Compatible Storage” and “Presigned URLs” chapters, so the focus here is on core concepts and how they differ from local file storage.
Key idea: In object storage, you store objects (file data + metadata) inside buckets, and you access them using keys (unique paths), usually through an HTTP API.
What Is Object Storage?
Object storage is a way of storing data where each file is treated as an independent object:
- Each object has:
- The raw data (for example the bytes of an image).
- Metadata (for example content type, creation time, custom tags).
- A unique identifier (a key or object name).
Unlike a traditional filesystem:
- There are no real directories, only object keys that look like paths, such as:
avatars/user_123.pnglogs/2025/01/01/app.log- You cannot do operations like “rename directory” or “move directory” in one atomic step.
- You work with a remote service over a network, usually HTTP.
Common examples of object storage services:
| Provider | Example service |
|---|---|
| Amazon Web Services | S3 (Simple Storage Service) |
| Google Cloud | Cloud Storage |
| Microsoft Azure | Blob Storage |
| Self-hosted | MinIO, Ceph, etc. |
Buckets, Objects, and Keys
Object storage has a few core terms you will see everywhere.
Buckets
A bucket is a top-level container for your objects:
- Think of a bucket like a high-level namespace.
- Bucket names are globally unique within a service.
- Buckets often map to:
- A single application environment, such as
myapp-prod-files. - A specific purpose, such as
myapp-avatarsormyapp-logs.
You usually:
- Create a bucket once.
- Configure permissions and lifecycle rules on that bucket.
- Then read and write objects inside it from your backend.
Objects
An object is what you store inside a bucket:
- Each object contains:
- The binary data (file contents).
- The key (identifier).
- Metadata such as
Content-Type: image/png.
Examples of objects:
- A user profile picture.
- A PDF invoice.
- A JSON export.
- A video file.
From your backend’s point of view, you typically:
- Upload an object.
- Download an object.
- Delete an object.
- Sometimes list objects with a certain prefix.
Keys (or Object Names)
Every object is identified by a key (also called object name). It looks like a path, but it is just a string.
Examples of keys:
avatars/123e4567-uuid.pnginvoices/2025/INV-000123.pdfbackups/db-2025-01-01.sql.gz
There is no real directory structure. The part before / is not a real folder, it is just part of the key string.
This has important consequences:
- Listing
avatars/is actually done by asking for objects with prefixavatars/. - Renaming “a directory” means copying or rewriting all objects that share a prefix.
Rule: Treat keys as opaque strings you control. Do not let users control keys directly, or they can overwrite other users’ files.
Object Storage vs Local File Storage
To understand why object storage is used so often, compare it with storing files on the server’s disk.
Local File Storage
- Files are stored on the server filesystem, for example
/var/www/myapp/uploads. - Access is done with direct system calls, like
open,read,write. - Fast for a single server, easy to understand.
Problems in real backend systems:
- If you have multiple servers, they do not share the same disk by default.
- Scaling storage requires adding or managing disks.
- Backups and durability are your responsibility.
- If your server dies, you risk losing files.
Object Storage
- Files are stored in buckets in a remote service.
- Access is done via HTTP APIs and SDKs.
- The storage service handles durability and scalability.
Benefits:
| Aspect | Local filesystem | Object storage |
|---|---|---|
| Scaling | Hard, manual | Easy, service scales for you |
| Durability | Your responsibility | Replicated, resilient by default |
| Multi-server | Extra work (NFS, shared disk) | All servers can access same bucket |
| Access | Direct file I/O | HTTP API, SDKs |
| Cost model | Disk / volume cost | Per GB + bandwidth |
Trade-offs:
- Network latency is higher than local disk.
- You need proper authentication.
- Operations are not traditional filesystem calls.
For backend development, object storage is usually the default for user-uploaded content and large assets.
Typical Backend Use Cases
Here are common situations where your backend will use object storage.
User-Uploaded Files
Examples:
- Profile pictures.
- Documents.
- Attachments in a ticket system.
Typical flow:
- User uploads a file (e.g., via an HTTP form).
- Backend receives the file stream.
- Backend uploads the file to object storage (for example to a bucket).
- Backend stores the resulting key or URL in the database.
You never keep the whole file in your database. You keep only a reference.
Generated Files
Your backend might generate:
- Reports or PDFs.
- Exported CSV data.
- Zipped backups.
Instead of storing them on disk:
- Generate the file in memory or in a temp location.
- Upload it to object storage.
- Return a URL or key to the client.
Logs and Backups
Object storage is also used for:
- Log archives.
- Database backups.
- Application snapshots.
These are usually written by background jobs or scheduled tasks, not by user-facing API endpoints.
Naming and Folder-like Structures
Even though there are no real folders, you will often want a predictable “structure” in your keys.
Using Prefixes as “Folders”
You can simulate folders using prefixes:
avatars/user_123.pngavatars/user_456.pnginvoices/2025/INV-0001.pdfinvoices/2025/INV-0002.pdf
If you list objects with prefix avatars/, you get all profile images.
If you list objects with prefix invoices/2025/, you get all invoices for that year.
Designing Key Patterns
You should design keys so they are:
- Unique.
- Hard to guess if files should not be public.
- Simple to compute.
Common patterns:
| Use case | Key example |
|---|---|
| User avatar | avatars/{user_id}/{uuid}.jpg |
| Public blog images | blog/{post_id}/{slug}-{uuid}.webp |
| Private documents | users/{user_id}/docs/{uuid}.pdf |
| Backups | backups/{date}/{db_name}-{timestamp}.sql.gz |
You will combine keys with presigned URLs in a later chapter to control access.
Rule: Never trust user file names directly as keys. Generate your own key that includes a random or unique ID, then store the original filename in metadata or in your database if you need it.
Metadata and Content Types
Each object can have metadata. Two important types are:
- System metadata
- Content type, such as
image/pngorapplication/pdf. - Content length (size).
- Last modified time.
- User metadata
- Custom key-value pairs you define, such as
x-meta-user-id: 123.
Content Type
When your backend uploads a file, it should set the correct content type so clients receive the correct header when they download it.
Examples:
| File extension | Content type |
|---|---|
.jpg, .jpeg | image/jpeg |
.png | image/png |
.pdf | application/pdf |
.json | application/json |
.csv | text/csv |
If you do not set it, clients might download files with an incorrect or generic type, such as application/octet-stream.
Custom Metadata
You can attach lightweight custom metadata to objects, such as:
x-meta-owner-id: 123x-meta-purpose: avatarx-meta-version: 1
However, do not overuse object metadata for business data. Complex business information should live in your database, not in object metadata.
Uploading and Downloading in a Backend
Even though the details depend on the specific object storage service and SDK, most backends follow similar patterns.
Upload Flow Example (High Level)
- Receive a file in an API endpoint.
- Generate a new key for the object.
- Use the storage SDK or HTTP API to upload the file stream.
- On success, store the key in your database.
- Return some representation to the client, such as:
- An internal ID that maps to the key.
- A URL built from the bucket and key.
- Or a short-lived presigned URL (covered later).
Download Flow Example (High Level)
- Client hits an endpoint, for example
GET /users/{id}/avatar. - Backend looks up the avatar key for that user in the database.
- Backend fetches the object from storage, or redirects client to object storage using a secure URL.
- Backend returns the data or redirect to the client.
You will often choose between:
- Proxying the file through your backend.
- Letting clients access storage directly using secure signed URLs.
Direct access is usually more efficient for large files, since your backend does not have to relay every byte.
Durability, Availability, and Cost
Object storage services usually promise high durability and good availability.
Durability
Durability is the probability that your data will remain safe over time.
For example, some services advertise something like:
- “11 nines” durability, written as $99.999999999\%$.
This means the chance of losing data is extremely small, because the service:
- Stores multiple copies.
- Replicates across different machines and sometimes different locations.
Availability
Availability is the percentage of time the service is accessible.
For example:
- $99.9\%$ availability means about 8.76 hours of possible downtime per year.
- $99.99\%$ availability means about 52.6 minutes of possible downtime per year.
Useful formula: Downtime per year (hours) $\approx (1 - \text{availability}) \times 8760$.
For example, with $99.9\%$ availability:
$$
\text{Downtime} = (1 - 0.999) \times 8760 = 8.76 \text{ hours}
$$
You normally rely on the storage provider’s durability and focus your backend on handling:
- Temporary unavailability.
- Timeouts.
- Retries with backoff.
Cost Considerations
Object storage is usually billed by:
- Data stored (GB per month).
- Data transferred out (egress).
- API requests (PUT, GET, DELETE, LIST).
This affects backend design:
- Avoid making too many small requests when you can batch.
- Consider lifecycle rules to move old data to cheaper storage or delete it.
Permissions and Access Control
Object storage is separate from your application, so you must manage its permissions carefully.
Basic Model
- You typically have an account or set of credentials with the object storage service.
- You might create:
- A dedicated user or role for your application’s servers.
- Bucket policies to restrict access (for example allow only read for public assets).
You will use access keys or an identity system from the provider to authenticate.
Public vs Private Objects
- Public objects can be accessed by anyone with the URL.
- Example: public images for a blog.
- Private objects require valid authentication or a special signed URL.
- Example: invoices or private documents.
For sensitive backend data, you should:
- Keep buckets private by default.
- Never expose raw access keys to the client.
- Use your backend to generate short-lived URLs for private access (covered in “Presigned URLs”).
Common Patterns in Backend Design
Object storage changes how you think about files in your system.
Store Only References in the Database
You almost never store the binary file data in your relational database. Instead, you:
- Store a record like:
| Column | Example value |
|--------------|--------------------------------------------------|
| id | 42 |
| user_id | 123 |
| storage_key | users/123/docs/7f9a-uuid.pdf |
| mime_type | application/pdf |
| created_at | 2025-01-01 10:00:00 |
- Use
storage_keyto retrieve the file from object storage.
Idempotent Operations
Uploads or deletes can fail partially due to network issues.
You will often design operations that are idempotent, which means:
- If the same request is repeated, the end state is the same.
Examples:
- Deleting the same object key twice is safe. The second delete should be a no-op.
- Generating a key based on a unique identifier, so re-uploading the same file overwrites the same object in a controlled way.
You will see “Idempotency” in more detail in a later chapter.
Summary
Object storage is a central building block of modern backend systems:
- You store objects inside buckets, each identified by a key.
- There are no real folders, only key prefixes that act like folders.
- You interact with storage over HTTP using SDKs or REST APIs.
- Backends typically:
- Receive files from clients.
- Upload them to object storage.
- Store only keys and metadata in the database.
- Object storage gives you high durability, easy scaling, and shared access across multiple servers.
- You must carefully design:
- Key naming patterns.
- Content types and metadata.
- Permissions and access control.
Later chapters will build on this foundation to show how to connect to S3-compatible services, generate presigned URLs, and integrate object storage cleanly into your backend APIs.
Views: 7
KAHIBARO