DEV Community

Cover image for Lioran S3 Storage Layout: Why Object Keys Never Become Filesystem Paths
Swaraj Puppalwar
Swaraj Puppalwar

Posted on

Lioran S3 Storage Layout: Why Object Keys Never Become Filesystem Paths

Lioran S3 Storage Layout

Suppose an object key is:

users/42/avatar.png
Enter fullscreen mode Exit fullscreen mode

The easiest implementation is:

/data/users/42/avatar.png
Enter fullscreen mode Exit fullscreen mode

Lioran S3 deliberately does not do that.

The Rust engine separates the user-visible object namespace from physical placement.

I’m Swaraj Puppalwar, Founder & CTO of Lioran Group / Lioran Developer Solutions, and this article goes inside StorageLayout.

The directory model

At its core:

<data_root>/
├── staging/
└── objects/
Enter fullscreen mode Exit fullscreen mode

Normal object writes first enter staging/.

Committed payloads live under objects/.

Internal IDs

When a PUT begins, Bastion creates an internal UUID for the object.

Physical placement is derived from that ID, not from the user key.

Example:

object ID:
ab12cd34-....

physical path:
objects/ab/12/ab12cd34-....
Enter fullscreen mode Exit fullscreen mode

The implementation removes hyphens, lowercases the ID, and uses two two-character prefixes.

Why fan out directories?

Putting millions of files into one directory can create ugly filesystem behavior and operational pain.

The two-level prefix layout spreads objects across a large namespace.

Conceptually:

objects/
├── 00/
│   ├── 00/
│   ├── 01/
│   └── ...
├── 01/
├── 02/
└── ...
Enter fullscreen mode Exit fullscreen mode

The object UUID remains the final filename.

Logical key versus physical path

Metadata connects the two worlds:

bucket + logical key
        ↓
ObjectMetadata
        ↓
physical relative path
        ↓
filesystem payload
Enter fullscreen mode Exit fullscreen mode

For example:

logical:
media/images/avatar.png

physical:
objects/ab/12/ab12cd34-....
Enter fullscreen mode Exit fullscreen mode

The application never needs to know that physical path.

Security consequence

The source states a critical invariant:

Raw user object keys are NEVER used directly as filesystem paths.
Enter fullscreen mode Exit fullscreen mode

That eliminates an entire class of naive path-concatenation mistakes.

A key containing:

../../etc/something
Enter fullscreen mode Exit fullscreen mode

should remain a logical key, not become a filesystem traversal instruction.

Staging paths

Temporary normal PUTs use:

staging/<staging-uuid>.tmp
Enter fullscreen mode Exit fullscreen mode

Multipart sessions use:

staging/uploads/<upload-id>/
Enter fullscreen mode Exit fullscreen mode

and individual parts are generated as:

part_00001.tmp
part_00002.tmp
...
Enter fullscreen mode Exit fullscreen mode

Again, external object keys do not define those paths.

Reads

A GET starts from metadata:

let meta = self.metadata.get_object(bucket, key)?;
Enter fullscreen mode Exit fullscreen mode

Then:

let abs_path =
    self.layout.resolve_absolute_path(&meta.physical_path);
Enter fullscreen mode Exit fullscreen mode

The stored relative physical path is resolved underneath the configured data root.

Range reads

Range reads use the same physical mapping.

After opening the committed payload, the engine can:

file.seek(SeekFrom::Start(start)).await
Enter fullscreen mode Exit fullscreen mode

and then limit the reader:

file.take(length)
Enter fullscreen mode Exit fullscreen mode

No special “range object” is created.

Video namespace

Media assets have a structured logical namespace, including forms such as:

videos/{video_id}/source/original
videos/{video_id}/poster/poster.webp
videos/{video_id}/renditions/{name}/video.mp4
videos/{video_id}/hls/master.m3u8
videos/{video_id}/hls/{name}/segment_00001.m4s
Enter fullscreen mode Exit fullscreen mode

These are canonical object keys for media workflows.

They should still be understood separately from arbitrary raw filesystem placement.

Why metadata must exist

Once logical keys stop being physical paths, metadata becomes essential.

The metadata record answers:

What object does this key represent?
Where is its payload?
How large is it?
What is its checksum?
Is it committed?
Enter fullscreen mode Exit fullscreen mode

That is why Bastion has a distinct metadata plane.

Tradeoff

This design means you cannot treat /data/objects as a friendly human-readable bucket tree.

That is intentional.

The filesystem is an implementation detail.

The API namespace is the contract.

That separation gives the engine freedom to change placement strategies later without forcing application object keys to change with it.

Top comments (0)