<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rabata</title>
    <description>The latest articles on DEV Community by Rabata (@rabata_io).</description>
    <link>https://dev.to/rabata_io</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3907550%2Fb6d0845d-debb-4809-b6df-caab65e2e82c.png</url>
      <title>DEV Community: Rabata</title>
      <link>https://dev.to/rabata_io</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rabata_io"/>
    <language>en</language>
    <item>
      <title>Dead disks, no errors: how we keep failures away from users</title>
      <dc:creator>Rabata</dc:creator>
      <pubDate>Thu, 29 Oct 2026 13:17:17 +0000</pubDate>
      <link>https://dev.to/rabata_io/how-rabata-s3-survives-dead-disks-2pd8</link>
      <guid>https://dev.to/rabata_io/how-rabata-s3-survives-dead-disks-2pd8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; is S3-compatible object storage running on our own hardware in the EU and the US. In this four-part series our engineers explain how the stack works under the hood. &lt;a href="https://dev.toPASTE_PART_3_URL"&gt;Part 3&lt;/a&gt; covered how files are split into chunks with redundancy. This part is about what that redundancy is for: surviving failures without the client seeing them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reads: rebuild the chunk and keep streaming
&lt;/h2&gt;

&lt;p&gt;If a data chunk is unavailable (a dead disk, a slow server, a corrupted file), the reader rebuilds it from the erasure codes and keeps streaming. The client sees a slightly slower read, not a failure.&lt;/p&gt;

&lt;p&gt;The read also keeps the recovery set in memory. If a chunk turns out to be unavailable, the chunks already fetched don't have to be downloaded again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writes: switch disks mid-upload
&lt;/h2&gt;

&lt;p&gt;On write the same property works the other way. If a chunk can't be written after several retries, the target disk is replaced with another one right in the middle of the upload. The request doesn't fail.&lt;/p&gt;

&lt;p&gt;This is what makes our storage fault-tolerant. Disks fail and become unavailable from time to time, but the errors don't reach users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every disk is a scheduled resource
&lt;/h2&gt;

&lt;p&gt;At any moment several kinds of operations compete for the same disk: user reads, user writes, background chunk verification, chunk repair and rebalancing. Left uncontrolled, background work would push up request latency for clients.&lt;/p&gt;

&lt;p&gt;So every chunk request to a storage server carries its initiator: a user, repair, the verifier, rebalancing, other background work. In front of each disk sits a bounded priority queue with a concurrency limit, plus a rate limiter that can slow down a specific initiator.&lt;/p&gt;

&lt;p&gt;Priorities are explicit and configured per operation type. A user read outranks a verifier read. Deletion is the lowest-priority work of all: we delete in the background, and a user's delete request only cleans up the database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj1s85836b42d2kp8l2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj1s85836b42d2kp8l2q.png" alt="Each chunk request carries its initiator; a priority queue with a rate limiter in front of each disk decides what runs first." width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The disk is modeled as a scheduled resource with known consumers and their operations, not as a filesystem anyone writes to whenever they like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware moves through explicit states
&lt;/h2&gt;

