<?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: Joichiro Mitaka</title>
    <description>The latest articles on DEV Community by Joichiro Mitaka (@coldstorage).</description>
    <link>https://dev.to/coldstorage</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%2F3991890%2F8d80d521-aaef-4748-9485-eabc2f04b9ed.png</url>
      <title>DEV Community: Joichiro Mitaka</title>
      <link>https://dev.to/coldstorage</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coldstorage"/>
    <language>en</language>
    <item>
      <title>Point-in-Time Recovery Without the Enterprise Price Tag</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Sat, 25 Jul 2026 07:28:28 +0000</pubDate>
      <link>https://dev.to/coldstorage/point-in-time-recovery-without-the-enterprise-price-tag-obm</link>
      <guid>https://dev.to/coldstorage/point-in-time-recovery-without-the-enterprise-price-tag-obm</guid>
      <description>&lt;p&gt;description: A deep dive into HuskHoard's open-source PITR engine — how the catalog tracks version history and byte offsets across physical media, and how you roll back a single file without touching the rest of the volume.&lt;/p&gt;

&lt;h1&gt;
  
  
  Point-in-Time Recovery Without the Enterprise Price Tag
&lt;/h1&gt;

&lt;p&gt;If you've priced out Veeam, Commvault, or a Zerto license recently, you already know the punchline: &lt;strong&gt;granular point-in-time recovery is one of the most aggressively monetized features in enterprise storage&lt;/strong&gt;. Vendors charge for it because it genuinely solves a hard problem — and because buyers are often in enough pain that they'll pay whatever it costs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huskhoard.com" rel="noopener noreferrer"&gt;HuskHoard&lt;/a&gt; is an open source data tiering archive for Linux that ships PITR as a first-class feature, built directly into its catalog engine, free under AGPL v3. This post is the deep dive that the project's docs don't yet cover: how versioning actually works under the hood, how the catalog tracks byte offsets across physical media (disk, LTO tape, and S3), and how you roll back a single file without touching anything else on the volume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Versioning vs Backup: Stop Using the Words Interchangeably
&lt;/h2&gt;

&lt;p&gt;Before getting into mechanics, it's worth being precise about terminology, because these two words describe fundamentally different operations with different tradeoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A backup&lt;/strong&gt; is a complete redundant copy of a dataset at a point in time. The goal is full-loss recovery. If your server catches fire, you restore the backup. The cost is that you're duplicating data — 10 TB of active data requires at least 20 TB of total storage to have one backup copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versioning&lt;/strong&gt; (or PITR at the object level) is a historical record of &lt;em&gt;how a specific file changed over time&lt;/em&gt;. The goal is granular rollback — recovering from an overwrite, a corruption, or a ransomware event that targeted specific files. It doesn't guarantee you can recover from total hardware loss unless your versions live on separate media. But it lets you say "give me &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; as it was three weeks ago" without restoring your entire backup set.&lt;/p&gt;

