DEV Community

Cover image for Inside Lioran S3: How a PUT Becomes a Durable Object in the Rust Engine
Swaraj Puppalwar
Swaraj Puppalwar

Posted on

Inside Lioran S3: How a PUT Becomes a Durable Object in the Rust Engine

Inside Lioran S3: How a PUT Becomes a Durable Object in the Rust Engine

Most object-storage APIs make PUT look almost offensively simple:

PUT /bucket/key
<body>
Enter fullscreen mode Exit fullscreen mode

Inside Lioran S3 / Lioran Bastion, that request crosses several boundaries before the engine returns success.

I’m Swaraj Puppalwar, Founder & CTO of Lioran Group and Lioran Developer Solutions (LDS). This article begins a deeper series about the actual Rust engine behind Lioran S3 V1 Pre-Alpha.

The implementation discussed here is the current pre-alpha code, not a generic object-store design.

The important types

The local engine is centered around LocalObjectStore:

pub struct LocalObjectStore {
    layout: StorageLayout,
    metadata: Arc<dyn MetadataStore>,
    durability: DurabilityMode,
    chunk_size: usize,
    metrics: Arc<PutMetrics>,
    min_free_space_bytes: u64,
    min_free_space_percent: Option<f64>,
}
Enter fullscreen mode Exit fullscreen mode

This already tells us a lot.

The object engine does not own one giant database containing everything. It coordinates:

  • physical filesystem layout
  • a metadata abstraction
  • durability policy
  • bounded streaming
  • performance metrics
  • disk-capacity guardrails

Step 1: validate the logical target

put_object rejects empty bucket names and keys.

Then it asks the metadata layer whether the bucket exists:

let bucket_meta = self
    .metadata
    .get_bucket(bucket)?
    .ok_or_else(|| BastionError::BucketNotFound(bucket.to_string()))?;
Enter fullscreen mode Exit fullscreen mode

The filesystem is not the namespace authority.

The metadata layer is.

Step 2: check physical capacity

Before receiving the object, Bastion checks host free-space guardrails:

check_disk_space_available(...)
Enter fullscreen mode Exit fullscreen mode

This is separate from bucket quota.

That distinction matters.

A bucket may have logical quota remaining while the physical host is nearly full.

Step 3: calculate overwrite-aware quota state

The engine checks whether the key already points to a committed object.

Its size is subtracted from projected usage so replacing a 5 GiB object with another 5 GiB object is not treated as adding a fresh 5 GiB forever.

Conceptually:

projected =
    current_usage
    - existing_object_size
    + new_object_size
Enter fullscreen mode Exit fullscreen mode

There is an early quota guard before streaming and an exact check after the actual byte count is known.

Step 4: create independent IDs

The engine generates:

object_id
staging_id
Enter fullscreen mode Exit fullscreen mode

using UUIDs.

The user key is not used as the physical filename.

That separation is fundamental to Bastion's storage model.

Step 5: stream into staging

The staging path is generated by StorageLayout.

The engine creates a temporary file and calls:

stream_to_staging(
    stream,
    &mut staging_file,
    self.durability,
    self.chunk_size,
)
Enter fullscreen mode Exit fullscreen mode

That function performs the heavy data-path work:

AsyncRead
   ↓
fixed-size buffer
   ↓
SHA-256 update
   ↓
staging file write
   ↓
flush
   ↓
optional fsync
Enter fullscreen mode Exit fullscreen mode

No full-object heap buffer is required.

Step 6: close the staging handle

After streaming, the file handle is explicitly dropped before rename.

That is especially relevant on platforms where open-handle rename behavior differs.

Step 7: validate capacity again

The engine checks host free space again after receiving the payload.

It also recalculates bucket quota using the exact number of bytes written.

If the final size exceeds quota, the staging file is removed.

Step 8: choose physical placement

The object ID is mapped into a partitioned physical path.

For an ID beginning with:

ab12...
Enter fullscreen mode Exit fullscreen mode

the layout becomes conceptually:

objects/
└── ab/
    └── 12/
        └── <full-object-uuid>
Enter fullscreen mode Exit fullscreen mode

This keeps arbitrary user keys away from raw filesystem path construction.

Step 9: promote the physical object

The current implementation performs:

fs::rename(&staging_path, &abs_path).await
Enter fullscreen mode Exit fullscreen mode

to promote the staged file into the permanent object tree.

This is an important detail: the current code promotes the physical file before writing the final metadata record.

That is the behavior of the implementation today.

Step 10: build metadata

The engine constructs ObjectMetadata containing information including:

  • internal object ID
  • bucket
  • logical key
  • physical relative path
  • byte size
  • content type
  • SHA-256

Step 11: commit metadata to RocksDB

The metadata record is written through:

self.metadata.put_object(&meta)
Enter fullscreen mode Exit fullscreen mode

If that operation fails, the engine attempts to roll back the already-promoted physical file:

let _ = fs::remove_file(&abs_path).await;
Enter fullscreen mode Exit fullscreen mode

This avoids intentionally leaving committed physical bytes with no metadata record.

Step 12: record timings

The PUT path records separate timings for:

receive
write
SHA-256
flush
fsync
close
mkdir
rename
metadata
total
Enter fullscreen mode Exit fullscreen mode

When BASTION_TRACE_PUT_TIMINGS is enabled, those stages can be inspected individually.

That is far more useful than seeing only:

PUT took 427 ms
Enter fullscreen mode Exit fullscreen mode

because it tells us where the 427 ms went.

The actual V1 write pipeline

The current implementation can be summarized as:

validate bucket/key
      ↓
check disk + quota
      ↓
create staging file
      ↓
stream + hash
      ↓
flush / optional fsync
      ↓
re-check capacity + quota
      ↓
choose UUID-based final path
      ↓
rename staging → object path
      ↓
write metadata to RocksDB
      ↓
rollback physical file if metadata fails
      ↓
return committed metadata
Enter fullscreen mode Exit fullscreen mode

This is the kind of machinery hidden behind one innocent-looking PUT.

Lioran S3 V1 Pre-Alpha launched on October 1, 2026. The next planned release is November 17, 2026.

Repository: https://github.com/LioranGroupOfficial/LioranBastion-Rust

Documentation: https://docs.liorans3.sbs

Top comments (0)