&lt;p&gt;Servers and disks go through an explicit state machine:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Chunks written / read&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Yes / yes&lt;/td&gt;
&lt;td&gt;Hardware works as expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overload&lt;/td&gt;
&lt;td&gt;Less likely / yes&lt;/td&gt;
&lt;td&gt;Write load is reduced until metrics return to acceptable bounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No metrics&lt;/td&gt;
&lt;td&gt;No / no&lt;/td&gt;
&lt;td&gt;The system has received no metrics from the hardware for a long time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Out of use&lt;/td&gt;
&lt;td&gt;No / no&lt;/td&gt;
&lt;td&gt;Metrics have been out of bounds for a very long time; an alert fires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault&lt;/td&gt;
&lt;td&gt;No / no&lt;/td&gt;
&lt;td&gt;The hardware is unfit for use; all its chunks are queued for repair; an alert fires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;No / no&lt;/td&gt;
&lt;td&gt;Returned to service manually only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decommissioning&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;All chunks are queued for repair; once done, the hardware is removed automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;States are derived from observed metrics, not set by hand. A dedicated control-plane service moves hardware between states and also computes disk weights for placement from free space and load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ways a repair starts
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scrubbing.&lt;/strong&gt; A background process runs on every storage server. It walks every chunk on every disk, reads it directly from the disk with &lt;code&gt;O_DIRECT&lt;/code&gt;, and checks its integrity against a stored hash. Hidden corruption is found by scanning, not by a user who happened to read a bad chunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk fault.&lt;/strong&gt; The control plane moves a disk to Fault and starts a job to repair all of its chunks. The job is split into smaller ones, which in turn schedule repairs of specific chunks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Questions about any of it? Our engineers read the comments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; S3: S3-compatible storage at $0.006/GB/month with no API request fees. Change the endpoint and keys, keep your code.&lt;/em&gt;&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>cloudstorage</category>
      <category>s3</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Same durability, less than half the disk: how we pick redundancy per file</title>
      <dc:creator>Rabata</dc:creator>
      <pubDate>Thu, 29 Oct 2026 13:03:09 +0000</pubDate>
      <link>https://dev.to/rabata_io/same-durability-less-than-half-the-disk-how-we-pick-redundancy-per-file-ijc</link>
      <guid>https://dev.to/rabata_io/same-durability-less-than-half-the-disk-how-we-pick-redundancy-per-file-ijc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; is S3-compatible object storage running on our own hardware in the EU and the US. In this four-part series our engineers explain how the stack works under the hood. &lt;a href="https://dev.to/rabata_io/rabata-s3-layer-never-stores-a-single-byte-of-your-data-4h7l"&gt;Part 1&lt;/a&gt; and &lt;a href="https://dev.toPASTE_PART_2_URL"&gt;Part 2&lt;/a&gt; covered the architecture and the S3 metadata layer. This part is about dstorage, the service that stores the bytes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Files, recovery sets, chunks
&lt;/h2&gt;

&lt;p&gt;A file is a byte stream with a numeric ID. On write it is split into recovery sets, and each recovery set consists of several chunks. Every chunk in a recovery set is placed on a different server.&lt;/p&gt;

&lt;p&gt;A chunk's address is a triple: (file ID, chunk index, disk ID). File metadata (size, redundancy scheme, the map of chunks to disks) lives in a sharded metadata store. Chunk bytes never go there. The metadata store holds only what's needed to find a chunk and to know the state of the hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many extra bytes
&lt;/h2&gt;

&lt;p&gt;Disks die. Servers reboot. A filesystem sometimes returns something other than what was written. Storage has to survive all of this without losing data, which means storing more bytes than the file itself takes. The only question is how many more.&lt;/p&gt;

&lt;p&gt;The simplest answer is several full copies on different servers. Three copies survive the loss of any two disks, but take three times the space of the data.&lt;/p&gt;

&lt;p&gt;Erasure codes solve the same problem cheaper. The file is cut into N data chunks, and M more parity chunks are computed from them arithmetically. All N + M go to different servers. The trick is that the original data can be restored from any N chunks of the set: lose two, and the missing ones are recomputed from the rest.&lt;/p&gt;

&lt;p&gt;The space overhead drops from (M + 1)× to (N + M) / N, and durability stays the same: the set survives the loss of any M chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small files pay a per-chunk tax
&lt;/h2&gt;

&lt;p&gt;We pay for the saved space with computation on every write and every chunk repair. On small files we also pay per-chunk overhead. Erasure coding a 3 KiB file gives chunks of a few hundred bytes. For each such chunk, the filesystem write, the metadata record, the network call and the I/O operation outweigh what the chunk protects.&lt;/p&gt;

&lt;p&gt;So the redundancy scheme is chosen for each file based on its size. Two schemes are configured today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scheme&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;th&gt;Layout&lt;/th&gt;
&lt;th&gt;Space&lt;/th&gt;
&lt;th&gt;Survives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copy&lt;/td&gt;
&lt;td&gt;Small files&lt;/td&gt;
&lt;td&gt;The file + 2 copies, all on different servers&lt;/td&gt;
&lt;td&gt;3×&lt;/td&gt;
&lt;td&gt;Loss of any 2 copies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reed–Solomon 5+2&lt;/td&gt;
&lt;td&gt;Everything above the threshold&lt;/td&gt;
&lt;td&gt;5 data + 2 parity chunks per recovery set&lt;/td&gt;
&lt;td&gt;1.4×&lt;/td&gt;
&lt;td&gt;Loss of any 2 of 7 chunks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Copy costs more space, but a small file is one chunk instead of seven, and nothing has to be computed on read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw94cuej195jwxueujlic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw94cuej195jwxueujlic.png" alt="1.4× instead of 3×. So why do small files still get three copies" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Schemes are stored by ID, not by value
&lt;/h2&gt;