&lt;p&gt;The confusion is expensive. People who treat their archive as a backup get burned when the archive goes down (there's no redundant copy). People who treat their backup as a versioning system get burned when they need to roll back a single file and discover they have to restore 2 TB of snapshot data to get at it.&lt;/p&gt;

&lt;p&gt;A solid DR strategy uses both:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;What You Want&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server catches fire / disk fails completely&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Backup&lt;/strong&gt; — full restore from a redundant copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer overwrites critical config file&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PITR&lt;/strong&gt; — roll back that specific file in seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ransomware encrypts 10,000 files&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PITR&lt;/strong&gt; — roll back affected files to pre-infection version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM guest OS corrupts itself&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Backup&lt;/strong&gt; — restore the whole VM image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM config file or snapshot is accidentally deleted&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PITR&lt;/strong&gt; — roll back just that object&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;HuskHoard handles versioning (PITR at the file/object level) natively. For full redundancy, pair it with N-way replication across multiple volumes, which HuskHoard also handles.&lt;/p&gt;




&lt;h2&gt;
  
  
  How HuskHoard's Versioning Engine Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Append-Only Storage Model
&lt;/h3&gt;

&lt;p&gt;HuskHoard's architecture is built on a core constraint borrowed from tape: &lt;strong&gt;all writes are append-only and sequential&lt;/strong&gt;. Even if you're archiving to a spinning disk image or an S3 bucket, HuskHoard writes to the volume as though it were a tape — from the current write head forward, never punching holes or overwriting previous data.&lt;/p&gt;

&lt;p&gt;This constraint, which exists primarily to avoid the "write wall" problem on SMR drives and to stay compatible with physical LTO hardware, turns out to give you versioning entirely for free. When a file is re-archived, the old payload stays exactly where it was on the volume. The new version gets appended after everything else. Nothing is overwritten.&lt;/p&gt;

&lt;p&gt;The catalog is where this becomes useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Catalog Schema — The Version Column
&lt;/h3&gt;

&lt;p&gt;Here is the actual schema from &lt;code&gt;database.rs&lt;/code&gt;, as discussed in &lt;a href="https://huskhoard.com/blog-post-cat.html" rel="noopener noreferrer"&gt;The Catalog post&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;               &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt;          &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tape_uuid&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tape_offset&lt;/span&gt;      &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payload_size&lt;/span&gt;     &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- uncompressed byte count&lt;/span&gt;
    &lt;span class="n"&gt;compressed_size&lt;/span&gt;  &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression_type&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- 0=None, 1=Zstd&lt;/span&gt;
    &lt;span class="n"&gt;uid&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;gid&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;posix_mode&lt;/span&gt;       &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;archived_at&lt;/span&gt;      &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;original_mtime&lt;/span&gt;   &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blake3_hash&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;custom_metadata&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;idx_path_version&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the &lt;code&gt;(file_path, version)&lt;/code&gt; pair. Every time the Janitor archives a file that has been modified since its last archive event, it does &lt;strong&gt;not&lt;/strong&gt; overwrite the previous row. It inserts a new row with &lt;code&gt;version = previous_version + 1&lt;/code&gt;. The old row — pointing to the old payload at its old &lt;code&gt;tape_offset&lt;/code&gt; — stays in the catalog permanently (until pruned by &lt;code&gt;max_versions&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Because the old payload is still physically present on the append-only volume, that old row is a complete, restorable snapshot of the file at the moment it was archived.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file_path                        | version | tape_uuid | tape_offset | archived_at
---------------------------------+---------+-----------+-------------+---------------------
/data/projects/config.yaml       |       1 | a1b2c3... |        4096 | 2026-04-01 09:00:00
/data/projects/config.yaml       |       2 | a1b2c3... |    52432896 | 2026-04-15 14:22:11
/data/projects/config.yaml       |       3 | a1b2c3... |   104857600 | 2026-06-01 09:30:45
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three archive events. Three catalog rows. Three independently restorable versions. Zero additional tooling required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rolling Back a Specific Version
&lt;/h3&gt;

&lt;p&gt;From the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Restore the latest version (default behavior)&lt;/span&gt;
huskhoard restore &lt;span class="nt"&gt;--file&lt;/span&gt; /data/projects/config.yaml

&lt;span class="c"&gt;# Roll back to a specific version&lt;/span&gt;
huskhoard restore &lt;span class="nt"&gt;--file&lt;/span&gt; /data/projects/config.yaml &lt;span class="nt"&gt;--version&lt;/span&gt; 2

&lt;span class="c"&gt;# List all available versions for a file&lt;/span&gt;
huskhoard versions &lt;span class="nt"&gt;--file&lt;/span&gt; /data/projects/config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;versions&lt;/code&gt; subcommand shows you what's available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/data/projects/config.yaml
  v1  2026-04-01 09:00:00  blake3: a3f1...  44 KB   [tape: a1b2c3..., offset: 4096]
  v2  2026-04-15 14:22:11  blake3: 7c90...  47 KB   [tape: a1b2c3..., offset: 52432896]
  v3  2026-06-01 09:30:45  blake3: d81e...  51 KB   [tape: a1b2c3..., offset: 104857600]  ← current
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Under the Hood: The Mechanics of a Byte-Level Rollback
&lt;/h2&gt;

&lt;p&gt;This is the part that separates HuskHoard's approach from snapshot-based alternatives — and it's worth understanding in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Catalog Lookup
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;huskhoard restore --file /path/to/file --version 2&lt;/code&gt;, the first thing that happens is a single indexed SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;tape_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tape_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compressed_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;compression_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blake3_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posix_mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original_mtime&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/data/projects/config.yaml'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;(file_path, version)&lt;/code&gt; composite index makes this microsecond-fast regardless of how many millions of files and versions are in the catalog.&lt;/p&gt;

&lt;p&gt;This query returns a &lt;code&gt;tape_uuid&lt;/code&gt; and a &lt;code&gt;tape_offset&lt;/code&gt; — a complete physical address for the version's payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Volume Resolution
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;tape_uuid&lt;/code&gt; isn't a device path. It's a 16-byte UUID burned into the volume's header block when it was first formatted with &lt;code&gt;huskhoard format&lt;/code&gt;. This is critical: it means the restore doesn't care that your LTO drive moved from &lt;code&gt;/dev/nst0&lt;/code&gt; to &lt;code&gt;/dev/nst1&lt;/code&gt;, or that your disk image was renamed, or that your rclone remote was reconfigured. The daemon resolves the current physical location of any UUID by scanning &lt;code&gt;tapes&lt;/code&gt; table entries against live hardware on startup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;device_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backend_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;tapes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tape_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'a1b2c3...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Returns: /dev/nst0, local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Seek to the Exact Byte Offset
&lt;/h3&gt;

&lt;p&gt;Once the device is located, HuskHoard seeks directly to &lt;code&gt;tape_offset&lt;/code&gt;. For disk images, this is a standard &lt;code&gt;lseek()&lt;/code&gt;. For physical LTO tape, this uses hardware SCSI positioning commands to reach the correct block without reading through intervening data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Volume [a1b2c3...]
 ┌─────────────────────────────────────────────────────────────────────┐
 │ Header │ v1 payload │ other files... │ v2 payload │ v3 payload │... │
 │  4KB   │  offset:   │               │  offset:   │  offset:   │    │
 │        │  4,096     │               │  52,432,896│ 104,857,600│    │
 └─────────────────────────────────────────────────────────────────────┘
                                             ↑
                               seek here for version 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No other version's data is read. No other file's data is read. The restore is surgical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Decompress and Verify
&lt;/h3&gt;

&lt;p&gt;The payload at that offset is a Zstd-compressed stream. HuskHoard decompresses it and feeds the output bytes through a BLAKE3 hasher simultaneously. When decompression completes, the computed hash is compared against the &lt;code&gt;blake3_hash&lt;/code&gt; stored in the catalog at archive time. If they don't match, the restore is aborted and the corrupted block is flagged.&lt;/p&gt;

&lt;p&gt;This means every version restore is automatically integrity-verified. You're not just getting the bytes that &lt;em&gt;were&lt;/em&gt; there — you're getting cryptographic confirmation that those bytes are exactly what was archived.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Atomic Placement
&lt;/h3&gt;

&lt;p&gt;The restored bytes are written to a temporary file, ownership and permissions are set from the catalog's &lt;code&gt;uid&lt;/code&gt;, &lt;code&gt;gid&lt;/code&gt;, and &lt;code&gt;posix_mode&lt;/code&gt; columns, and then the file is atomically renamed into place at the target path. The previous stub (or the current version, if one existed) is replaced atomically — no partially-written state is ever visible to the OS.&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;object_frames&lt;/code&gt; Table: Partial-Content Rollback Without Full Recall
&lt;/h2&gt;

&lt;p&gt;For large files, there's an additional optimization. When HuskHoard archives a file, it writes the Zstd stream in discrete frames (typically 16 MB each) and records the mapping of uncompressed byte ranges to their compressed positions in the &lt;code&gt;object_frames&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;object_frames&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;           &lt;span class="nb"&gt;TEXT&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt;             &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;uncompressed_offset&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compressed_offset&lt;/span&gt;   &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compressed_size&lt;/span&gt;     &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is primarily what powers the StreamGate HTTP gateway (letting Plex or VLC seek into a 100 GB video on tape without full recall), but it also means that targeted restores of large files don't require reading the entire payload from media. If you need to verify the state of bytes &lt;code&gt;4,000,000,000&lt;/code&gt; through &lt;code&gt;4,016,000,000&lt;/code&gt; of a 2 TB database dump at version 5, HuskHoard can seek directly to the relevant frame, decompress only that 16 MB chunk, and verify just the portion you need.&lt;/p&gt;

&lt;p&gt;This is fundamentally different from snapshot-based PITR in backup tools, which typically require mounting an entire snapshot volume before you can access any individual file within it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Strategy 1: File Versioning for Granular DR
&lt;/h3&gt;

&lt;p&gt;The most common use case. You have a home lab or a small team environment where files change regularly — code, configs, media project files, database exports. You want the ability to say "give me that file as it was two weeks ago."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;: Point HuskHoard's hot tier at your working directories. Set a &lt;code&gt;max_age_days&lt;/code&gt; appropriate to your workflow (7 or 14 days is typical). HuskHoard will archive files that haven't been touched since the threshold, and re-archive modified files, building up a version history automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# husk_config.toml&lt;/span&gt;
&lt;span class="py"&gt;hot_tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/home/user/projects"&lt;/span&gt;
&lt;span class="py"&gt;primary_volumes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/mnt/archive/main.img"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;max_age_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;
&lt;span class="py"&gt;janitor_interval_secs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="py"&gt;max_versions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="c"&gt;# keep the 10 most recent versions per file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Recovery scenario&lt;/strong&gt;: Your web app's &lt;code&gt;database.conf&lt;/code&gt; got overwritten by a botched deployment script at 3 PM on a Tuesday. You want the version from Monday.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See what versions exist&lt;/span&gt;
huskhoard versions &lt;span class="nt"&gt;--file&lt;/span&gt; /home/user/projects/myapp/database.conf

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;#   v1  2026-05-20 08:14:00  blake3: 9a2f...  1.2 KB&lt;/span&gt;
&lt;span class="c"&gt;#   v2  2026-05-27 11:55:33  blake3: 4d8c...  1.3 KB   ← Monday's version&lt;/span&gt;
&lt;span class="c"&gt;#   v3  2026-06-03 15:01:44  blake3: f1a0...  1.3 KB   ← broken deployment&lt;/span&gt;

&lt;span class="c"&gt;# Restore Monday's version directly to path&lt;/span&gt;
huskhoard restore &lt;span class="nt"&gt;--file&lt;/span&gt; /home/user/projects/myapp/database.conf &lt;span class="nt"&gt;--version&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total recovery time: seconds. You don't need to find last night's backup tape, mount a snapshot, or navigate a backup UI. You query the catalog, seek to a byte offset, decompress, verify. Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategy 2: VM Backup + File Versioning for Faster DR
&lt;/h3&gt;

&lt;p&gt;This is where the versioning-vs-backup distinction pays off in a real operational context.&lt;/p&gt;

&lt;p&gt;If you're running KVM or Proxmox, your VM disk images (&lt;code&gt;.qcow2&lt;/code&gt; or raw) are just files. You can absolutely archive them with HuskHoard, and every re-archive creates a new versioned copy that you can roll back to. But a full restore of a 500 GB VM image is still going to take time, even with direct seeks to the right offset on tape.&lt;/p&gt;

&lt;p&gt;A better hybrid strategy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Back up the full VM image&lt;/strong&gt; periodically (weekly or on major changes). This lives as a versioned archive in HuskHoard — your safety net for total VM loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version the individual files that change frequently&lt;/strong&gt; and are small enough to restore in seconds: VM configs, cloud-init seeds, network configuration files, snapshot manifests, Terraform state files, Ansible inventories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Archive the full VM disk image (becomes version 1, then 2, then N)&lt;/span&gt;
&lt;span class="c"&gt;# HuskHoard handles this automatically when the Janitor runs&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; /var/lib/libvirt/images/production-web.qcow2
&lt;span class="c"&gt;# 487G production-web.qcow2&lt;/span&gt;

&lt;span class="c"&gt;# But for the config files alongside it:&lt;/span&gt;
huskhoard versions &lt;span class="nt"&gt;--file&lt;/span&gt; /etc/libvirt/qemu/production-web.xml
&lt;span class="c"&gt;#   v1  2026-04-01  blake3: ...  12 KB&lt;/span&gt;
&lt;span class="c"&gt;#   v2  2026-05-15  blake3: ...  13 KB&lt;/span&gt;
&lt;span class="c"&gt;#   v3  2026-06-10  blake3: ...  14 KB  ← current&lt;/span&gt;

&lt;span class="c"&gt;# VM XML config accidentally deleted or corrupted?&lt;/span&gt;
huskhoard restore &lt;span class="nt"&gt;--file&lt;/span&gt; /etc/libvirt/qemu/production-web.xml &lt;span class="nt"&gt;--version&lt;/span&gt; 2
&lt;span class="c"&gt;# Restored in &amp;lt; 1 second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: when something breaks, you reach for the right tool at the right granularity. Config went wrong? PITR the config file. VM guest OS corrupted itself? Restore the full image from the latest VM image archive. You're never force-multiplying a small problem into a large recovery operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DR time comparison&lt;/strong&gt; for "someone edited the wrong network bridge config":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Recovery Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Restore full VM from last night's backup&lt;/td&gt;
&lt;td&gt;45 minutes to 2 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mount last night's VM snapshot, extract file&lt;/td&gt;
&lt;td&gt;10–20 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuskHoard PITR the specific XML file&lt;/td&gt;
&lt;td&gt;&amp;lt; 5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Strategy 3: Defending Against Ransomware
&lt;/h3&gt;

&lt;p&gt;Ransomware targets active files. A ransomware event that encrypts your working data will create a wave of "modified" files that, if your archive system isn't careful, will get archived as new versions — burying the pre-encryption versions under the corrupted ones.&lt;/p&gt;

&lt;p&gt;HuskHoard's &lt;code&gt;max_versions&lt;/code&gt; config is your safety net here. As long as &lt;code&gt;max_versions&lt;/code&gt; is set high enough that the pre-infection versions haven't been pruned, you can roll back every affected file individually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# After discovering ransomware encrypted files on June 10th at 14:00&lt;/span&gt;

&lt;span class="c"&gt;# Find all files modified after that timestamp that have prior versions&lt;/span&gt;
huskhoard versions &lt;span class="nt"&gt;--file&lt;/span&gt; /data/documents/proposal.docx

&lt;span class="c"&gt;#   v1  2026-05-01 10:00:00  blake3: 3c8a...  45 KB  ← clean&lt;/span&gt;
&lt;span class="c"&gt;#   v2  2026-06-10 14:03:22  blake3: 9f1b...  45 KB  ← encrypted&lt;/span&gt;

&lt;span class="c"&gt;# Roll back to clean version&lt;/span&gt;
huskhoard restore &lt;span class="nt"&gt;--file&lt;/span&gt; /data/documents/proposal.docx &lt;span class="nt"&gt;--version&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The append-only storage model is what makes this safe. The encrypted version was appended to the volume — it didn't overwrite the clean version. Both are present, both restorable.&lt;/p&gt;

&lt;p&gt;For a large-scale ransomware event affecting thousands of files, you'd script the rollback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Roll back all files in /data/documents/ to their version 1&lt;/span&gt;
huskhoard restore &lt;span class="nt"&gt;--directory&lt;/span&gt; /data/documents/ &lt;span class="nt"&gt;--before&lt;/span&gt; &lt;span class="s2"&gt;"2026-06-10 14:00:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Pruning Old Versions
&lt;/h2&gt;

&lt;p&gt;Versions accumulate on append-only media. The &lt;code&gt;max_versions&lt;/code&gt; config key controls how many versions per file are retained in the catalog (and eligible for restore):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;max_versions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="c"&gt;# Default: keep the 10 most recent versions per file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a file exceeds &lt;code&gt;max_versions&lt;/code&gt;, the oldest version's catalog row is deleted. The underlying payload bytes on the storage volume are &lt;em&gt;not&lt;/em&gt; immediately freed — they become "dark" space that gets reclaimed the next time &lt;code&gt;huskhoard repack&lt;/code&gt; is run on that volume (the garbage collection pass).&lt;/p&gt;

&lt;p&gt;This means you always have control over the disk-vs-history tradeoff. More versions = more history = more storage consumed on the archive tier over time. For most file versioning use cases, 5–10 versions is sufficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Enterprise Vendors Charge for This
&lt;/h2&gt;

&lt;p&gt;Just for context. Veeam's granular file recovery requires their Enterprise Plus tier. Commvault's IntelliSnap point-in-time recovery is licensed per agent. Zerto's journal-based PITR (which tracks continuous block-level changes for arbitrary-timestamp recovery) starts at thousands of dollars per month for production workloads. IBM Spectrum Protect (formerly TSM) charges per terabyte of managed data.&lt;/p&gt;

&lt;p&gt;HuskHoard's PITR engine — the catalog, the version rows, the byte-offset seeks, the BLAKE3 verification — is in the open-source core, licensed AGPL v3. You can read every line of the implementation in &lt;a href="https://github.com/HuskHoard/HuskHoard/blob/main/src/database.rs" rel="noopener noreferrer"&gt;&lt;code&gt;src/database.rs&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/HuskHoard/HuskHoard/blob/main/src/daemon.rs" rel="noopener noreferrer"&gt;&lt;code&gt;src/daemon.rs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The tradeoff is that HuskHoard's versioning is event-driven (archive events) rather than continuous (block-level journal). You get snapshots at the moments the Janitor ran, not arbitrary-timestamp recovery for every write in between. For most use cases — homelab, small teams, media workflows, development environments — that's entirely sufficient. For continuous block-level CDP of a high-transaction OLTP database, you need a dedicated database tool for that layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and build&lt;/span&gt;
git clone https://github.com/huskhoard/huskhoard.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;huskhoard
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Grant capabilities (no root daemon needed)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;setcap cap_sys_admin,cap_dac_read_search+ep target/release/huskhoard

&lt;span class="c"&gt;# Format an archive volume and start the engine&lt;/span&gt;
./target/release/huskhoard format &lt;span class="nt"&gt;--tape-dev&lt;/span&gt; my_archive.img
./target/release/huskhoard daemon

&lt;span class="c"&gt;# After files are archived, list versions&lt;/span&gt;
./target/release/huskhoard versions &lt;span class="nt"&gt;--file&lt;/span&gt; /path/to/your/file

&lt;span class="c"&gt;# Roll back&lt;/span&gt;
./target/release/huskhoard restore &lt;span class="nt"&gt;--file&lt;/span&gt; /path/to/your/file &lt;span class="nt"&gt;--version&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full documentation is on &lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. The architecture series on &lt;a href="https://huskhoard.com/blog.html" rel="noopener noreferrer"&gt;HuskHoard.com/blog.html&lt;/a&gt; covers the catalog schema, file stubbing, the fanotify interceptor, and SMR drive handling in depth — good reading if you want to understand the full system before deploying it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Versioning and backup are different tools for different failure modes.&lt;/strong&gt; Use both, not one instead of the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HuskHoard's append-only storage model gives you versioning for free&lt;/strong&gt; — old payloads are never overwritten, so every archive event is inherently a new restorable version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The catalog is the version index.&lt;/strong&gt; A &lt;code&gt;(file_path, version)&lt;/code&gt; row with a &lt;code&gt;tape_uuid + tape_offset&lt;/code&gt; physical address is all you need to restore any specific version of any file in microseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling back a single file is a single seek.&lt;/strong&gt; No snapshot volumes to mount, no backup sets to restore, no staging areas. Seek to the offset, decompress, verify the BLAKE3 hash, place atomically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;object_frames&lt;/code&gt; jump table means even large files can be partially restored&lt;/strong&gt; without reading the entire payload from media.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The whole engine is open source, free under AGPL v3,&lt;/strong&gt; built in Rust, and runs in user-space on any Linux kernel 5.1+.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're evaluating backup or archiving software and PITR is on your requirements list, it's worth spinning up HuskHoard before you sign any enterprise contracts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;HuskHoard is an open source data tiering archive. Source code at &lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;github.com/HuskHoard/HuskHoard&lt;/a&gt;. Project site and architecture blog at &lt;a href="https://huskhoard.com" rel="noopener noreferrer"&gt;huskhoard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>aws</category>
      <category>rust</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why You Can't Just Mount an S3 Bucket and Start Editing</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Fri, 17 Jul 2026 07:01:45 +0000</pubDate>
      <link>https://dev.to/coldstorage/why-you-cant-just-mount-an-s3-bucket-and-start-editing-23eb</link>
      <guid>https://dev.to/coldstorage/why-you-cant-just-mount-an-s3-bucket-and-start-editing-23eb</guid>
      <description>&lt;p&gt;Everyone who's tried pointing an NLE at a mounted S3 bucket has hit the same wall. Browsing folders is slow. Scrubbing stutters or hangs. The usual fix people reach for is downloading the whole file before touching it, which defeats the entire point of using cheap, scalable object storage in the first place.&lt;/p&gt;

&lt;p&gt;The reason is simple once you see it: object storage — S3, MinIO, Ceph RGW, TrueNAS's S3-compatible gateway — isn't a &lt;a href="https://huskhoard.com/blog-post-fs.html" rel="noopener noreferrer"&gt;filesystem&lt;/a&gt;. There's no directory tree, no &lt;code&gt;stat()&lt;/code&gt;, no partial reads in the POSIX sense. Everything is a flat key with a &lt;code&gt;GET&lt;/code&gt; and a &lt;code&gt;PUT&lt;/code&gt;. Any workflow that wants to browse and edit media stored this way has to build a filesystem-shaped illusion on top of an object-shaped reality.&lt;/p&gt;

&lt;p&gt;Tools like LucidLink made that illusion feel seamless enough that people forget it's an illusion at all. But the trick isn't magic, and it isn't proprietary to any one product — it's a handful of well-understood pieces working together. Worth breaking those apart before looking at any specific implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach: mount and download
&lt;/h2&gt;

&lt;p&gt;The simplest way to make an S3 bucket look like a drive is a FUSE mount (&lt;code&gt;s3fs&lt;/code&gt;, &lt;code&gt;goofys&lt;/code&gt;, rclone mount, etc.). These translate filesystem calls into API calls in real time. That works fine for sequential access — copying a file, reading a log — but post-production workloads are not sequential. Scrubbing a timeline, seeking to a mark, or letting a codec read its index atoms means constant, unpredictable jumps around a file. Each of those can turn into a separate HTTP request under a naive FUSE translation layer, and latency stacks up fast.&lt;/p&gt;

&lt;p&gt;The common workaround is to just download the whole file before handing it to the application — effectively a cache-on-open. It works, but it reintroduces the thing object storage was supposed to let you avoid: waiting for a full copy before you can touch a frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic building block: transparent stubbing
&lt;/h2&gt;

&lt;p&gt;Before getting to how bytes get fetched, it's worth being explicit about &lt;em&gt;what the filesystem shows the application in the meantime&lt;/em&gt; — this is the part most write-ups skip, and it's the actual foundation the rest of the system sits on.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;stub&lt;/strong&gt; (also called a placeholder, or "smart" file in the OneDrive/Dropbox world) is a filesystem entry that reports correct, fully-formed metadata — file size, timestamps, permissions — without the underlying data being present locally. &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;stat&lt;/code&gt;, project panels, media browsers, and autosave routines all see what looks like a complete, correctly-sized file. Only when something actually tries to &lt;em&gt;read&lt;/em&gt; content does the system need to do anything.&lt;/p&gt;

&lt;p&gt;This is the same underlying idea as OneDrive Files On-Demand or Dropbox Smart Sync, but post-production adds a wrinkle: those consumer implementations generally hydrate a file in full on first touch. A &lt;a href="https://huskhoard.com/blog-post-stubs.html" rel="noopener noreferrer"&gt;stub&lt;/a&gt; used for editorial has to support &lt;strong&gt;partial hydration&lt;/strong&gt; — serving byte range 40MB–41MB of a 40GB file without pulling the other 39.999GB. That distinction is what separates a "smart sync" folder from something you can scrub a 4K timeline against.&lt;/p&gt;

&lt;p&gt;Two things a stubbing layer needs to get right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Correct size reporting from creation.&lt;/strong&gt; If an NLE or transcoder trusts &lt;code&gt;stat()&lt;/code&gt; size and the stub lies about it, seeks past the "real" data will behave unpredictably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read interception at the right layer.&lt;/strong&gt; Something has to sit between "application calls read()" and "bytes are actually available," decide what's missing, fetch it, and hand it back — ideally without the application knowing this happened at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second point is where implementations diverge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intercepting reads: FUSE vs. fanotify
&lt;/h2&gt;

&lt;p&gt;FUSE handles this by owning the entire filesystem — every syscall against the mount routes through a userspace daemon. It's flexible but has real overhead, since even reads that could be served straight from a local page cache round-trip through the FUSE protocol.&lt;/p&gt;

&lt;p&gt;An alternative is Linux's &lt;strong&gt;fanotify&lt;/strong&gt; API, which lets a userspace process subscribe to filesystem events — opens, accesses, and, with permission events, the ability to &lt;em&gt;block a read until the daemon signals it's safe to proceed&lt;/em&gt;. Instead of intermediating the entire filesystem, &lt;a href="https://huskhoard.com/blog-post-fa.html" rel="noopener noreferrer"&gt;fanotify&lt;/a&gt; sits over a real (or virtual) filesystem and only intervenes when it needs to: on open, or on access to a range that isn't populated yet. Once data is present, subsequent reads can be served by the kernel's normal page cache with no daemon involvement.&lt;/p&gt;

&lt;p&gt;Practically, that means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Application opens a file → daemon sees the event, resolves it against the object store, ensures a stub exists locally.&lt;/li&gt;
&lt;li&gt;Application reads offset X → if that range isn't hydrated, the daemon fetches exactly that byte range from the backend (an S3 &lt;code&gt;GET&lt;/code&gt; with a &lt;code&gt;Range&lt;/code&gt; header, or equivalent) and writes it into the local stub before releasing the read.&lt;/li&gt;
&lt;li&gt;Application reads an already-hydrated offset → served locally, no network round trip.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is closer to how a block device with a remote backing store behaves than a download manager. It also means media stays in its native container/codec — no re-chunking or transcoding is required to make this work, which matters if you don't want to convert an existing archive to participate.&lt;/p&gt;

&lt;p&gt;The trade-off is real: fanotify permission events typically need elevated privileges, and the very first touch of a cold region always costs a network round trip no matter how the interception is done. Nothing eliminates the physics of "the bytes have to come from somewhere the first time."&lt;/p&gt;

&lt;h2&gt;
  
  
  The other bottleneck: just listing files
&lt;/h2&gt;

&lt;p&gt;Byte-range fetching solves reading data. It doesn't solve &lt;em&gt;finding out what data exists&lt;/em&gt;. Object stores don't have real directories — &lt;code&gt;list-objects&lt;/code&gt; calls against a prefix are how "folders" get faked, and at scale (media libraries commonly run into the millions of objects) those list calls get slow, especially with deep, nested project structures. This is the part of cloud-mount tools that often feels worse than the streaming itself: opening a folder shouldn't take longer than opening the file inside it.&lt;/p&gt;

&lt;p&gt;The usual fix is a &lt;strong&gt;local metadata catalog&lt;/strong&gt; — commonly something as simple as SQLite — that mirrors the object store's namespace and metadata (paths, sizes, timestamps, hydration state) locally. Directory listings, &lt;code&gt;stat()&lt;/code&gt; calls, and search then hit the local database instead of the network. The catalog needs to stay in sync with the backend (via polling, event notifications, or explicit indexing passes), but once it does, browsing a library feels local regardless of library size, because it &lt;em&gt;is&lt;/em&gt; local — only the file contents are remote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the layers together
&lt;/h2&gt;

&lt;p&gt;Strip away branding and this pattern generally has three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A local metadata catalog&lt;/strong&gt; — fast namespace and &lt;code&gt;stat()&lt;/code&gt; info, decoupled from the backend's list performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent, partially-hydratable stub files&lt;/strong&gt; — so applications see a complete, correctly-sized tree immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demand-driven byte-range fetching&lt;/strong&gt; — via fanotify, FUSE, or another interception method — that pulls only the ranges actually requested, on the fly, in native format.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is exotic on its own. The interesting engineering is in making them consistent with each other under real editorial load: concurrent readers, scrubbing, proxy generation, and multiple users hitting the same objects from different machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where HuskHoard fits
&lt;/h2&gt;

&lt;p&gt;HuskHoard is a project that implements this pattern, aimed at people who want it running against storage they already control rather than a hosted service. It uses a local SQLite catalog for directory/metadata performance, fanotify-based interception for read handling, and treats the backend as swappable — S3, MinIO, Ceph RGW, or TrueNAS's S3 gateway all work the same way from the client side, since the client only needs an S3-compatible API to talk to.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not only cloud
&lt;/h2&gt;

&lt;p&gt;The three-layer pattern above doesn't actually require the slow, non-randomly-addressable backend to be a network API. It applies just as well when the backend is a local device with the same basic problem: sequential, high-latency access that punishes naive random reads.&lt;/p&gt;

&lt;p&gt;LTO tape is the obvious case — real seek latency, strictly sequential media, no benefit from re-reading small ranges the way a disk cache would help. SMR (shingled) hard drives have their own version of it: random writes into an already-shingled zone are drastically slower than sequential writes, because the drive has to read-modify-rewrite an entire shingle band. Both are cheap-per-terabyte storage that behaves badly under an editorial access pattern, which is exactly the problem the catalog/stub/interceptor stack was built to solve in the first place.&lt;/p&gt;

&lt;p&gt;HuskHoard runs that same stack against local media directly, rather than only against S3-style APIs:&lt;/p&gt;

&lt;p&gt;A native SCSI &lt;a href="https://huskhoard.com/blog-post-lto.html" rel="noopener noreferrer"&gt;tape&lt;/a&gt; driver handles &lt;a href="https://huskhoard.com/blog-post-lto.html" rel="noopener noreferrer"&gt;LTO-5&lt;/a&gt; through LTO-9, tracking filemark positions and 256KB block alignment so the catalog can seek a file to within seconds instead of doing a linear tape rewind (and avoiding "shoe-shining" — the repeated back-and-forth tape motion that happens when a drive is fed data slower than its streaming speed).&lt;br&gt;
SMR drives get a strict log-structured write path, so data is only ever appended sequentially and nothing writes back into an already-written shingle zone.&lt;br&gt;
A local HTTP gateway (StreamGate) exposes the same indexed byte-range seeking whether the file lives on tape, an &lt;a href="https://huskhoard.com/blog-post-smr.html" rel="noopener noreferrer"&gt;SMR&lt;/a&gt; archive drive, or S3 — so a player like mpv, ffmpeg, Plex, or Jellyfin can scrub into a multi-terabyte file on a tape shelf the same way it would scrub a file on cloud storage, without staging the whole thing to disk first.&lt;/p&gt;

&lt;p&gt;That's the actual differentiator worth calling out: this isn't "S3 but with a nicer client." The stubbing-and-intercept model isn't tied to object storage as a concept — it works anywhere data can be indexed and fetched by range, which turns out to include a tape autoloader as much as it includes a cloud bucket. It's the LucidLink-style instant-access experience pointed at whatever's actually holding the cold data, network-attached or not.&lt;/p&gt;

&lt;p&gt;It's not doing anything conceptually different from the LucidLink-style tools people on this forum already know — the value proposition is that it's self-hosted against infrastructure you own, rather than a subscription tied to a vendor's storage backend. Worth a look if you're already running S3-compatible storage in-house and want the streaming-editorial workflow without adding a hosted middleman&lt;/p&gt;

&lt;p&gt;GitHub.com/huskhoard/huskhoard&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>cloud</category>
      <category>performance</category>
    </item>
    <item>
      <title>Tape Has Come Back From the Dead (It Was Never Dead)</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Tue, 14 Jul 2026 06:44:37 +0000</pubDate>
      <link>https://dev.to/coldstorage/tape-has-come-back-from-the-dead-it-was-never-dead-3gbg</link>
      <guid>https://dev.to/coldstorage/tape-has-come-back-from-the-dead-it-was-never-dead-3gbg</guid>
      <description>&lt;p&gt;Every few years someone declares tape dead. Earlier this year it was &lt;a href="https://www.tomshardware.com/pc-components/storage/months-after-elon-musks-doge-crusade-to-wipe-it-out-lto-tape-storage-is-bigger-than-ever-a-record-176-5-exabytes-shipped-in-2024-the-fourth-consecutive-year-of-growth" rel="noopener noreferrer"&gt;DOGE staffers reportedly mocking the Library of Congress for still using tape&lt;/a&gt;. Before that it was the cloud. Before that it was cheap spinning disk. Tape has been "dying" for roughly thirty years now.&lt;/p&gt;

&lt;p&gt;It isn't dying. The numbers say the opposite, and the reason most people miss this is that they're still picturing tape the way it worked in 2005: write a reel, label it by hand, put it on a shelf, forget what's on it. That workflow &lt;em&gt;is&lt;/em&gt; dying. The medium underneath it is having its best decade ever — but only if you stop treating it like a glorified USB stick and start treating it like what it actually is: a storage tier that needs the same indexing, metadata, and search infrastructure you'd expect from any modern filesystem.&lt;/p&gt;

&lt;p&gt;This post covers three things: why the "tape is dead" narrative is wrong by the numbers, why the common ways people &lt;em&gt;do&lt;/em&gt; use tape (pure backup target, raw LTFS) leave most of its value on the table, and what a real solution looks like, a global namespace, a persistent catalog, and (looking forward a bit) AI generated descriptive metadata that makes a multi-petabyte cold archive as searchable as your local filesystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers don't lie
&lt;/h2&gt;

&lt;p&gt;In 2024, the LTO Program — the joint body run by HPE, IBM, and Quantum that governs the Linear Tape-Open standard — shipped a record 176.5 exabytes of compressed tape capacity. That's the fourth consecutive year of growth, and a 15.4% jump over 2023.&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%2Fny8akfp6n7ple44ho8k6.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%2Fny8akfp6n7ple44ho8k6.png" alt="tape growth" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A couple of things stand out in that chart. First, the 2020 dip is the pandemic — new data center buildouts paused and tape shipments fell 8% before snapping back with 40% growth in 2021. Second, the climb since then hasn't slowed down at all. The LTO Program's own attribution is blunt about why: enterprises adopting AI/ML workloads are generating enormous volumes of unstructured data, and tape is where a meaningful chunk of it ends up once it goes cold.&lt;/p&gt;

&lt;p&gt;To put 176.5 exabytes in perspective against the rest of the storage industry: in early 2025 alone, hard drive vendors shipped roughly 361 exabytes in a single quarter, putting annualized HDD shipments somewhere north of 1,400 exabytes. So tape isn't winning a head-to-head capacity war against disk — it was never going to, and that's not the point. &lt;a href="https://huskhoard.com/blog-post-lto.html" rel="noopener noreferrer"&gt;Tape&lt;/a&gt; occupies a specific niche (cold, durable, offline, cheap) inside a much larger storage market that IDC pegs as growing toward &lt;a href="https://www.lto.org/2023/03/help-me-ronna/" rel="noopener noreferrer"&gt;hundreds of zettabytes of new data created annually&lt;/a&gt; by the back half of this decade. The interesting story isn't "tape vs. &lt;a href="https://huskhoard.com/blog-post-smr.html" rel="noopener noreferrer"&gt;disk&lt;/a&gt;." It's that the cold tier of that exploding datasphere is, by a wide margin, choosing tape — and the market backs that up too: analysts size the global tape storage market at somewhere around $5–6 billion in 2026, &lt;a href="https://www.persistencemarketresearch.com/market-research/tape-storage-market.asp" rel="noopener noreferrer"&gt;growing at a 7–8% CAGR&lt;/a&gt; through the early 2030s.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why now, specifically
&lt;/h3&gt;

&lt;p&gt;A few forces are converging at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HDD prices have gone the wrong way.&lt;/strong&gt; AI demand for nearline drives has pushed enterprise HDD prices up — some consumer-facing drives are reportedly &lt;a href="https://www.tomshardware.com/pc-components/storage/months-after-elon-musks-doge-crusade-to-wipe-it-out-lto-tape-storage-is-bigger-than-ever-a-record-176-5-exabytes-shipped-in-2024-the-fourth-consecutive-year-of-growth" rel="noopener noreferrer"&gt;up an average of 46% since last September&lt;/a&gt;. For the first time in a decade, the "just buy more disk" answer is getting noticeably more expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ransomware made the air gap matter again.&lt;/strong&gt; Roughly 90% of organizations worldwide have experienced a ransomware attack, by IDC's count, and a tape sitting in a drawer with no network or USB path is fundamentally un-attackable in a way that a "cold" but still-attached HDD is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI/ML training and inference data is enormous and mostly cold.&lt;/strong&gt; You don't need fast random access to last year's training corpus. You need it to exist cheaply, durably, and verifiably for a long time — which is exactly tape's value proposition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hypothetical vendor spin. It's the LTO Program's own technology providers and IDC analysts saying it on the record, and the shipment numbers back it up year over year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual cost case, including the part everyone leaves out
&lt;/h2&gt;

&lt;p&gt;Here's a comparison most articles on this topic don't make: tape vs. HDD vs. SSD, &lt;em&gt;and&lt;/em&gt; tape vs. cloud cold storage — because that last one is where the real surprise is.&lt;/p&gt;

&lt;p&gt;At 2026 retail pricing, an 18TB-native LTO-9 cartridge works out to roughly $0.005/GB. A 20TB enterprise HDD lands around $0.030/GB. A 4TB NAS SSD is closer to $0.075/GB. All three of those are &lt;strong&gt;one-time&lt;/strong&gt; costs — you pay once and the bytes are yours.&lt;/p&gt;

&lt;p&gt;Cloud cold storage looks irresistibly cheap by comparison on a per-month basis. AWS S3 Glacier Deep Archive, for instance, is published at $0.00099/GB-month — a fraction of a cent. But "per month" is the catch: that's a recurring charge for as long as the data exists, not a purchase.&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%2F7e8zx5z8s5d9xe4ek9d3.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%2F7e8zx5z8s5d9xe4ek9d3.png" alt="cost graph" width="799" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the math out and the crossover points are almost comically close. Cloud cold storage costs more, cumulatively, than an LTO-9 cartridge's &lt;em&gt;entire one-time purchase price&lt;/em&gt; after about five months of retention. It passes the cost of an equivalent amount of enterprise HDD storage at around two and a half years. And this is before counting retrieval fees, request fees, or egress — Glacier Deep Archive in particular charges per-GB to get your own data back out, on top of a mandatory 180-day minimum storage commitment. For data you're keeping for 5, 10, or 30 years (LTO media carries a 30-year archival rating), the cloud's "low monthly price" framing quietly becomes the most expensive option on the table.&lt;/p&gt;

&lt;p&gt;This isn't an argument against cloud storage in general — it's excellent for hot and warm data with unpredictable access patterns. It's specifically an argument that "cold archive" and "cloud" are not synonyms, and that the economics tilt hard toward owned, offline media the longer your retention window gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where most tape setups go wrong
&lt;/h2&gt;

&lt;p&gt;So tape is cheap, durable, and having a renaissance. Given that, why does it still have a reputation as something only enterprise IT departments bother with?&lt;/p&gt;

&lt;p&gt;Because the tooling around it never grew up. Walk into most home labs or small shops running tape and you'll find a workflow that looks like this: write data to a cartridge, label it by hand with a Sharpie, put it on a shelf. Need a file back? Find the right tape (maybe), restore the &lt;em&gt;entire&lt;/em&gt; archive set, then go looking for your file inside it.&lt;/p&gt;

&lt;p&gt;Two specific approaches dominate, and both have a structural weakness that doesn't show up until you're a year or two into using them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tape as a pure &lt;a href="https://huskhoard.com/blog-post-archive.html" rel="noopener noreferrer"&gt;backup&lt;/a&gt; target.&lt;/strong&gt; This is the "3-2-1 rule" use case — tape as your offsite, air-gapped third copy. It's a legitimate and important use of tape, but it's also where tape's reputation problem comes from: a backup set is something you restore &lt;em&gt;wholesale&lt;/em&gt;, in a disaster, not something you browse day to day. If tape is &lt;em&gt;only&lt;/em&gt; ever your backup target, you've correctly captured its durability and its air-gap security, but you've left its cost-per-GB advantage completely untapped for anything except disaster recovery. You're paying for the cheapest storage medium that exists and using it like an expensive one you're afraid to touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LTFS as a primary archive.&lt;/strong&gt; &lt;a href="https://www.lto.org/" rel="noopener noreferrer"&gt;LTFS (Linear Tape File System)&lt;/a&gt; was genuinely useful when it landed — for the first time, you could mount a tape cartridge and have it behave like a USB drive, no proprietary restore software required. For an interchange format between studios or broadcast houses, it's still great. But LTFS was designed for a "write a reel, hand it to someone, they mount it" workflow. It is a &lt;em&gt;transport&lt;/em&gt; format, not a living archive, and the cracks show fast once you try to use it as your primary cold tier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's no &lt;a href="https://huskhoard.com/blog-post-cat.html" rel="noopener noreferrer"&gt;catalog&lt;/a&gt;. You cannot search the contents of an ejected tape without physically mounting it first.&lt;/li&gt;
&lt;li&gt;Every cartridge is its own isolated volume. A 50-tape archive is fifty disconnected &lt;a href="https://huskhoard.com/blog-post-fs.html" rel="noopener noreferrer"&gt;filesystems&lt;/a&gt;, not one coherent library.&lt;/li&gt;
&lt;li&gt;Finding a file means knowing — or guessing — which physical cartridge it lives on.&lt;/li&gt;
&lt;li&gt;There's no demand-loading. Applications can't transparently request a file; a human has to manually restore it first.&lt;/li&gt;
&lt;li&gt;Metadata lives &lt;em&gt;only&lt;/em&gt; on the tape itself. Lose or damage the cartridge, and you lose the index along with the data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both patterns share the same root cause: there's no persistent, off-media index that knows what's on your tapes. Without that, tape stays stuck as either a write-only insurance policy or a manual, file-by-file restore chore — and most people understandably give up and just buy more disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: a global namespace and a catalog
&lt;/h2&gt;

&lt;p&gt;The fix looks less like "better tape software" and more like "treat your tape library the way a search engine treats the web" — index everything centrally, keep the index separate from the thing it describes, and let the physical location of any given object become an implementation detail.&lt;/p&gt;

&lt;p&gt;Concretely, that means two things working together: a &lt;strong&gt;global namespace&lt;/strong&gt; (your entire collection of tapes presents as one coherent filesystem, not fifty islands) and a &lt;strong&gt;catalog&lt;/strong&gt; (a persistent, queryable index of every object you've ever archived, regardless of whether the media holding it is currently online).&lt;/p&gt;

&lt;p&gt;In a system like this, every archive operation writes a row into a small database — something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt;         &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;volume_uuid&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- which cartridge, identified by UUID, not /dev/nst0&lt;/span&gt;
    &lt;span class="n"&gt;volume_offset&lt;/span&gt;   &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- exact byte position on that volume&lt;/span&gt;
    &lt;span class="n"&gt;payload_size&lt;/span&gt;    &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blake3_hash&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;archived_at&lt;/span&gt;     &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;custom_metadata&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;             &lt;span class="c1"&gt;-- more on this below&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design choices here matter more than they look like they do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identify volumes by UUID, not device path.&lt;/strong&gt; &lt;code&gt;/dev/nst0&lt;/code&gt; changes when you move a drive to a different port. A UUID burned into the volume's header at format time doesn't. This is what lets a catalog survive hardware shuffling without breaking every restore path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version instead of overwrite.&lt;/strong&gt; Tape is sequential and append-only — you literally cannot punch a hole in the middle of a cartridge to update a file in place. Treat that as a feature: every re-archive of a file becomes a new row with an incremented version, which gives you point-in-time rollback essentially for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the volumes self-describing, independent of the catalog.&lt;/strong&gt; If every object is preceded by a small header containing its own path, hash, and &lt;a href="https://huskhoard.com/blog-post-tag.html" rel="noopener noreferrer"&gt;metadata&lt;/a&gt;, the catalog becomes an &lt;em&gt;index&lt;/em&gt;, not a &lt;em&gt;dependency&lt;/em&gt;. Lose the database — drive failure, &lt;code&gt;rm -rf&lt;/code&gt;, whatever — and you can rebuild it by scanning the raw tape for header magic bytes. This is the property that makes the difference between "we lost our archive" and "we lost an afternoon re-indexing it."&lt;/p&gt;

&lt;p&gt;With that in place, search stops requiring a mounted tape drive at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;archive find &lt;span class="s2"&gt;"project_alpha_final.mov"&lt;/span&gt;
&lt;span class="go"&gt;
Found 1 result:
  /archive/video/2024/project_alpha/project_alpha_final.mov
  Volume: 83ad72b7 | Size: 47.2 GB | Written: 2024-11-14
  Status: OFFLINE — cartridge not currently mounted
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a full answer instantly, with zero tape movement, even though the cartridge is sitting on a shelf in a box somewhere. That's the part LTFS structurally cannot do — its index lives only on the medium it's describing.&lt;/p&gt;

&lt;p&gt;The other half of "global namespace" is transparent access: instead of a restore being a separate job a human has to remember to run, the filesystem layer can intercept an open() call (on Linux, &lt;code&gt;fanotify&lt;/code&gt; is the relevant kernel interface) for a file that's currently offline, pause the requesting process, and prompt for the right cartridge by UUID. The application doesn't know a tape was ever involved — from its point of view, the file was just slow to open. This is conceptually identical to enterprise Hierarchical Storage Management (HSM), minus the enterprise price tag and the dedicated hardware requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking it further: AI-generated descriptions as searchable metadata
&lt;/h2&gt;

&lt;p&gt;A catalog that indexes paths, sizes, hashes, and timestamps solves "where is this file." It doesn't solve "what's actually in this file" — and for a multi-petabyte cold archive accumulated over years, that second question is often the one you actually have.&lt;/p&gt;

&lt;p&gt;POSIX extended attributes (xattrs) are an underused piece of infrastructure here. Linux, macOS, and most Unix filesystems let you attach arbitrary key-value metadata directly to a file's inode, independent of its contents — &lt;code&gt;user.camera.operator&lt;/code&gt;, &lt;code&gt;user.project.codename&lt;/code&gt;, whatever your workflow needs. If your archiving pipeline already scrapes xattrs at write time (which it should, since editorial and scientific tools frequently rely on them), then attaching one more attribute costs nothing architecturally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conceptual sketch — generate a description, attach it as an xattr,
# let the existing archive pipeline pick it up like any other tag.
&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vision_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# e.g. a multimodal model
&lt;/span&gt;&lt;span class="n"&gt;xattr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user.ai.description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;xattr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user.ai.tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it — no new pipeline, no separate database to keep in sync. Whatever process already pulls xattrs into the catalog's &lt;code&gt;custom_metadata&lt;/code&gt; column at archive time picks up the AI-generated description the same way it picks up a manually-set tag. A vision-language model can describe what's in a video frame; an ASR model can transcribe spoken audio into the same field; an embedding model can index the description for semantic search instead of just exact-match. None of this requires touching the underlying archive format — it's additive metadata riding along on a mechanism the system already needs for ordinary tags.&lt;/p&gt;

&lt;p&gt;The payoff is a search experience that goes well beyond filenames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;archive search &lt;span class="nt"&gt;--semantic&lt;/span&gt; &lt;span class="s2"&gt;"drone footage of a mountain, no people visible"&lt;/span&gt;
&lt;span class="go"&gt;
Found 3 results across 2 volumes (0 tapes mounted):
  /archive/footage/2023/alps_b-roll_07.mov   (Volume a1f9, OFFLINE)
  /archive/footage/2024/denali_aerial_02.mov (Volume 83ad, OFFLINE)
  /archive/footage/2024/denali_aerial_04.mov (Volume 83ad, OFFLINE)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For media production, scientific archives, surveillance footage, or just a chaotic personal NAS, that's the difference between "I know it's in here somewhere" and actually finding it. And critically, the search itself never touches a tape drive — it's a query against the hot, SSD-backed catalog database. The tape only spins up once you actually want the bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;None of these pieces are individually exotic — xattrs have existed since the early 2000s, SQLite is older than most of us, and &lt;a href="https://huskhoard.com/blog-post-fa.html" rel="noopener noreferrer"&gt;fanotify&lt;/a&gt; has been in the kernel for over a decade. What changes the picture is using them together, deliberately, instead of treating tape as an isolated dumb-storage device that occasionally gets a file copied onto it.&lt;/p&gt;

&lt;p&gt;The pattern is: write data to the cheapest durable medium that exists, index it centrally and redundantly (catalog plus self-describing volumes), make retrieval transparent instead of a manual restore job, and use cheap, off-the-shelf AI to make the contents — not just the filenames — searchable. Tape's economics have always been good. What's been missing is software that takes that seriously enough to build real infrastructure around it instead of a Sharpie and a shelf.&lt;/p&gt;

&lt;p&gt;You can find most of these features in an open-source, single-binary archive system in Rust called &lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;HuskHoard&lt;/a&gt; — and most of the concrete examples above (the catalog schema, the demand-load mechanism, the xattr capture pipeline) come directly out of that project. It's AGPL v3. Other catalogs include &lt;a href="https://open-metadata.org/" rel="noopener noreferrer"&gt;open metadata&lt;/a&gt; and &lt;a href="https://github.com/amundsen-io/amundsen" rel="noopener noreferrer"&gt;Amundsen&lt;/a&gt; Tape is still relevant in 2026 if we apply the tools we have available in 2026 not those that were used in 2006.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: LTO Program Technology Provider Companies (HPE, IBM, Quantum) annual media shipment reports, 2020–2025; IDC Global DataSphere research; AWS S3 published pricing (verified 2026); Tom's Hardware, Blocks &amp;amp; Files, and TechTarget reporting on tape and HDD shipment trends. Cost figures reflect 2026 retail media pricing and exclude drive amortization, power, and (for cloud) retrieval/egress fees.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>storage</category>
      <category>archive</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
    <item>
      <title>Why Is Only 40% of Your Data Compressed? The Tarball Trap and How to Escape It</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Mon, 06 Jul 2026 07:06:25 +0000</pubDate>
      <link>https://dev.to/coldstorage/why-is-only-40-of-your-data-compressed-the-tarball-trap-and-how-to-escape-it-2004</link>
      <guid>https://dev.to/coldstorage/why-is-only-40-of-your-data-compressed-the-tarball-trap-and-how-to-escape-it-2004</guid>
      <description>&lt;h2&gt;
  
  
  The Uncompressed Elephant in the Room
&lt;/h2&gt;

&lt;p&gt;Open a shell on your primary storage array. Run &lt;code&gt;du -sh&lt;/code&gt; on the directories nobody has touched since 2022. Now look at your cloud bill. There's a good chance you're paying full price to store data that could be sitting in less than half the space.&lt;/p&gt;

&lt;p&gt;Industry analysts have been saying the same thing for years: somewhere around 80% of enterprise data is unstructured — logs, CSVs, old project exports, documents, media, backups of backups. And unstructured data is exactly the kind of data that modern compression algorithms are good at. Zstd routinely gets 2:1 to 3:1 on typical unstructured payloads, which means 50-66% of that storage footprint doesn't need to exist.&lt;/p&gt;

&lt;p&gt;So here's the question worth sitting with: if the algorithms are this good, and the financial case is this obvious, why does so much cold data sit completely uncompressed on expensive primary storage?&lt;/p&gt;

&lt;p&gt;The answer isn't ignorance. It's fear — a very rational fear, rooted in how compression tools actually behave once you use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Don't Compress: The Tarball Trap
&lt;/h2&gt;

&lt;p&gt;Everyone has run &lt;code&gt;tar czf archive.tar.gz project/&lt;/code&gt; at some point, watched a directory of ten thousand files collapse into a single blob, and felt the small dread of knowing that directory is now effectively gone.&lt;/p&gt;

&lt;p&gt;This is the core usability problem with traditional compression:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loss of visibility.&lt;/strong&gt; Once files are inside a tarball or zip, they vanish from the filesystem's view. No more &lt;code&gt;find&lt;/code&gt;, no more &lt;code&gt;grep -r&lt;/code&gt;, no more casually browsing a folder in your file manager. The files still exist, technically, but they're locked behind an opaque container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "I might need that" price.&lt;/strong&gt; Someone eventually needs one 5MB config file out of that archive. To get it, they have to pull down and decompress the entire 100GB tarball — CPU time, disk I/O, and a coffee break, just to retrieve a file a fraction of a percent the size of the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Faced with that trade-off, most teams make the rational choice: leave it uncompressed. Storage is "cheap enough," and a working filesystem beats a compressed black box you're afraid to touch. We don't skip compression because we don't understand its value — we skip it because traditional tooling makes accessibility and compression mutually exclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Financial Cost of That Fear
&lt;/h2&gt;

&lt;p&gt;Choosing not to compress isn't a one-time decision that costs you some disk space. It's a decision that gets multiplied across your entire infrastructure stack, every day, forever.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backups.&lt;/strong&gt; Every backup job now moves and stores the full uncompressed volume — 100TB instead of 40TB, on every cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replication.&lt;/strong&gt; Cloud egress fees and inter-site bandwidth are spent transmitting bytes that were mostly redundant text or empty padding to begin with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Power and cooling.&lt;/strong&gt; Uncompressed data means more spinning drives, drawing wattage around the clock, to hold data nobody has read in three years.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these costs show up as a single line item labeled "we didn't compress our archive." They show up quietly, spread across every budget line that touches storage — which is exactly why they're so easy to ignore and so expensive to sustain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Compression That Doesn't Change the User Experience
&lt;/h2&gt;

&lt;p&gt;The real fix isn't a better zip format. It's removing the trade-off entirely: what if data could be compressed at rest, but still look and behave like a normal, uncompressed file to everything above the storage layer?&lt;/p&gt;

&lt;p&gt;That's the specific problem &lt;a href="https://huskhoard.com" rel="noopener noreferrer"&gt;HuskHoard&lt;/a&gt; is built around. It's an open-source (&lt;a href="https://github.com/HuskHoard/HuskHoard/blob/main/LICENSE" rel="noopener noreferrer"&gt;AGPL v3&lt;/a&gt;), Rust-based tiering engine for Linux that automatically moves cold data off expensive NVMe/SSD storage onto disk, cloud buckets, or physical &lt;a href="https://huskhoard.com/blog-post-lto.html" rel="noopener noreferrer"&gt;LTO tape&lt;/a&gt; — while leaving a normal-looking file behind in your directory tree.&lt;/p&gt;

&lt;p&gt;The mechanism that makes this work is worth calling out, because it's not FUSE. FUSE-based transparent filesystems exist, but they route every single read through a userspace filesystem driver, which adds latency and a layer you now depend on for correctness. HuskHoard instead uses the Linux &lt;a href="https://huskhoard.com/blog-post-fa.html" rel="noopener noreferrer"&gt;&lt;code&gt;fanotify&lt;/code&gt;&lt;/a&gt; kernel API. When a process opens a file that's been archived, the kernel itself pauses that process, HuskHoard's daemon recalls the data, and the process resumes — with zero awareness that anything happened. &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, and your file manager all keep working normally, because as far as the filesystem is concerned, the file is still there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Layer: How the Catalog and Frames Actually Work
&lt;/h2&gt;

&lt;p&gt;This is the part worth understanding if you're evaluating whether this approach holds up under real workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Catalog fixes visibility.&lt;/strong&gt; Instead of hiding archived files inside a monolithic container, HuskHoard logs every file's metadata — path, version history, byte offset on physical media, &lt;a href="https://huskhoard.com/blog-post-blake.html" rel="noopener noreferrer"&gt;BLAKE3&lt;/a&gt; hash — into a SQLite database it calls the &lt;a href="https://huskhoard.com/blog-post-cat.html" rel="noopener noreferrer"&gt;Catalog&lt;/a&gt;. The filesystem keeps a lightweight stub in place of the original file, so directory listings, &lt;code&gt;find&lt;/code&gt;, and search all keep working exactly as before. You never lose sight of what you have or where it lives; the Catalog is queryable in milliseconds even across a multi-petabyte archive, without waking up a single tape drive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independent &lt;a href="https://huskhoard.com/blog-post-zstd.html" rel="noopener noreferrer"&gt;Zstd frames&lt;/a&gt; fix the "give me 5MB out of 100GB" problem.&lt;/strong&gt; A standard Zstd or gzip stream builds a single continuous compression window across the whole file — to read byte 90,000,000 you generally have to decompress everything before it. HuskHoard avoids this by packing archived data into independent 16MB Zstd frames when writing to cloud storage via &lt;code&gt;rclone&lt;/code&gt;. Each frame is a self-contained compression boundary, so decompressing frame 400 doesn't require touching frames 1 through 399. This is also what keeps cloud costs down on the write side — batching into 16MB units minimizes the number of PUT requests, which matters when providers bill per-request as well as per-byte.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://huskhoard.com/blog-post-lto.tag" rel="noopener noreferrer"&gt;TLV headers&lt;/a&gt; carry the metadata that makes recovery possible without a working database.&lt;/strong&gt; Every file HuskHoard archives is preceded by a 4,096-byte object header. The first 136 bytes hold the essentials — UUID, POSIX mode, compressed size, BLAKE3 hash. The remaining ~3,960 bytes are packed using Type-Length-Value (TLV) encoding, which stores POSIX extended attributes (xattrs) directly alongside the payload. That detail matters more than it sounds: if a parser encounters a TLV type it doesn't recognize, it just reads the length and skips it, which is what makes the format tolerant of future changes. It also means the archive is self-describing — if the Catalog database is ever lost, HuskHoard can rebuild it from scratch by scanning the tape or bucket and reading every object header back out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame indexing is what makes partial reads actually fast.&lt;/strong&gt; Combine the Catalog's byte-offset tracking with independent frame boundaries, and you get real range access instead of full-file extraction. HuskHoard's &lt;a href="https://huskhoard.com/blog-post-stream.html" rel="noopener noreferrer"&gt;&lt;code&gt;StreamGate&lt;/code&gt;&lt;/a&gt; HTTP gateway uses this to let you seek into massive files without downloading them wholesale — want to scrub through a 4K video sitting on S3 or a physical LTO tape, in Plex or &lt;code&gt;mpv&lt;/code&gt;? StreamGate resolves the byte range you're asking for, fetches only the relevant frame (an HTTP Range request for cloud, a SCSI tape seek for LTO), and decompresses just that piece. Tape gets its own physics-aware handling on top of this — writes land in 256KB SCSI-aligned blocks with filemarks, specifically to avoid "shoe-shining" the drive, and the Catalog indexes those blocks the same way it indexes cloud frames.&lt;/p&gt;

&lt;p&gt;You can see the shape of this in practice — after installing and pointing the daemon at a test volume, dropping a file into the hot tier and waiting a few seconds is enough to watch it happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Drop a file into the watched hot tier&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/urandom &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hot_tier/dummy_data.bin &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12

&lt;span class="c"&gt;# after the janitor interval elapses...&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ls&lt;/span&gt; hot_tier        &lt;span class="c"&gt;# allocated size drops to ~0, logical size unchanged&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; hot_tier         &lt;span class="c"&gt;# it's become a "husk"&lt;/span&gt;

&lt;span class="c"&gt;# open the file normally — fanotify intercepts the read and recalls it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And streaming straight off tape or cloud without ever touching local disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./target/release/huskhoard &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nt"&gt;--file-path&lt;/span&gt; /media/movies/scifi.mp4 | mpv -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing about that command looks like &lt;a href="https://huskhoard.com/blog-post-archive.html" rel="noopener noreferrer"&gt;archive&lt;/a&gt; recovery. That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have Your Cake and Compress It Too
&lt;/h2&gt;

&lt;p&gt;The underlying idea here isn't really about Zstd, or SQLite, or fanotify specifically — it's that compression shouldn't be a choice between "save money" and "keep my data usable." Those two goals only feel opposed because most tooling makes them opposed. Once compression happens at the infrastructure layer instead of as a manual, user-facing chore, the trade-off disappears.&lt;/p&gt;

&lt;p&gt;If you want to see how the frame layout and TLV headers work in more detail, the &lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;HuskHoard GitHub repo&lt;/a&gt; has the full source (it's Rust, MIT-adjacent AGPL v3, and genuinely readable), and the &lt;a href="https://huskhoard.com/blog.html" rel="noopener noreferrer"&gt;project blog&lt;/a&gt; has deeper dives into specific pieces like the Catalog and the tape format.&lt;/p&gt;

&lt;p&gt;In the meantime, it's worth an honest audit: how much of your own cold, unstructured data is sitting around uncompressed right now — not because compression wouldn't help, but because the tools made it too painful to bother?&lt;/p&gt;

</description>
      <category>storage</category>
      <category>compression</category>
      <category>rust</category>
      <category>devops</category>
    </item>
    <item>
      <title>50% of Cloud Hard Drives are Now SMR: Why Hyperscalers Love What Consumers Hate</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Tue, 30 Jun 2026 05:39:23 +0000</pubDate>
      <link>https://dev.to/coldstorage/50-of-cloud-hard-drives-are-now-smr-why-hyperscalers-love-what-consumers-hate-26kb</link>
      <guid>https://dev.to/coldstorage/50-of-cloud-hard-drives-are-now-smr-why-hyperscalers-love-what-consumers-hate-26kb</guid>
      <description>&lt;p&gt;If you hang around r/DataHoarder or home lab communities, you’ve likely heard the golden rule of buying hard drives: &lt;strong&gt;"Avoid SMR at all costs."&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The general consensus is that &lt;a href="https://huskhoard.com/blog-post-smr.html" rel="noopener noreferrer"&gt;Shingled Magnetic Recording&lt;/a&gt; (SMR) drives have abysmal write speeds, fail early, and will crash your RAID arrays. Yet, if you look at the enterprise storage market today, a massive paradox emerges. &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%2Ftrju2dw9e1jn12eyiyf9.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%2Ftrju2dw9e1jn12eyiyf9.png" alt=" " width="494" height="463"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Caption: Data from Western Digital’s Quarterly Earnings showing UltraSMR crossing 50% of nearline exabyte shipments in 2023)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The numbers don't lie. During recent earnings calls, Western Digital confirmed a massive milestone: their UltraSMR technology now makes up over &lt;strong&gt;50% of their total nearline exabyte shipments&lt;/strong&gt;. Cloud giants like Dropbox rely on SMR for over 90% of their entire storage fleet. In fact, production for high-capacity SMR drives is essentially sold out as AI and data centers buy them by the truckload. &lt;/p&gt;

&lt;p&gt;So, SMR offers higher capacity and a lower price per terabyte. Why does everyone in the consumer and small enterprise space avoid them, and more importantly, how can &lt;em&gt;you&lt;/em&gt; start using them like the cloud giants do?&lt;/p&gt;

&lt;p&gt;Let's demystify SMR technology, look at how the hyperscalers bypassed its limitations, and explore how you can leverage it in your own home lab or small enterprise using modern open-source tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root of the Stigma: Drive-Managed SMR and the Physics of the Write Head
&lt;/h2&gt;

&lt;p&gt;To understand the hate, you have to understand the physics of the technology. &lt;/p&gt;

&lt;p&gt;Hard drive manufacturers eventually hit a physical wall: &lt;strong&gt;the magnetic write head of a hard drive is physically wider than the read head.&lt;/strong&gt; You need a certain amount of magnetic energy to flip bits on the platter, meaning the write head can only be shrunk so much before it fails to work. However, the &lt;em&gt;read&lt;/em&gt; head can be made incredibly tiny. &lt;/p&gt;

&lt;p&gt;Conventional Magnetic Recording (CMR) accommodates this bulky write head by writing wide data tracks and leaving tiny gaps between them so they don't overlap. &lt;/p&gt;

&lt;p&gt;SMR, on the other hand, exploits the tiny read head. It writes a wide track, and then writes the next track partially overlapping the first—like shingles on a roof. This leaves just enough of the previous track exposed for the smaller read head to read, allowing manufacturers to squeeze up to 25% more data onto the same physical platters.&lt;/p&gt;

&lt;p&gt;The catch? &lt;strong&gt;You cannot randomly overwrite data.&lt;/strong&gt; Because the tracks overlap, modifying a single block of data requires the drive to read a massive "zone" of data into a cache, modify it, and rewrite the entire zone sequentially. &lt;/p&gt;

&lt;p&gt;The stigma started around 2020 when manufacturers silently slipped &lt;strong&gt;Drive-Managed SMR&lt;/strong&gt; into consumer NAS drives. These drives pretended to be normal CMR drives to the host operating system, attempting to manage all that complex "read-modify-write" garbage collection internally. Under heavy, random-write NAS workloads, their caches would fill up, performance would tank to dial-up speeds, and RAID controllers would assume the drives were dead and drop them from arrays.&lt;/p&gt;

&lt;p&gt;Consumers rightly felt burned. But the cloud providers took a completely different approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cloud's Secret Weapon: Host-Managed SMR, Aggressive Tiering, and Pin 3
&lt;/h2&gt;

&lt;p&gt;Hyperscalers like Dropbox and Google didn't try to force SMR drives to act like traditional CMR drives. Instead, they completely rebuilt their storage infrastructure around three core concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Host-Managed Zoned Storage
&lt;/h3&gt;

&lt;p&gt;In a Host-Managed setup, the hard drive doesn't try to hide its shingled nature. It exposes its architecture to the operating system as a &lt;strong&gt;Zoned Block Device (ZBD)&lt;/strong&gt;. The drive essentially says to the host: &lt;em&gt;"Here are my zones. You can only write to them sequentially. If you want to delete or overwrite something, you must reset the entire zone and start from the beginning."&lt;/em&gt; By shifting the responsibility from the drive's underpowered internal processor to the host machine's software, the performance penalty vanishes. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Aggressive Telemetry and Tiering
&lt;/h3&gt;

&lt;p&gt;Rewriting the file system is only half the magic. Cloud services have immense intelligence about your files and access patterns. When you upload a file, it rarely lands directly on a spinning disk; it hits a blazing-fast NVMe cache (the "hot" tier). &lt;/p&gt;

&lt;p&gt;However, the moment their algorithms determine that a file is no longer actively being used—say, a cloud backup from three months ago, or an old photo album—they aggressively tier that data. It is migrated off the expensive flash and funneled sequentially, block by tightly-packed block, into the zones of a high-capacity SMR drive (the "cold" tier). &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pin 3 Power Management (Letting the Disks Go Cold)
&lt;/h3&gt;

&lt;p&gt;Once that data is packed into an SMR drive, cloud providers take it a step further: they literally pull the plug. &lt;/p&gt;

&lt;p&gt;If you've ever "shucked" an external hard drive for your home lab, you might have run into the infamous &lt;strong&gt;"Pin 3 Power Disable" (PWDIS)&lt;/strong&gt; issue, where you had to put Kapton tape over the 3rd SATA pin just to get the drive to spin up. While consumers were taping over this pin out of frustration, hyperscalers were demanding this feature by design.&lt;/p&gt;

&lt;p&gt;The PWDIS feature allows data centers to remotely hard-reset or cut power to individual drives. When an SMR drive is full of cold data, it doesn't just sit there spinning, consuming electricity, and generating heat. It is commanded to spin down. It sits entirely dormant, acting as a high-density, rapidly accessible tape replacement. It only wakes up when an API call requests a specific dormant file—spinning up, serving the file with a few seconds of acceptable latency, and going back to sleep. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Power of Zoned.io
&lt;/h3&gt;

&lt;p&gt;If you want to dive deep into how the foundation of this works on a technical level, the ultimate resource is &lt;a href="https://zonedstorage.io/" rel="noopener noreferrer"&gt;Zoned.io&lt;/a&gt;. Backed by major storage players, it documents the Linux kernel's implementation of Zoned Block Devices. Zoned Storage is becoming critical not just for SMR hard drives, but for next-generation NVMe SSDs (ZNS) that use the exact same sequential-write paradigms to reduce write amplification and extend flash memory life.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bringing SMR to the Home Lab and Small Enterprise
&lt;/h2&gt;

&lt;p&gt;You don't need to be AWS or Dropbox to take advantage of SMR pricing and density. You just need to pay attention to your workload requirements and use software designed to treat storage sequentially.&lt;/p&gt;

&lt;p&gt;SMR is an absolute powerhouse for &lt;strong&gt;WORM (Write Once, Read Many)&lt;/strong&gt; workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Media archiving and Plex servers&lt;/li&gt;
&lt;li&gt;  Immutable backup targets&lt;/li&gt;
&lt;li&gt;  Log aggregation and long-term surveillance video storage&lt;/li&gt;
&lt;li&gt;  Data hoarding and cold storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are using ZFS, you can utilize sequential-friendly configurations, but to truly unlock the potential of Host-Managed SMR in a small enterprise or home lab, you need purpose-built archiving tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Do's and Don'ts of SMR &lt;a href="https://huskhoard.com/blog-post-archive.html" rel="noopener noreferrer"&gt;Archiving&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;SMR is an absolute powerhouse for WORM (Write Once, Read Many) workloads, but only if you follow the rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Don'ts: RAID, ZFS, and Standard File Systems
&lt;/h3&gt;

&lt;p&gt;The biggest mistake you can make is blindly handing an SMR drive over to your operating system or RAID controller.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Don't use hardware RAID or standard ZFS.&lt;/strong&gt; RAID arrays (and ZFS RAIDZ) rely on parity calculations and constant metadata updates. If an array needs to rebuild or "resilver", it blasts the drives with random, fragmented writes. The SMR cache will choke, write speeds will drop to practically zero, and the controller will drop the drive entirely.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Don't just format it to ext4 or NTFS.&lt;/strong&gt; Traditional file systems are designed for random I/O. Even if you are just sequentially copying a large video file, the file system is constantly darting its write head back and forth across the platter to update the journal, timestamps, and file allocation tables. This micro-random I/O ruins SMR performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Do's: Buffer, Batch, and Stream
&lt;/h3&gt;

&lt;p&gt;Instead of fighting the drive with random I/O, treat it exactly like a modern tape drive.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Do use an SSD staging tier.&lt;/strong&gt; Borrow the cloud provider's playbook. Buffer your incoming files on an NVMe or standard SSD first. Once you have accumulated a large batch of cold data, flush it to the SMR drive in one massive, continuous stream.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Do use Zoned-aware storage layers.&lt;/strong&gt; The Linux kernel has made massive strides here. For example, &lt;code&gt;btrfs&lt;/code&gt; now has a dedicated "Zoned Mode" explicitly designed to work with Zoned Block Devices. It forces all writes to be strictly sequential, keeping metadata organized and eliminating the read-modify-write penalty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to go deeper into SMR this is a good resource(&lt;a href="https://huskhord.com/blog-post-smr.html" rel="noopener noreferrer"&gt;Stop Murdering your SMR Drives&lt;/a&gt;), but if you want to take advantage of SMR pricing and density today, you must completely rethink your storage architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The storage industry is moving toward Zoned Storage, whether consumers like it or not. With the massive shift toward UltraSMR in the enterprise space, the era of ignoring the technology is over.&lt;/p&gt;

&lt;p&gt;It's time we stop universally hating SMR drives and start treating them like the specialized, high-capacity archiving tools they are. If you stop handing them directly to standard file systems and instead provide them with the buffered, sequential workloads they crave, they will reward you with massive capacity, incredible cost savings, and a much lower power bill.&lt;/p&gt;

&lt;p&gt;Check out the documentation at &lt;a href="https://zonedstorage.io/" rel="noopener noreferrer"&gt;Zoned.io&lt;/a&gt; to understand the kernel-level magic, and if you are looking for an out-of-the-box archiving solution that loves SMR drives, check out  AGPL archiving at &lt;a href="https://huskhord.com" rel="noopener noreferrer"&gt;Huskhord.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>linux</category>
      <category>rust</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Addressing the global namespace</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Thu, 25 Jun 2026 06:20:12 +0000</pubDate>
      <link>https://dev.to/coldstorage/addressing-the-global-namespace-1k21</link>
      <guid>https://dev.to/coldstorage/addressing-the-global-namespace-1k21</guid>
      <description>&lt;p&gt;There is a moment in every data hoarder's life — and in every small media shop's IT history — when the first archive drive fills up. You slot in a second tape, plug in another external drive, configure a new cloud bucket. Problem solved. &lt;/p&gt;

&lt;p&gt;Except now you have a different problem: &lt;strong&gt;you have no idea where anything is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not in the "I lost the file" sense. More in the "I know I have that file somewhere across these six volumes, but I don't know which one, and I'm not sure I haven't archived it twice, and that third copy might be the old version" sense. The data is there. The knowledge of where it is, and which copy is canonical, is not.&lt;/p&gt;

&lt;p&gt;This is the multi-volume namespace problem. It's been lurking in storage management since reel-to-reel tape in the 1960s, and the solutions to it span from "I have a spreadsheet" to "I have a $200,000 enterprise storage cluster." Most people end up somewhere in the uncomfortable middle.&lt;/p&gt;

&lt;p&gt;Let's look at the problem properly, put some numbers on it, and walk through what people actually do — and why each approach eventually runs out of road.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers First
&lt;/h2&gt;

&lt;p&gt;"Multiple volumes" sounds modest. It isn't, once you start measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LTO tape capacity by generation (native / 2.5:1 compressed):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation&lt;/th&gt;
&lt;th&gt;Native&lt;/th&gt;
&lt;th&gt;Compressed&lt;/th&gt;
&lt;th&gt;$/TB (cartridge only, approx.)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LTO-7&lt;/td&gt;
&lt;td&gt;6 TB&lt;/td&gt;
&lt;td&gt;15 TB&lt;/td&gt;
&lt;td&gt;~$0.02/GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LTO-8&lt;/td&gt;
&lt;td&gt;12 TB&lt;/td&gt;
&lt;td&gt;30 TB&lt;/td&gt;
&lt;td&gt;~$0.015/GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LTO-9&lt;/td&gt;
&lt;td&gt;18 TB&lt;/td&gt;
&lt;td&gt;45 TB&lt;/td&gt;
&lt;td&gt;~$0.012/GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LTO-10&lt;/td&gt;
&lt;td&gt;40 TB&lt;/td&gt;
&lt;td&gt;100 TB&lt;/td&gt;
&lt;td&gt;(2026, available now)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A serious homelab data hoarder with a modest LTO-8 drive will fill a 12 TB cartridge in 6–18 months depending on what they're collecting. A four-tape rotation is a realistic minimum for anyone who takes offline copies seriously. That's 48 TB native — multiple volumes by definition.&lt;/p&gt;

&lt;p&gt;Scale up to a small production company, a university research group, or a regional broadcaster. Now you have dozens of tapes, offline drives at an off-site location, and a cloud backend for disaster recovery. Each one a separate island of storage.&lt;/p&gt;

&lt;p&gt;Hard drive archives have the same problem. A rotating set of 14–20 TB USB drives for cold backup is cheap and practical. It's also, over five years, eight or ten drives with no shared index.&lt;/p&gt;

&lt;p&gt;The problem isn't finding the storage. The problem is that the more storage you buy, the worse your ability to reason about it gets — unless you build something to manage the namespace across all of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Namespace" Means in This Context
&lt;/h2&gt;

&lt;p&gt;A namespace is simply a unified view of your files. On a single volume, it's trivial: mount the drive, run &lt;code&gt;ls&lt;/code&gt;, see everything. The file system &lt;em&gt;is&lt;/em&gt; the namespace.&lt;/p&gt;

&lt;p&gt;The moment you have two volumes, the file system isn't enough. A file might be on either one. If both are mounted simultaneously, you have two separate trees. If one is offline, its files are invisible. If the same logical file has been archived to both volumes at different points in time, you have two physical copies with no built-in way to determine which is current.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ideal global namespace would let you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List every file you've ever archived, regardless of which physical volume holds it&lt;/li&gt;
&lt;li&gt;Know immediately which volume a file is on, and what its current version is&lt;/li&gt;
&lt;li&gt;Know that you haven't accidentally archived the same content twice on different volumes&lt;/li&gt;
&lt;li&gt;Retrieve the right version without checking six different drives one by one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single standard delivers all four of these, which is why the solutions below exist — and why each one is only a partial answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach 1: LTFS — A File System on Every Tape
&lt;/h2&gt;

&lt;p&gt;The closest thing to a proper multi-generation standard for tape namespace management is &lt;strong&gt;LTFS&lt;/strong&gt; (Linear Tape File System), adopted by the LTO Consortium in 2010 and standardized by SNIA. If you're running LTO-5 or later, you can format any cartridge as LTFS and mount it exactly like a USB drive.&lt;/p&gt;

&lt;p&gt;The way it works is elegant. LTFS divides a tape cartridge into two partitions. Partition 0 is the index — an XML document describing your directory tree, file names, timestamps, and the exact tape block where each file's data begins. Partition 1 is the data itself. When you insert an LTFS tape and run the mount command, the drive reads Partition 0 into memory, and your OS presents the tape as a browsable volume.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ltfs &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;devname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 /mnt/tape
&lt;span class="nb"&gt;ls&lt;/span&gt; /mnt/tape/projects/alpha/
&lt;span class="c"&gt;# rushes/  finals/  audio/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can drag and drop. You can &lt;code&gt;cp&lt;/code&gt;. Files are written sequentially to the end of the data partition and the index is updated. It's a genuine open standard — an LTFS tape written on Linux can be read on macOS or Windows with compatible software, with no proprietary software in the middle.&lt;/p&gt;

&lt;p&gt;For interchange and manual archiving, LTFS is excellent. If you're sending a tape to a collaborator or to a facility you've never worked with before, LTFS is the right choice. It is genuinely self-describing: the metadata travels with the media.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where LTFS Stops
&lt;/h3&gt;

&lt;p&gt;The problem is that LTFS solves the per-tape namespace and stops there. &lt;strong&gt;Each tape is its own island.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a tape is offline — sitting on a shelf — its files are invisible. There is no LTFS spanning standard that lets you query across multiple cartridges simultaneously. If you have four tapes and need to find a file from 2022, you are mounting tapes until you find it, or maintaining a separate external record of what's where.&lt;/p&gt;

&lt;p&gt;LTFS also inherits tape's fundamental constraint: files can only be appended, never overwritten. If you delete a file in the LTFS index, the data blocks on tape are simply marked unavailable — the space is not recovered until you reformat the entire cartridge. More importantly for long-lived archives: &lt;strong&gt;LTFS has no native versioning.&lt;/strong&gt; The spec doesn't define what a version is or how to track one. If you archive &lt;code&gt;final.mov&lt;/code&gt;, re-edit it, and archive it again to the same tape, the second write is a separate file entry in the same index. Version control becomes your problem.&lt;/p&gt;

&lt;p&gt;The index partition itself has a ceiling — roughly 5–10% of tape capacity — which can become a bottleneck for archives with millions of small files.&lt;/p&gt;

&lt;p&gt;For many users, these aren't dealbreakers for a single-tape workflow. But the moment you're managing a tape library, you need something sitting above LTFS to provide the cross-volume view. LTFS gives you a great building block. It doesn't give you a global namespace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach 2: The Spreadsheet (And Its Cousins)
&lt;/h2&gt;

&lt;p&gt;The most common approach in homelabs and small shops isn't LTFS or enterprise software. It's a spreadsheet. Or a text file. Or a Notion database. Or a Python script someone wrote in a weekend that generates a CSV of filenames and tape labels.&lt;/p&gt;

&lt;p&gt;This approach is completely understandable and immediately comprehensible to everyone on the team. "Tape 003 has the 2023 raw footage. Tape 007 has the 2024 project deliverables. Check the sheet."&lt;/p&gt;

&lt;p&gt;It also has a half-life of about six months.&lt;/p&gt;

&lt;p&gt;The spreadsheet tracks what you intended to put on each tape at the moment you archived it. It doesn't automatically update when files change. It doesn't track versions — when you re-archive an updated file, do you add a new row? Update the existing one? Add a note? Different people make different decisions, and over time the spreadsheet becomes an archaeological record of past intentions rather than a live index.&lt;/p&gt;

&lt;p&gt;Deduplication is purely manual. Nothing in the spreadsheet warns you that &lt;code&gt;client_deliverable_FINAL_v3.mov&lt;/code&gt; on Tape 003 and &lt;code&gt;client_deliverable_FINAL_v3_2.mov&lt;/code&gt; on Tape 009 are the same underlying content, archived twice by different team members six weeks apart.&lt;/p&gt;

&lt;p&gt;Search is grep over a CSV or a Notion filter — workable for hundreds of files, painful for tens of thousands, and broken for millions.&lt;/p&gt;

&lt;p&gt;The spreadsheet approach is a record of what someone thinks is on the tapes. The tapes themselves know what's actually on them. These two sources of truth diverge over time, and the divergence accelerates as the archive grows.&lt;/p&gt;

&lt;p&gt;Some teams move from spreadsheets to proper databases — SQLite or Postgres — and write custom tooling to maintain the index. This is a genuine improvement. The index is now queryable, consistent, and maintainable. But custom tooling requires ownership: someone has to build it, update it when formats change, and ensure it stays in sync with the physical media. Most teams don't have that person, or lose them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach 3: Enterprise HSM
&lt;/h2&gt;

&lt;p&gt;The "correct" industrial answer to this problem is a &lt;strong&gt;Hierarchical Storage Management (HSM)&lt;/strong&gt; system. HSM has existed since IBM implemented it for mainframes in 1978. The idea is straightforward: files on fast, expensive storage automatically migrate to slower, cheaper storage based on access patterns, and transparent stub files replace them so users never notice the transition.&lt;/p&gt;

&lt;p&gt;Modern commercial HSM products — IBM Spectrum Protect, HPE Data Management Framework, Quantum StorNext — provide exactly the global namespace you need. You have a single directory tree. Files that haven't been touched in 90 days silently move to tape. Open one, and the system recalls it transparently. The catalog tracks every file, every version, every volume.&lt;/p&gt;

&lt;p&gt;They work. They work very well. They're also built for organizations with dedicated storage administrators, SAN infrastructure, enterprise support contracts, and storage budgets in the low-to-mid six figures.&lt;/p&gt;

&lt;p&gt;Enterprise HSM is designed for the assumption that you have a team, a budget, and a vendor relationship. The software itself can cost more than a homelab enthusiast's entire NAS. The hardware requirements (dedicated tape libraries, Fibre Channel HBAs, redundant metadata servers) further narrow the addressable audience.&lt;/p&gt;

&lt;p&gt;For a small production company with four LTO drives and a NAS, enterprise HSM is a missile to kill a mosquito. And there is no meaningful open-source equivalent in this space — the gap between "hobbyist script" and "enterprise HSM" has historically been a cliff with nothing in the middle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach 4: Backup Software (The Wrong Tool)
&lt;/h2&gt;

&lt;p&gt;A common misconception: "I'll just use my backup software to manage my archive." Backup software — Veeam, Bacula, Duplicati, Restic, Amanda — is excellent at what it does, which is taking point-in-time snapshots and storing them in a recoverable format.&lt;/p&gt;

&lt;p&gt;It is not designed for an archive, and the distinction matters.&lt;/p&gt;

&lt;p&gt;A backup is a recovery mechanism. You restore from it when something goes wrong. The interface is: "restore system/file to state as of [date]." Backup software maintains its own catalog of backup sets, not a live representation of your file namespace.&lt;/p&gt;

&lt;p&gt;An archive is a primary store. Files live there permanently, and you access them directly. The interface should be: "open this file" — and it should just work, regardless of which volume holds the bytes.&lt;/p&gt;

&lt;p&gt;Backup software can hold your &lt;a href="https://huskhoard.com/blog-post-archive.html" rel="noopener noreferrer"&gt;archival data&lt;/a&gt;. It will not give you a unified browsable namespace across multiple tapes. It will not transparently serve a file from tape when an application requests it. It will not tell you whether a file is already archived before you try to archive it again. These are the functions a real archive system needs to provide, and backup software isn't designed to provide them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Ideal Solution Would Look Like
&lt;/h2&gt;

&lt;p&gt;At this point the shape of the problem is clear, and you can start to describe what a complete solution would need to provide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume identity that outlives device paths.&lt;/strong&gt; If your LTO drive moves from &lt;code&gt;/dev/nst0&lt;/code&gt; to &lt;code&gt;/dev/nst1&lt;/code&gt; because you rearranged your HBA, every restore should still work. Volume identity needs to be based on something burned into the media at format time — a UUID — not a device path or a human-assigned label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A &lt;a href="http://huskhoard.com/blog-post-cat.html" rel="noopener noreferrer"&gt;catalog&lt;/a&gt; that is separate from any single volume.&lt;/strong&gt; You need a central index that can answer "where is this file?" without mounting every volume in your library. The catalog holds the address (volume UUID + byte offset) for every archived file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But also: volumes that are self-describing.&lt;/strong&gt; The catalog is the fast path. But if you lose the catalog database, you should be able to reconstruct it from the physical media — because each volume should carry enough metadata to identify every object it contains. The catalog is an optimization, not a dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A namespace that is always visible, regardless of volume state.&lt;/strong&gt; Files that are on offline tapes should still appear in your OS. You should be able to see the file, read its metadata, and — when you try to open it — get a useful message about which volume to insert, not a confusing "file not found" error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://huskhoard.com/blog-post-blake.html" rel="noopener noreferrer"&gt;Hash-based&lt;/a&gt; deduplication, not path-based.&lt;/strong&gt; The system should know whether a file has already been archived by its content, not just its name. Renaming a file shouldn't trigger a redundant archive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://huskhoard.com/blog-post-version.html" rel="noopener noreferrer"&gt;Versioning&lt;/a&gt; as a first-class concept.&lt;/strong&gt; When a file changes and is re-archived, both the old and new versions should be tracked and retrievable. The system should know that version 3 is on Tape C and version 1 is on Tape A, and serve the right one by default while making rollback possible on request.&lt;/p&gt;

&lt;p&gt;None of the approaches above deliver all six of these properties. LTFS gets you two or three. Spreadsheets get you one on a good day. Enterprise HSM gets you all six, but only if you have an enterprise budget.&lt;/p&gt;




&lt;h2&gt;
  
  
  An Open-Source Answer
&lt;/h2&gt;

&lt;p&gt;This gap — between hobbyist scripts and enterprise HSM — is where a relatively new open-source project called &lt;strong&gt;&lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;HuskHoard&lt;/a&gt;&lt;/strong&gt; is trying to operate.&lt;/p&gt;

&lt;p&gt;HuskHoard is a data-tiering engine built in Rust for Linux. It uses the &lt;code&gt;fanotify&lt;/code&gt; kernel API to transparently stub cold files — replacing them with zero-byte placeholders while keeping them visible in your file system — and automatically recalls the data from the appropriate volume when an application opens the file. It supports LTO tape drives natively (via the SCSI tape driver), flat image files, external drives, and cloud backends via &lt;code&gt;rclone&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The catalog is a SQLite database that tracks every archived file by its content hash, its version history, and its exact byte offset on a specific volume UUID — not a device path. Plug a drive into a different port, and the catalog heals the mapping automatically via a rescan. Volumes carry their own self-describing headers, so the catalog can be fully rebuilt from raw media if it's ever lost.&lt;/p&gt;

&lt;p&gt;For multi-volume management specifically: when a needed volume is offline, HuskHoard reports the volume's UUID and the human-readable &lt;code&gt;location_hint&lt;/code&gt; you've assigned it ("Shelf A, Slot 2 — LTO-9 Vol 001"), then waits for you to insert it. It won't re-archive a file that's already in the catalog with a matching content hash, regardless of which volume holds the original. Each volume is formatted once with a UUID, and that identity persists for the life of the media.&lt;/p&gt;

&lt;p&gt;It's not enterprise HSM. The robotic tape library automation, the Fibre Channel fabric, the dedicated metadata servers — those aren't there. But for a homelab operator, a small production company, or any team that has more data than fits on one thing and needs a coherent answer to "where is that file?", it addresses all six requirements above as open-source software, free under AGPL v3.&lt;/p&gt;

&lt;p&gt;The architecture notes and a deeper dive into how the catalog handles the multi-volume problem are on the &lt;a href="https://huskhoard.com/blog.html" rel="noopener noreferrer"&gt;HuskHoard blog&lt;/a&gt; if you want to go further.&lt;/p&gt;




&lt;p&gt;The multi-volume namespace problem isn't glamorous. It doesn't show up in benchmarks or feature comparison tables. It shows up the third time you're manually mounting tapes trying to find a project file from 18 months ago, or the moment you realize you've been archiving the same directory to two different drives for six months and have no idea which copies are current.&lt;/p&gt;

&lt;p&gt;Every solution in this space involves a catalog — some record of what is where. The question is whether that catalog is a spreadsheet you maintain manually, a per-tape index that goes dark when the tape goes offline, an enterprise product with an enterprise price tag, or something designed to sit in the middle and make this problem manageable for the rest of us.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>rust</category>
      <category>aws</category>
      <category>software</category>
    </item>
    <item>
      <title>Using Zstd Frames to Egress Partial Parquet Files</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Wed, 24 Jun 2026 15:26:22 +0000</pubDate>
      <link>https://dev.to/coldstorage/using-zstd-frames-to-egress-partial-parquet-files-1fdd</link>
      <guid>https://dev.to/coldstorage/using-zstd-frames-to-egress-partial-parquet-files-1fdd</guid>
      <description>&lt;h2&gt;
  
  
  Jump Tables, TLV Footers, and the Real Cost of Reading What You Don't Need
&lt;/h2&gt;

&lt;p&gt;You're paying for bytes you never read.&lt;/p&gt;

&lt;p&gt;A data engineer on a busy pipeline touches dozens of Parquet files a day: schema discovery, predicate pushdown, column pruning, metadata scrapes for a data catalog sync. In each case, the application needs maybe 200 KB of context from a file that is 4 GB on disk. Without a seekable archive format and a jump table to find the right frame, your HTTP client fetches the whole thing, and your cloud egress invoice reflects every unnecessary gigabyte.&lt;/p&gt;

&lt;p&gt;This post quantifies the problem, then walks through how &lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;HuskHoard&lt;/a&gt; uses seekable Zstd frames, a per-volume jump table, and TLV-encoded footer metadata to make partial egress a first-class citizen across multi-volume archives — disk, cloud, and LTO tape alike.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem, In Dollars
&lt;/h2&gt;

&lt;p&gt;S3 standard egress runs $0.09/GB. GCS is $0.08/GB. Even Cloudflare R2, which is free for egress &lt;em&gt;from R2 to the internet&lt;/em&gt;, still costs you in latency and API call count when you cannot bound the range of bytes you need.&lt;/p&gt;

&lt;p&gt;Here is a representative read pattern for a cold analytics archive:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Bytes Needed&lt;/th&gt;
&lt;th&gt;Bytes Fetched (naïve)&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schema discovery&lt;/td&gt;
&lt;td&gt;~50 KB (Parquet footer)&lt;/td&gt;
&lt;td&gt;1–8 GB (full file)&lt;/td&gt;
&lt;td&gt;~1:16,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single column scan&lt;/td&gt;
&lt;td&gt;~200 MB (one column chunk)&lt;/td&gt;
&lt;td&gt;4 GB (full row group)&lt;/td&gt;
&lt;td&gt;1:20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data catalog sync (1M files)&lt;/td&gt;
&lt;td&gt;~50 GB (footers only)&lt;/td&gt;
&lt;td&gt;~4 PB (full files)&lt;/td&gt;
&lt;td&gt;1:80,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selective restore (1 row group)&lt;/td&gt;
&lt;td&gt;~128 MB&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;td&gt;1:32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On 100 TB of cold Parquet data with $0.09/GB egress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full read for schema sync&lt;/strong&gt;: 100 TB × $0.09 = &lt;strong&gt;$9,216&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial read (footers only, avg 100 KB/file, 1M files)&lt;/strong&gt;: ~100 GB × $0.09 = &lt;strong&gt;$9.00&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Savings per catalog sync: $9,207 — 99.9% reduction&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even a conservative column-scan scenario (pulling 15% of each file's bytes) cuts a $9,216 monthly read bill to &lt;strong&gt;$1,382&lt;/strong&gt;. The ceiling on savings is determined entirely by how precisely you can address the bytes you actually need.&lt;/p&gt;

&lt;p&gt;That precision is what frames and jump tables buy you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zstd Frames: What They Are and Why They Matter
&lt;/h2&gt;

&lt;p&gt;A single &lt;code&gt;.zst&lt;/code&gt; file produced by the standard &lt;code&gt;zstd&lt;/code&gt; CLI is one frame. Everything inside is a single compressed stream. You have to start decompression at byte 0 to reach any byte inside.&lt;/p&gt;

&lt;p&gt;But the Zstd spec allows a concatenation of independent frames. Each frame is a complete, self-contained unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Frame 0][Frame 1][Frame 2]...[Frame N]
 ^         ^         ^           ^
 16 MB     16 MB     16 MB       partial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every frame has a known &lt;code&gt;compressed_size&lt;/code&gt; and &lt;code&gt;decompressed_size&lt;/code&gt;. If you know those sizes in advance (stored in a jump table), you can seek directly to Frame N by summing the compressed sizes of frames 0 through N-1. You never decompress anything you don't need. Frame N is fetched with a single HTTP Range request, decompressed independently, and the relevant bytes are piped downstream.&lt;/p&gt;

&lt;p&gt;This is the architectural core of HuskHoard's egress model, and it maps cleanly onto how the Parquet format itself carves up a file.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Parquet Parallel: Row Groups as Frames
&lt;/h2&gt;

&lt;p&gt;Parquet is deliberately designed for partial reads. A Parquet file contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Row groups&lt;/strong&gt; — horizontal partitions of the data, each independently readable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column chunks&lt;/strong&gt; — vertical slices within a row group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page headers&lt;/strong&gt; — per-page metadata within each column chunk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Footer&lt;/strong&gt; — the &lt;code&gt;FileMetaData&lt;/code&gt; Thrift struct at the end of the file, containing the schema, row group offsets, column statistics, and key-value metadata. Preceded by a 4-byte footer length and terminated with the magic bytes &lt;code&gt;PAR1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reader that wants only the footer performs two range requests: one to get the last 8 bytes (magic + footer length), and one to get the footer itself. Everything else stays on the remote. A reader that wants one column from one row group consults the footer to find the column chunk's byte offset and length, then fires a single range request.&lt;/p&gt;

&lt;p&gt;HuskHoard's frame model mirrors this exactly, but at the archive level rather than within a single Parquet file.&lt;/p&gt;




&lt;h2&gt;
  
  
  HuskHoard's Implementation: Frames, the Catalog, and the Jump Table
&lt;/h2&gt;

&lt;p&gt;When HuskHoard archives a file to any backend — a flat image file acting as a tape volume, a physical LTO cartridge, or an rclone cloud remote — it writes the payload as a sequence of 16 MB Zstd frames. For each frame, it records the mapping between uncompressed byte position and compressed byte position on the volume.&lt;/p&gt;

&lt;p&gt;That mapping is the &lt;code&gt;object_frames&lt;/code&gt; table in &lt;code&gt;husk_catalog.db&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;object_frames&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;           &lt;span class="nb"&gt;TEXT&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt;             &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;uncompressed_offset&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- where this frame starts in the original file&lt;/span&gt;
    &lt;span class="n"&gt;compressed_offset&lt;/span&gt;   &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- where this frame starts on the storage volume&lt;/span&gt;
    &lt;span class="n"&gt;compressed_size&lt;/span&gt;     &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;    &lt;span class="c1"&gt;-- how many bytes to fetch from the volume&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;idx_frames&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;object_frames&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the jump table. Given a byte range request for &lt;code&gt;bytes=2147483648-2281701376&lt;/code&gt; (a 128 MB window starting at the 2 GB mark), the gateway does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;compressed_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compressed_size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;object_frames&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/warehouse/events/2024-01-01.parquet'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="k"&gt;version&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_frames&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;uncompressed_offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2147483648&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;uncompressed_offset&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One row. One seek. One range request against the volume. Everything else stays dark.&lt;/p&gt;

&lt;p&gt;The HTTP gateway loop in StreamGate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Range request arrives (bytes=X-Y)
        │
        ▼
Query object_frames → nearest frame boundary ≤ X
        │
        ▼
Seek to compressed_offset on volume (tape block, S3 range, local seek)
        │
        ▼
Decompress forward to exact byte X, stream through Y
        │
        ▼
Client receives exactly what it asked for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a 4K video file seeking to the 2-hour mark, this is why &lt;code&gt;mpv&lt;/code&gt; can start playing from tape or S3 in under a second instead of waiting for a multi-gigabyte download.&lt;/p&gt;




&lt;h2&gt;
  
  
  TLV Footers: Turning the Frame Header Into a Parquet-Style Catalog Entry
&lt;/h2&gt;

&lt;p&gt;Every file archived by HuskHoard is preceded on the storage volume by a strict &lt;strong&gt;4,096-byte ObjectHeader&lt;/strong&gt;. The first 136 bytes carry the fixed-width mechanics: a magic string (&lt;code&gt;USTDHUSK&lt;/code&gt;), the file's UUID, POSIX permissions, BLAKE3 hash, compressed and uncompressed sizes, and a CRC32 of the header itself.&lt;/p&gt;

&lt;p&gt;The remaining &lt;strong&gt;3,960 bytes&lt;/strong&gt; are dedicated to &lt;strong&gt;TLV (Type-Length-Value)&lt;/strong&gt; encoded metadata — the same binary framing used in X.509 certificates, SNMP, and dozens of wire protocols chosen specifically because unknown type codes can be safely skipped by any forward-compatible parser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Byte  0 –  7: Magic "USTDHUSK"
Byte  8 – 23: Volume UUID (16 bytes)
Byte 24 – 55: BLAKE3 hash (32 bytes)
Byte 56 – 63: Uncompressed payload size
Byte 64 – 71: Compressed payload size
Byte 72 – 79: Original mtime
Byte 80 – 83: POSIX mode
Byte 84 – 87: Header CRC32
Byte 88 –135: File path (null-terminated, 48 bytes max inline)
Byte 136–4095: TLV region (3,960 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A TLV tag entry looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Type: u8][Key-Length: u16][Key: bytes][Value-Length: u32][Value: bytes]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the Parquet footer analogy becomes structural rather than metaphorical. For a Parquet file being archived, HuskHoard can embed the Parquet &lt;code&gt;FileMetaData&lt;/code&gt; statistics directly into this TLV region:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TLV Type&lt;/th&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parquet.schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serialized Thrift schema (JSON or binary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parquet.row_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Total row count as little-endian u64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parquet.col.event_ts.min&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Minimum value of &lt;code&gt;event_ts&lt;/code&gt; column&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parquet.col.event_ts.max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum value of &lt;code&gt;event_ts&lt;/code&gt; column&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parquet.col.user_id.null_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Null count for &lt;code&gt;user_id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parquet.row_group.count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of row groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workflow.pipeline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"ingest_v3"&lt;/code&gt; — POSIX xattr from source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workflow.owner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"data-eng-team"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These statistics travel &lt;strong&gt;physically bonded to the data&lt;/strong&gt; on every storage medium — disk image, tape cartridge, S3 object. If the SQLite catalog is lost, &lt;code&gt;husk rebuild&lt;/code&gt; walks the volume, reads every 4 KB header, and reconstructs the catalog complete with all column statistics. The tape is entirely self-describing.&lt;/p&gt;

&lt;p&gt;But the real payoff is what this enables while the catalog &lt;em&gt;is&lt;/em&gt; present.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Volume Catalog Queries: Pruning at the Volume Level
&lt;/h2&gt;

&lt;p&gt;A production HuskHoard deployment might span several volumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Volume A (tape, 12 TB) — archive 2022–2023
Volume B (tape, 12 TB) — archive 2023–2024
Volume C (NVMe image, 2 TB) — archive 2024–present
Volume D (S3:us-east-1, 50 TB) — cloud replica
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;catalog&lt;/code&gt; table records which volume holds each archived version of each file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tape_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- identifies the volume&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tape_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- byte offset of the ObjectHeader on that volume&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compressed_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom_metadata&lt;/span&gt;     &lt;span class="c1"&gt;-- mirrors the TLV tags as JSON&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="k"&gt;catalog&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$.parquet.col.event_ts.min'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$.parquet.col.event_ts.max'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024-03-31'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$.parquet.row_count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query executes in milliseconds against the SQLite catalog on your SSD. The tape drives stay spun down. S3 is never contacted. You get back a list of &lt;code&gt;(file_path, tape_uuid, tape_offset)&lt;/code&gt; tuples — the exact volumes and positions to touch.&lt;/p&gt;

&lt;p&gt;Then, per file, for each column you actually need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;compressed_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compressed_size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;object_frames&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;file_path&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/warehouse/events/2024-01-15.parquet'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="k"&gt;version&lt;/span&gt;             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;uncompressed_offset&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;col_chunk_start&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;col_chunk_end&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;uncompressed_offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You issue a range request for only those frames. For a 4 GB Parquet file where you need one 200 MB column chunk:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Data Transferred&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catalog query (SQLite, local)&lt;/td&gt;
&lt;td&gt;0 bytes egress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;object_frames lookup (SQLite, local)&lt;/td&gt;
&lt;td&gt;0 bytes egress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Range to S3 (compressed frame bytes)&lt;/td&gt;
&lt;td&gt;~85 MB (at 2.4:1 Zstd ratio)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total vs naïve full-file fetch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85 MB vs 1.7 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is a &lt;strong&gt;95% reduction&lt;/strong&gt; on a per-query basis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting Numbers to the Savings
&lt;/h2&gt;

&lt;p&gt;Let's use a concrete scenario: a data team maintains a 10 TB cold Parquet archive on S3, with an average file size of 4 GB. They run three workloads:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workload A — Nightly catalog sync (schema + statistics only)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files: 2,500 Parquet files&lt;/li&gt;
&lt;li&gt;Data needed per file: footer only (~150 KB each)&lt;/li&gt;
&lt;li&gt;Total needed: ~375 MB&lt;/li&gt;
&lt;li&gt;Full-file cost: 10 TB × $0.09 = &lt;strong&gt;$921.60/month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Partial-frame cost: 375 MB × $0.09 = &lt;strong&gt;$0.03/month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monthly savings: $921.57&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Workload B — Ad-hoc column scan (one column across 20% of files)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files queried: 500 (selected by TLV statistics predicate)&lt;/li&gt;
&lt;li&gt;Column chunk per file: ~200 MB uncompressed → ~85 MB compressed frames&lt;/li&gt;
&lt;li&gt;Total fetched: ~42.5 GB&lt;/li&gt;
&lt;li&gt;Full-file cost: 500 × 4 GB × $0.09 = &lt;strong&gt;$180.00/query&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Partial-frame cost: 42.5 GB × $0.09 = &lt;strong&gt;$3.83/query&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-query savings: $176.17 (97.9%)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Workload C — Point-in-time restore of a single row group&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 file × 1 row group = 128 MB uncompressed → ~54 MB compressed&lt;/li&gt;
&lt;li&gt;Full-file cost: 4 GB × $0.09 = &lt;strong&gt;$0.36&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Partial-frame cost: 54 MB × $0.09 = &lt;strong&gt;$0.005&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-restore savings: $0.355 (98.6%)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At scale, Workload A alone — a nightly catalog sync that most teams run without thinking about the bill — generates &lt;strong&gt;~$11,000/year in unnecessary egress&lt;/strong&gt; on a 10 TB archive. The frame-indexed approach reduces that to under $1/year.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Self-Describing Volume: Your Catalog Backup Is on the Tape
&lt;/h2&gt;

&lt;p&gt;One underappreciated consequence of storing TLV column statistics in every ObjectHeader is that the volume itself becomes a data catalog. After a complete disaster recovery (catalog database lost, fresh server), &lt;code&gt;husk rebuild&lt;/code&gt; walks the storage volume 4 KB at a time, reads every ObjectHeader, validates the CRC32, and inserts a new catalog row including all TLV-encoded metadata — column statistics, schema, pipeline tags, everything.&lt;/p&gt;

&lt;p&gt;The catalog is not a separate system that the archive depends on. The catalog is a cache that accelerates access to information already encoded in the archive itself. This is the same philosophical commitment Parquet makes: the footer is not a separate sidecar file; it is part of the format.&lt;/p&gt;

&lt;p&gt;For teams integrating with external data catalogs (Apache Atlas, Hive Metastore, Unity Catalog), this means HuskHoard can emit catalog events on &lt;code&gt;husk rebuild&lt;/code&gt; just as well as on initial archive — the metadata survives the worst failure scenario, format-native.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wiring It Up: What a Partial Read Looks Like End-to-End
&lt;/h2&gt;

&lt;p&gt;A data engineer's dbt model lands at the StreamGate HTTP gateway with a &lt;code&gt;Range: bytes=536870912-671088640&lt;/code&gt; request (512 MB – 640 MB, pulling a specific row group from a 4 GB Parquet file on S3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;1. GET http://localhost:8080/v1/stream/warehouse/events/2024-01-15.parquet
   Range: bytes=536870912-671088640

2. Gateway queries object_frames:
   → nearest frame boundary ≤ 536870912 is at uncompressed_offset=536870912
   → compressed_offset=225,978,112 on Volume D (S3:us-east-1)
   → 6 frames needed, compressed total = 56.3 MB

3. Gateway fires:
   GET s3://huskhoard-cold/volume-d.img
   Range: bytes=225978112-285884415

4. Gateway decompresses frames on the fly, streams bytes 536870912–671088640
   to the client.

5. Total egress from S3: 56.3 MB
   Total egress if client had fetched the full file: 1.71 GB
   Savings: 96.7%
   Time to first byte (LAN): ~180ms vs ~14s for full-file download
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client — dbt, Spark, DuckDB, &lt;code&gt;curl&lt;/code&gt;, whatever — receives a standard HTTP 206 Partial Content response. No special client library. No SDK. Just the HTTP Range spec, universally supported.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Takeaways for Data Engineers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Frame size is a tuning knob.&lt;/strong&gt; HuskHoard defaults to 16 MB frames, optimized for cloud PUT cost (fewer, larger requests) and Zstd compression ratio. For workloads with very fine-grained access patterns (column-level reads in narrow schemas), smaller frames (1–4 MB) reduce the minimum fetch size at the cost of more catalog rows and higher PUT count. Benchmark against your actual access patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. TLV statistics are opt-in per file type.&lt;/strong&gt; For video files you probably don't store column min/max values. For Parquet, CSV, and Arrow IPC files it's worth paying the archiver CPU time to extract and embed statistics at archive time — you pay once and recoup every time a catalog query avoids a volume read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The catalog query is your explain plan.&lt;/strong&gt; Before a restore or a scan, &lt;code&gt;husk catalog query --path "/warehouse/events/*.parquet" --filter "parquet.col.event_ts.min &amp;gt;= 2024-01-01"&lt;/code&gt; shows you which volumes and frame ranges will be touched. Run it first. If the egress estimate is unexpected, the TLV coverage on those files probably needs improving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Multi-volume means cross-volume pruning is free.&lt;/strong&gt; A query that touches two volumes and skips three is doing volume-level predicate pushdown before any I/O. The catalog does this automatically based on the &lt;code&gt;tape_uuid&lt;/code&gt; in each matching row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Egress savings compound with replication.&lt;/strong&gt; HuskHoard replicates to multiple volumes simultaneously. If your primary volume is on S3 ($0.09/GB egress) and your replica is on Cloudflare R2 ($0.00 egress), the gateway can route the range request to whichever backend minimizes cost. Partial reads from R2 are free. You still benefit from the jump table because API call count and latency still matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://huskhoard.com/blog-post-cat.html" rel="noopener noreferrer"&gt;The Catalog Is the Archive: Inside HuskHoard's Ground Truth Engine&lt;/a&gt; — deep dive on the SQLite schema, WAL mode, and catalog rebuild&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huskhoard.com/blog-post-tag.html" rel="noopener noreferrer"&gt;Data Without Context is Entropy: The Architecture of Tagging&lt;/a&gt; — TLV byte packing, POSIX xattrs, and the self-healing archive&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;HuskHoard on GitHub&lt;/a&gt; — source, README, and the &lt;code&gt;object_frames&lt;/code&gt; implementation in &lt;code&gt;src/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/facebook/zstd/blob/dev/contrib/seekable_format/zstd_seekable_compression_format.md" rel="noopener noreferrer"&gt;Zstd Seekable Format spec&lt;/a&gt; — the upstream spec HuskHoard's frame model is compatible with&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huskhoard.com/blog-post-parquet.html" rel="noopener noreferrer"&gt;Apache Parquet with an Archive&lt;/a&gt; — Building a Zero-Impact Data Lake&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;HuskHoard is open-source under AGPL v3. If you're running cold data tiers on Linux and want to stop paying for bytes you never read, contributions and issues are welcome at &lt;a href="https://github.com/HuskHoard/HuskHoard" rel="noopener noreferrer"&gt;github.com/HuskHoard/HuskHoard&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>rust</category>
      <category>aws</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title># Why I Bypassed FUSE: Building a Transparent DataTiering Engine in Rust</title>
      <dc:creator>Joichiro Mitaka</dc:creator>
      <pubDate>Fri, 19 Jun 2026 05:51:33 +0000</pubDate>
      <link>https://dev.to/coldstorage/-why-i-bypassed-fuse-building-a-transparent-datatiering-engine-in-rust-4b8d</link>
      <guid>https://dev.to/coldstorage/-why-i-bypassed-fuse-building-a-transparent-datatiering-engine-in-rust-4b8d</guid>
      <description>&lt;p&gt;If you run a home lab or manage large datasets, you’ve hit this wall: NVMe drives are fast but too expensive to hoard data on. Hard drives or cloud buckets are cheap, but they are slow and a pain to manage manually.&lt;/p&gt;

&lt;p&gt;The enterprise world solves this with &lt;strong&gt;HSM Hierarchical Storage Management&lt;/strong&gt; automatically shuffling cold data to slow storage while keeping a transparent stub on the fast drive. But enterprise HSMs cost thousands of dollars and lock your data in proprietary black boxes.&lt;/p&gt;

&lt;p&gt;I wanted this for Linux, for free. So, I started building &lt;strong&gt;HuskHoard&lt;/strong&gt;, an opensource data tiering engine. &lt;/p&gt;

&lt;p&gt;My first thought, like almost every Linux developer building a virtual filesystem, was to use FUSE. But I quickly realized FUSE was the wrong tool for the job. Here is why I abandoned it, and how I used the Linux fanotify API and Rust to build a transparent, zero overhead archiving engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with FUSE
&lt;/h2&gt;

&lt;p&gt;FUSE is fantastic for creating custom filesystems like SSHFS or mounting an S3 bucket. But for an HSM, it creates a massive bottleneck.&lt;/p&gt;

&lt;p&gt;When you use FUSE, &lt;em&gt;every single read and write&lt;/em&gt; has to go through a context switch:&lt;br&gt;
Application &amp;gt; Kernel &amp;gt; FUSE Daemon Userspace &amp;gt; Kernel &amp;gt; Physical Drive.&lt;/p&gt;

&lt;p&gt;If 90% of your data is Hot actively being used on your fast NVMe, forcing it through FUSE overhead completely defeats the purpose of buying expensive NVMe drives in the first place. You sacrifice native I/O performance just to manage the 10% of Cold data.&lt;/p&gt;

&lt;p&gt;I needed a solution where the Hot data ran at native speed, touching nothing but the XFS/Ext4 kernel drivers. &lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: Enter fanotify
&lt;/h2&gt;

&lt;p&gt;Instead of intercepting every transaction via FUSE, I realized I only needed to intervene in one specific scenario: &lt;strong&gt;When a user tries to open a file that has been archived.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Linux has a kernel API called fanotify originally designed for antivirus scanners. It allows a userspace program to monitor a mount point and, crucially, &lt;em&gt;block&lt;/em&gt; an application from opening a file until the daemon says it’s okay.&lt;/p&gt;

&lt;p&gt;Here is how HuskHoard uses fanotify to create transparent tiering:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Janitor:&lt;/strong&gt; A background Rust thread scans my NVMe drive. When it finds a file that hasnt been touched in 30 days, it compresses it Zstd and moves the payload to a cheap HDD, LTO Tape, or S3 bucket.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Husk Stub:&lt;/strong&gt; It leaves the original file on the NVMe drive but truncates its allocated size to 0 bytes creating a sparse file. To the OS and the user, the file still looks like it’s 50GB and sits in /home/movies.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Interceptor:&lt;/strong&gt; This is where fanotify shines. The HuskHoard daemon listens for FAN_ACCESS_PERM events. If VLC media player tries to open that Husk file, fanotify pauses VLCs execution in the kernel.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Recall:&lt;/strong&gt; HuskHoard intercepts the request, streams the 50GB payload from the tape/S3 bucket back into the sparse file on the NVMe, and then tells fanotify to allow VLC to proceed. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;VLC thinks it just opened a local file. It has no idea the data was fetched from an S3 bucket 50 milliseconds ago. &lt;/p&gt;
&lt;h2&gt;
  
  
  The Rust Implementation
&lt;/h2&gt;

&lt;p&gt;Rust was the obvious choice for this. When you are blocking kernellevel I/O requests, memory safety and predictable latency are nonnegotiable. &lt;/p&gt;

&lt;p&gt;Handling the fanotify loop requires a few specific Linux capabilities specifically CAP_SYS_ADMIN, but Rust allows us to safely manage the multithreaded heavy lifting of the Archive Worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pub fn run_interceptor&lt;span class="o"&gt;(&lt;/span&gt;config: Arc&amp;lt;HuskConfig&amp;gt;, use_direct_io: bool&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; std::io::Result&amp;lt;&lt;span class="o"&gt;()&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;let &lt;/span&gt;watch_dir &lt;span class="o"&gt;=&lt;/span&gt; &amp;amp;config.hot_tier&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;let &lt;/span&gt;db_path &lt;span class="o"&gt;=&lt;/span&gt; &amp;amp;config.db_path&lt;span class="p"&gt;;&lt;/span&gt;
    info!&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;[Daemon] Starting fanotify interceptor on '{}'..."&lt;/span&gt;, watch_dir&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;let &lt;/span&gt;abs_dir &lt;span class="o"&gt;=&lt;/span&gt; std::fs::canonicalize&lt;span class="o"&gt;(&lt;/span&gt;watch_dir&lt;span class="o"&gt;)&lt;/span&gt;?&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;let &lt;/span&gt;fan_fd &lt;span class="o"&gt;=&lt;/span&gt; unsafe &lt;span class="o"&gt;{&lt;/span&gt;
        libc::fanotify_init&lt;span class="o"&gt;(&lt;/span&gt;libc::FAN_CLASS_PRE_CONTENT, libc::O_RDWR as u32&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;fan_fd &amp;lt; 0 &lt;span class="o"&gt;{&lt;/span&gt; 
        &lt;span class="nb"&gt;let &lt;/span&gt;err &lt;span class="o"&gt;=&lt;/span&gt; std::io::Error::last_os_error&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        error!&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" fanotify_init failed: {}. Missing Root or Capabilities!"&lt;/span&gt;, err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;Err&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="o"&gt;}&lt;/span&gt;


        &lt;span class="nb"&gt;let &lt;/span&gt;mark_mask &lt;span class="o"&gt;=&lt;/span&gt; libc::FAN_ACCESS_PERM | libc::FAN_CLOSE_WRITE | libc::FAN_EVENT_ON_CHILD&lt;span class="p"&gt;;&lt;/span&gt;

        // 1. Recursively mark the root watch directory and all current subdirectories
        info!&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[Daemon]  Scanning and attaching listeners to all subdirectories..."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        mark_directory_recursive&lt;span class="o"&gt;(&lt;/span&gt;fan_fd, &amp;amp;abs_dir, mark_mask, &amp;amp;config&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Escaping Vendor Lockin The Easy Exit Promise
&lt;/h2&gt;

&lt;p&gt;One of the biggest issues with commercial HSMs is that if the daemon dies, your data is gone, trapped in proprietary metadata.&lt;/p&gt;

&lt;p&gt;Because I was building this for the opensource community, I enforced a strict Easy Exit architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Payload data is stored in standard &lt;strong&gt;Zstd&lt;/strong&gt; streams verified by &lt;strong&gt;BLAKE3&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  The catalog metadata the Brain tracking where the cold bytes live is an SQLite database.&lt;/li&gt;
&lt;li&gt;  You can natively export the entire catalog to &lt;strong&gt;Apache Parquet&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means if you decide to stop using HuskHoard, you dont need my software to get your data back. You can query your catalog with DuckDB or Python and manually extract your Zstd archives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whats Next?
&lt;/h2&gt;

&lt;p&gt;Building HuskHoard has been a massive deepdive into Linux kernel APIs and SCSI Tape drivers. Yes, it natively supports physical LTO drives via /dev/nstX to prevent tape shoeshining. &lt;/p&gt;

&lt;p&gt;The engine currently supports automated replication across local drives, tapes, and rclone supported cloud buckets.&lt;/p&gt;

&lt;p&gt;If you are a Rust developer, a HomeLab data hoarder, or just interested in Linux storage architecture, Id love your feedback or code reviews. It is fully AGPL v3 licensed. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the repo here:&lt;/strong&gt; [GitHub  HuskHoard]&lt;a href="https://github.com/huskhoard/huskhoard" rel="noopener noreferrer"&gt;https://github.com/huskhoard/huskhoard&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;More architecture details:&lt;/strong&gt; [HuskHoard Blog]&lt;a href="https://www.huskhoard.com/blog.html" rel="noopener noreferrer"&gt;https://www.huskhoard.com/blog.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>rust</category>
      <category>infrastructure</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