&lt;p&gt;File metadata records which configuration the file was written with. New configurations are added rarely.&lt;/p&gt;

&lt;p&gt;If we want a new scheme, or new parameters for an existing one (a different number of data or parity chunks, a different chunk size), we don't need a migration. Old files are always read with the scheme they were written with. A new redundancy policy rolls out as a config change and affects new writes only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunk count matters more than byte count
&lt;/h2&gt;

&lt;p&gt;The last group of a file rarely divides evenly. Instead of an incomplete group of tiny chunks, the remainder is appended to the previous group. This keeps chunk sizes in a sane range regardless of file size.&lt;/p&gt;

&lt;p&gt;That matters because the number of chunks, not the number of bytes, defines metadata volume, the number of I/O operations and the cost of repairing a chunk.&lt;/p&gt;

&lt;h2&gt;
  
  
  No per-chunk index
&lt;/h2&gt;

&lt;p&gt;There is no index that maps byte offsets to chunks. The layout is computed. Given a file's size and redundancy scheme, the position of any byte (which recovery set, which chunk, which offset inside the chunk) can be calculated. Reads and repairs of damaged or unavailable chunks use the same code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming writes with bounded memory
&lt;/h2&gt;

&lt;p&gt;Writes are streamed, but erasure coding needs a full parity group in memory to compute the parity chunks. Chunks of a recovery set are accumulated and flushed once parity is computed and the chunks are uploaded to storage servers.&lt;/p&gt;

&lt;p&gt;Memory use is bounded by one parity group (group width × chunk size), whether the file is 1 MiB or 1 TiB. And the parallelism that matters is preserved: the chunks of a group go to different disks on different servers at the same time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; S3: S3-compatible storage at $0.006/GB/month with no API request fees. Change the endpoint and keys, keep your code.&lt;/em&gt;&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>s3</category>
      <category>cloudstorage</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>No hashing, no fan-out: how we shard S3 metadata for ListObjects</title>
      <dc:creator>Rabata</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:56:43 +0000</pubDate>
      <link>https://dev.to/rabata_io/hash-sharding-would-have-broken-listobjects-so-we-dont-hash-keys-437d</link>
      <guid>https://dev.to/rabata_io/hash-sharding-would-have-broken-listobjects-so-we-dont-hash-keys-437d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; is S3-compatible object storage running on our own hardware in the EU and the US. In this four-part series our engineers explain how the stack works under the hood. &lt;a href="https://dev.to/rabata_io/rabata-s3-layer-never-stores-a-single-byte-of-your-data-4h7l"&gt;Part 1 &lt;/a&gt;covered the overall architecture. This part is about s3-front, the service that holds all S3 metadata.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Redis
&lt;/h2&gt;

&lt;p&gt;All S3 metadata lives in Redis. So does metadata in our other services. The main argument is speed: request latency is measured in microseconds, not in the milliseconds of relational databases. Object metadata is small, uniform, and read far more often than written.&lt;/p&gt;

&lt;p&gt;Redis also comes with constraints. Everything lives in memory, so data volume is bounded by RAM. Redis is single-threaded, so it scales through sharding and replicas. Three more constraints define our whole data model.&lt;/p&gt;

&lt;h2&gt;
  
  
  No transactions: one script per S3 operation
&lt;/h2&gt;

&lt;p&gt;Redis has no transactions, or more precisely, no atomicity across commands. There are only Lua scripts and optimistic locking via WATCH.&lt;/p&gt;

&lt;p&gt;A single S3 operation touches several metadata structures at once: the object record, for multipart objects the records of their parts, the latest-version pointer, the bucket's ordered listing index and the bucket's usage counters.&lt;/p&gt;

&lt;p&gt;Doing that from the client would mean a chain of network calls with no atomicity and no way to roll back. So every S3 operation is wrapped in a script executed inside Redis. It reads what it needs, checks preconditions, applies all mutations and returns the result. The unit of atomicity is the script. The boundary of that atomicity is one shard.&lt;/p&gt;

&lt;h2&gt;
  
  
  No cross-slot commands: every object lands in one shard
&lt;/h2&gt;

&lt;p&gt;A single command can't touch keys from different slots. So an operation that can't be expressed within one shard can't be atomic.&lt;/p&gt;

&lt;p&gt;The data model is built so that any operation on one object (put, get, delete, any multipart step) lands in exactly one shard by construction. This is not an optimization. It is the reason the model looks the way it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  No secondary indexes: every access pattern is built by hand
&lt;/h2&gt;

&lt;p&gt;There is no query planner. Each access pattern S3 requires gets its own structure, written synchronously by the same script that writes the object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an ordered per-bucket index for listings;&lt;/li&gt;
&lt;li&gt;a pointer to resolve the latest version;&lt;/li&gt;
&lt;li&gt;an offset index to find the parts of a multipart object.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The obvious answer: hash the key
&lt;/h2&gt;

&lt;p&gt;In Redis Cluster the unit of sharding is the slot, and there are always exactly 16,384 of them. Every metadata key belongs to exactly one slot. The objects of each bucket live across several slots, and an object is routed to a slot by its key.&lt;/p&gt;

&lt;p&gt;The obvious way to route is hashing: hash the object key, get the slot. It gives near-perfect load distribution and needs no coordination.&lt;/p&gt;

&lt;p&gt;But it makes ListObjects inefficient. S3 requires results in lexicographic order, with prefixes, delimiters and resumable pagination. With hash sharding, every listing fans out to all shards. Results then have to be merge-sorted, and most of what was read is thrown away. The cost grows with the number of shards, not with the size of the response.&lt;/p&gt;

&lt;p&gt;And since the number of objects in a bucket is unbounded, keeping a whole bucket on one shard is not an option either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we do instead: range sharding
&lt;/h2&gt;

&lt;p&gt;Buckets are sharded through an explicit mapping. The object key space is split into contiguous key ranges, and one bucket can span several shards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A request for an object goes to the slot whose range contains its key.&lt;/li&gt;
&lt;li&gt;A listing starts at the slot that contains the start key and walks forward in order. It stops as soon as the next slot's range falls outside the requested prefix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd49t1zkadp2utsljo24l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd49t1zkadp2utsljo24l.png" alt="Hash sharding one request fans out to all six shards, then results are merge-sorted and most are discarded; with range sharding the request starts at the shard holding the start key and walks forward through one more shard, then stops." width="799" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The price: hot ranges
&lt;/h2&gt;

&lt;p&gt;Range sharding has a weak spot: hot prefixes, or keys written by date, where every write lands at the end of the range. Range sharding turns that skew into a hot shard, so placement has to be managed actively.&lt;/p&gt;

&lt;p&gt;Today per-slot throughput leaves us a large margin. If that changes, there is a middle ground: hash the key first and give each bucket a range of hashes. A bucket then spreads over a controlled subset of shards rather than all of them.&lt;/p&gt;

&lt;p&gt;New buckets are placed by weighted random choice across slots. Weights are recalculated periodically from the number of keys per slot, so emptier slots are more likely to get new buckets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-region: only bucket names are global
&lt;/h2&gt;

&lt;p&gt;Exactly one thing must be globally consistent. Bucket names in S3 are globally unique, so the registry “name → owner and region” is shared across regions. Everything else (object metadata, usage accounting, control state) is strictly regional.&lt;/p&gt;

&lt;p&gt;A request that hits the wrong region is not an error. The registry says which region owns the bucket, and the request is proxied there over an internal API. Global consistency is limited to a small, rarely changing, cacheable dataset. The expensive parts of the system never pay for cross-region coordination.&lt;/p&gt;

&lt;h2&gt;
  
  
  For contrast: a layer that doesn't need order
&lt;/h2&gt;

&lt;p&gt;The storage layer, dstorage, also keeps its metadata in Redis Cluster, with the same constraints but different sharding. Files are addressed by ID and there are no listings, so order doesn't matter.&lt;/p&gt;

&lt;p&gt;New file IDs are handed out across slots round-robin, and the slot is encoded in the ID itself: it is derived arithmetically, with no index lookup. Load spreads evenly, and hot ranges simply don't occur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata S3&lt;/a&gt;: S3-compatible storage at $0.006/GB/month with no API request fees. Change the endpoint and keys, keep your code.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>systemdesign</category>
      <category>s3</category>
      <category>database</category>
    </item>
    <item>
      <title>Zero bytes of your data: how our S3 layer keeps only metadata</title>
      <dc:creator>Rabata</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:20:49 +0000</pubDate>
      <link>https://dev.to/rabata_io/rabata-s3-layer-never-stores-a-single-byte-of-your-data-4h7l</link>
      <guid>https://dev.to/rabata_io/rabata-s3-layer-never-stores-a-single-byte-of-your-data-4h7l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; is S3-compatible object storage running on our own hardware in the EU and the US. We built the stack ourselves, from the S3 API down to how bytes land on disk. In this four-part series our engineers explain how it works: the trade-offs we made, what they cost, and why. Part 1 is the map. Parts 2–4 go deep on metadata, redundancy and failure handling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Three services, three jobs
&lt;/h2&gt;

&lt;p&gt;Our storage is three independent services. Each has its own repository, its own databases and its own release cycle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;s3-front&lt;/strong&gt; is everything that makes up the S3 protocol: buckets, keys, versions, multipart uploads, ownership, ACLs and policies, request signing, encryption, usage accounting. It stores S3 metadata only. Not a single byte of object data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dstorage&lt;/strong&gt; is distributed, fault-tolerant file storage: servers, disks, files and chunks. On upload a file is split into chunks with redundancy, cheap to keep but able to survive a server or disk failure. On download it is reassembled from them. Lost data is restored from chunk copies or from erasure codes such as Reed–Solomon. Chunk placement across servers and disks lives here too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IAM&lt;/strong&gt; is authentication and authorization: accounts, IAM users, access keys, account state. It is out of scope for this series.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81t1u3puhh8nzagmwq0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81t1u3puhh8nzagmwq0y.png" alt="A key space split into six contiguous ranges; a listing for the prefix &amp;quot;photos/&amp;quot; walks across two adjacent ranges" width="799" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One integer between metadata and bytes
&lt;/h2&gt;

&lt;p&gt;The split between s3-front and dstorage is deliberate. A file in dstorage is identified by a single 64-bit integer. That integer is the only thing s3-front keeps from dstorage.&lt;/p&gt;

&lt;p&gt;The entire interaction between them is six operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reserve a file ID.&lt;/li&gt;
&lt;li&gt;Upload a byte stream.&lt;/li&gt;
&lt;li&gt;Read a byte range.&lt;/li&gt;
&lt;li&gt;Read a concatenation of ranges from several files, so a multipart object is served in one network request instead of one per part.&lt;/li&gt;
&lt;li&gt;Copy one byte stream into another.&lt;/li&gt;
&lt;li&gt;Delete a file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What the split buys us
&lt;/h2&gt;

&lt;p&gt;dstorage exposes only a file: a byte stream with an ID. So inside it we can change the redundancy scheme, chunk size, disk layout and placement policy without touching the S3 layer.&lt;/p&gt;

&lt;p&gt;The reverse holds too. S3 semantics (versioning rules, conditional requests, listing order) never leak into the storage layer. Each service is tested and rolled out independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consistency without distributed transactions
&lt;/h2&gt;

&lt;p&gt;Neither service has distributed transactions. Yet each has to keep two independent stores in agreement. For s3-front, that is its metadata and the files in dstorage. For dstorage, its metadata and the bytes on disks. Both solve it the same way.&lt;/p&gt;

&lt;p&gt;Every write follows one order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Record intent.&lt;/strong&gt; Before a single byte is written, the operation records that it is about to write: a timestamped marker in the same shard that will hold the final metadata.&lt;br&gt;
&lt;strong&gt;2. Write data.&lt;/strong&gt; Bytes go over the network to the disks.&lt;br&gt;
&lt;strong&gt;3. Commit metadata.&lt;/strong&gt; Only once the bytes are durably written is metadata committed, atomically, in a single script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsbk9asyig4yxzbqqjjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsbk9asyig4yxzbqqjjj.png" alt="Three full copies take 3× the space; five data chunks plus two parity chunks take 1.4×" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Metadata is committed last, so it never points at bytes that aren't there. And because intent is recorded first, no bytes are ever left untracked: if a write dies halfway, the marker says what to clean up.&lt;/p&gt;

&lt;p&gt;Orphaned bytes are removed asynchronously, the same way in both systems. Write and delete paths put records into queues. Periodic jobs pick them up from the database and move them to a deletion queue.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata S3&lt;/a&gt;: S3-compatible storage at $0.006/GB/month with no API request fees. Change the endpoint and keys, keep your code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>distributedsystems</category>
      <category>s3</category>
      <category>cloudstorage</category>
    </item>
    <item>
      <title>Cloud storage credits for Gen-AI startups: what's real, what's not, and what we're offering</title>
      <dc:creator>Rabata</dc:creator>
      <pubDate>Tue, 05 May 2026 10:15:44 +0000</pubDate>
      <link>https://dev.to/rabata_io/cloud-storage-credits-for-gen-ai-startups-whats-real-whats-not-and-what-were-offering-41cf</link>
      <guid>https://dev.to/rabata_io/cloud-storage-credits-for-gen-ai-startups-whats-real-whats-not-and-what-were-offering-41cf</guid>
      <description>&lt;p&gt;If you're building a Gen-AI startup, you've probably already seen the headlines. $100K here, $350K there. Every major cloud has a startup program and they all sound generous until you read the fine print.&lt;/p&gt;

&lt;p&gt;This is a breakdown of what those programs actually look like in practice: the caps, the catches, and the pricing cliffs nobody puts in the headline. And at the end, what we're offering at Rabata and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What programs actually look like
&lt;/h2&gt;

&lt;p&gt;Every major cloud has a startup program. The numbers look generous on paper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://aws.amazon.com/startups" rel="noopener noreferrer"&gt;AWS Activate&lt;/a&gt; offers up to $100K&lt;/strong&gt; — but the $100K tier requires affiliation with an approved VC or accelerator. And **"up to" **is doing a lot of work in that sentence. In practice, AWS evaluates your stage, traction, and provider relationship and most early-stage teams walk away with $5K–$25K, not the headline number. Even then, AWS's own support forum acknowledges they have "internal requirements beyond what's publicly listed." Founders get rejected with no explanation. Credits expire in 12–24 months and AWS will not extend them, even if you have unused balance. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;One subtle trap: the Free Tier silently disappears the moment you set up AWS Organizations (their own recommended multi-account structure) with no warning.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;Google for Startups&lt;/a&gt;&lt;/strong&gt; offers up to $350K for AI startups. Year 1 covers 100% of GCP usage up to $250K — that part is real. Year 2 is 20% off your usage up to $100K. To redeem the full $100K in Year 2, you'd need to be spending $500K/year on GCP. For most early-stage teams, that number isn't there yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.cloudflare.com/en-gb/forstartups/" rel="noopener noreferrer"&gt;Cloudflare for Startups&lt;/a&gt;&lt;/strong&gt; goes up to $250K, but the top tier requires Tier-1 VC backing. More importantly for storage-heavy workloads: R2 (their S3 alternative) is capped at $10K of credit usage regardless of your headline tier. If you're storing model checkpoints, training data, or generated video at any scale, you'll hit that in weeks.&lt;/p&gt;

&lt;p&gt;These programs exist for good reasons and some of them are worth applying to — especially for compute and managed AI services. But they're designed around platform lock-in: the clock starts on day one, the credits are capped on the things you actually need, and the pricing after credits run out is full rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trap nobody talks about upfront&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before you pick any cloud provider (credit program or not) &lt;strong&gt;run the egress math first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS charges $0.09/GB for data transfer out. That doesn't sound catastrophic until you're moving terabytes of training data, model outputs, or video assets regularly. At scale, egress alone can exceed your storage costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The worse part:&lt;/strong&gt; once you're on AWS and your data is there, leaving gets expensive too. Migrating out means paying egress on everything you move. So you end up in a position where staying is costly and leaving is also costly. That's not an accident — it's how the economics are designed.&lt;/p&gt;

&lt;p&gt;This applies beyond AWS. Check egress pricing on day one, not after you've built your pipeline around a provider. &lt;strong&gt;The credit offer is the entry point; the pricing model is what you'll actually live with.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built our grant
&lt;/h2&gt;

&lt;p&gt;Gen-AI startups are data-heavy in a way most software startups aren't. Training datasets, model checkpoints, fine-tuning runs, inference logs, generated outputs — storage compounds fast, and it doesn't stop after your free credits expire.&lt;/p&gt;

&lt;p&gt;Because at &lt;a href="https://rabata.io/" rel="noopener noreferrer"&gt;Rabata&lt;/a&gt; we control our infrastructure costs, we can price differently from resellers and hyperscalers: $0.01/GB/month, free ingress, $0.01/GB egress, no per-request fees.&lt;/p&gt;

&lt;p&gt;At that price, **$100K in Rabata credits is roughly 833TB of active storage for a year. **The same $100K on AWS S3 Standard gets you around 250TB, before egress. If your pipeline moves data between services, that gap widens further.&lt;/p&gt;

&lt;p&gt;That's not a marketing trick — it's what happens when you don't resell AWS underneath your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Rabata grant is
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;$100K in Rabata credits&lt;/li&gt;
&lt;li&gt;Covers S3-compatible object storage&lt;/li&gt;
&lt;li&gt;2-year duration — not 12 months, not 90 days&lt;/li&gt;
&lt;li&gt;No equity. No VC requirement. No exclusivity.&lt;/li&gt;
&lt;li&gt;Short application form. We respond within 24–48 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;Eligibility: *&lt;/em&gt; you're building a Gen-AI product and have real storage needs. That's it.&lt;/p&gt;

&lt;p&gt;Apply here: &lt;a href="https://rabata.io/grant-application" rel="noopener noreferrer"&gt;https://rabata.io/grant-application&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudstorage</category>
      <category>offers</category>
      <category>genai</category>
    </item>
    <item>
      <title>We benchmarked 10+ S3 providers — here's what the numbers actually show</title>
      <dc:creator>Rabata</dc:creator>
      <pubDate>Fri, 01 May 2026 13:23:12 +0000</pubDate>
      <link>https://dev.to/rabata_io/we-benchmarked-10-s3-providers-heres-what-the-numbers-actually-show-3025</link>
      <guid>https://dev.to/rabata_io/we-benchmarked-10-s3-providers-heres-what-the-numbers-actually-show-3025</guid>
      <description>&lt;p&gt;With so many S3-compatible providers on the market, it can be difficult to understand which one truly fits your workload.&lt;/p&gt;

&lt;p&gt;To keep things objective, we used MinIO warp v1.0.7, a widely adopted open-source benchmarking tool. Tests included upload, download, mixed workloads, small object throughput, and large object handling. All benchmarks were run under identical conditions on a Debian 13 VM with 16 GB RAM, located in US-East-1, using 8 concurrent threads and consistent object sizes.&lt;/p&gt;

&lt;p&gt;Our methodology is fully reproducible — every parameter and setup detail is publicly available. You can find the complete benchmark methodology and replication guide with step-by-step instructions to run your own tests. All pricing information reflects rates publicly available as of October 1, 2025.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the summary:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8hkwurlaiuk0egzo6dgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8hkwurlaiuk0egzo6dgs.png" alt="S3 Storage Providers&lt;br&gt;
Performance Comparison" width="800" height="394"&gt;&lt;/a&gt;&lt;br&gt;
A few things that surprised us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixed operations (simultaneous reads, writes, metadata) is the most realistic measure of real-world performance — and results vary wildly&lt;/li&gt;
&lt;li&gt;Cloudflare R2 has zero egress fees but came in last on mixed ops (44 Mbit/s) and small objects (42 obj/s)&lt;/li&gt;
&lt;li&gt;Hetzner's numbers look low — but that's because we tested from US-East-1 and they're EU-only, so transatlantic latency skews the results&lt;/li&gt;
&lt;li&gt;Backblaze B2 dominates on downloads but showed timeouts during large-object tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For full per-provider breakdowns and detailed analysis: &lt;a href="https://rabata.io/s3-comparison" rel="noopener noreferrer"&gt;https://rabata.io/s3-comparison&lt;/a&gt; &lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>cloudstorage</category>
      <category>api</category>
    </item>
  </channel>
</rss>
