惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
爱范儿
爱范儿
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
P
Palo Alto Networks Blog
博客园 - 司徒正美
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
罗磊的独立博客
阮一峰的网络日志
阮一峰的网络日志
The Register - Security
The Register - Security
D
DataBreaches.Net
A
Arctic Wolf
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy & Cybersecurity Law Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog
V
Vulnerabilities – Threatpost
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
T
Tor Project blog
GbyAI
GbyAI
Recent Announcements
Recent Announcements
T
The Blog of Author Tim Ferriss
Simon Willison's Weblog
Simon Willison's Weblog
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
G
GRAHAM CLULEY
宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
C
Check Point Blog
W
WeLiveSecurity
F
Fortinet All Blogs
P
Proofpoint News Feed
Security Archives - TechRepublic
Security Archives - TechRepublic
月光博客
月光博客
Project Zero
Project Zero
Know Your Adversary
Know Your Adversary
V
Visual Studio Blog
H
Help Net Security
H
Hacker News: Front Page
Webroot Blog
Webroot Blog
S
Securelist
酷 壳 – CoolShell
酷 壳 – CoolShell
O
OpenAI News
The Cloudflare Blog
Attack and Defense Labs
Attack and Defense Labs

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Using Zstd Frames to Egress Partial Parquet Files
Joichiro Mitaka · 2026-06-24 · via DEV Community

Jump Tables, TLV Footers, and the Real Cost of Reading What You Don't Need

You're paying for bytes you never read.

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.

This post quantifies the problem, then walks through how HuskHoard 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.


The Problem, In Dollars

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

Here is a representative read pattern for a cold analytics archive:

Operation Bytes Needed Bytes Fetched (naïve) Ratio
Schema discovery ~50 KB (Parquet footer) 1–8 GB (full file) ~1:16,000
Single column scan ~200 MB (one column chunk) 4 GB (full row group) 1:20
Data catalog sync (1M files) ~50 GB (footers only) ~4 PB (full files) 1:80,000
Selective restore (1 row group) ~128 MB 4 GB 1:32

On 100 TB of cold Parquet data with $0.09/GB egress:

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

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

That precision is what frames and jump tables buy you.


Zstd Frames: What They Are and Why They Matter

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

But the Zstd spec allows a concatenation of independent frames. Each frame is a complete, self-contained unit:

[Frame 0][Frame 1][Frame 2]...[Frame N]
 ^         ^         ^           ^
 16 MB     16 MB     16 MB       partial

Every frame has a known compressed_size and decompressed_size. 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.

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


The Parquet Parallel: Row Groups as Frames

Parquet is deliberately designed for partial reads. A Parquet file contains:

  • Row groups — horizontal partitions of the data, each independently readable
  • Column chunks — vertical slices within a row group
  • Page headers — per-page metadata within each column chunk
  • Footer — the FileMetaData 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 PAR1.

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.

HuskHoard's frame model mirrors this exactly, but at the archive level rather than within a single Parquet file.


HuskHoard's Implementation: Frames, the Catalog, and the Jump Table

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.

That mapping is the object_frames table in husk_catalog.db:

CREATE TABLE IF NOT EXISTS object_frames (
    file_path           TEXT    NOT NULL,
    version             INTEGER NOT NULL,
    uncompressed_offset INTEGER NOT NULL,   -- where this frame starts in the original file
    compressed_offset   INTEGER NOT NULL,   -- where this frame starts on the storage volume
    compressed_size     INTEGER NOT NULL    -- how many bytes to fetch from the volume
);

CREATE INDEX IF NOT EXISTS idx_frames
    ON object_frames (file_path, version);

This is the jump table. Given a byte range request for bytes=2147483648-2281701376 (a 128 MB window starting at the 2 GB mark), the gateway does:

SELECT compressed_offset, compressed_size
FROM   object_frames
WHERE  file_path = '/warehouse/events/2024-01-01.parquet'
  AND  version   = (SELECT MAX(version) FROM object_frames WHERE file_path = ...)
  AND  uncompressed_offset <= 2147483648
ORDER  BY uncompressed_offset DESC
LIMIT  1;

One row. One seek. One range request against the volume. Everything else stays dark.

The HTTP gateway loop in StreamGate:

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

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


TLV Footers: Turning the Frame Header Into a Parquet-Style Catalog Entry

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

The remaining 3,960 bytes are dedicated to TLV (Type-Length-Value) 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.

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)

A TLV tag entry looks like:

[Type: u8][Key-Length: u16][Key: bytes][Value-Length: u32][Value: bytes]

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

TLV Type Key Value
0x02 parquet.schema Serialized Thrift schema (JSON or binary)
0x02 parquet.row_count Total row count as little-endian u64
0x02 parquet.col.event_ts.min Minimum value of event_ts column
0x02 parquet.col.event_ts.max Maximum value of event_ts column
0x02 parquet.col.user_id.null_count Null count for user_id
0x02 parquet.row_group.count Number of row groups
0x01 workflow.pipeline "ingest_v3" — POSIX xattr from source
0x01 workflow.owner "data-eng-team"

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

But the real payoff is what this enables while the catalog is present.


Multi-Volume Catalog Queries: Pruning at the Volume Level

A production HuskHoard deployment might span several volumes:

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

The catalog table records which volume holds each archived version of each file:

SELECT
    c.file_path,
    c.tape_uuid,          -- identifies the volume
    c.tape_offset,        -- byte offset of the ObjectHeader on that volume
    c.payload_size,
    c.compressed_size,
    c.custom_metadata     -- mirrors the TLV tags as JSON
FROM   catalog c
WHERE  json_extract(c.custom_metadata, '$.parquet.col.event_ts.min') >= '2024-01-01'
  AND  json_extract(c.custom_metadata, '$.parquet.col.event_ts.max') <= '2024-03-31'
  AND  json_extract(c.custom_metadata, '$.parquet.row_count')        > 0;

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 (file_path, tape_uuid, tape_offset) tuples — the exact volumes and positions to touch.

Then, per file, for each column you actually need:

SELECT compressed_offset, compressed_size
FROM   object_frames
WHERE  file_path           = '/warehouse/events/2024-01-15.parquet'
  AND  version             = 3
  AND  uncompressed_offset BETWEEN :col_chunk_start AND :col_chunk_end
ORDER  BY uncompressed_offset;

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

Step Data Transferred
Catalog query (SQLite, local) 0 bytes egress
object_frames lookup (SQLite, local) 0 bytes egress
HTTP Range to S3 (compressed frame bytes) ~85 MB (at 2.4:1 Zstd ratio)
Total vs naïve full-file fetch 85 MB vs 1.7 GB

That is a 95% reduction on a per-query basis.


Putting Numbers to the Savings

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:

Workload A — Nightly catalog sync (schema + statistics only)

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

Workload B — Ad-hoc column scan (one column across 20% of files)

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

Workload C — Point-in-time restore of a single row group

  • 1 file × 1 row group = 128 MB uncompressed → ~54 MB compressed
  • Full-file cost: 4 GB × $0.09 = $0.36
  • Partial-frame cost: 54 MB × $0.09 = $0.005
  • Per-restore savings: $0.355 (98.6%)

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


The Self-Describing Volume: Your Catalog Backup Is on the Tape

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), husk rebuild 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.

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.

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


Wiring It Up: What a Partial Read Looks Like End-to-End

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

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

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


Practical Takeaways for Data Engineers

1. Frame size is a tuning knob. 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.

2. TLV statistics are opt-in per file type. 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.

3. The catalog query is your explain plan. Before a restore or a scan, husk catalog query --path "/warehouse/events/*.parquet" --filter "parquet.col.event_ts.min >= 2024-01-01" 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.

4. Multi-volume means cross-volume pruning is free. 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 tape_uuid in each matching row.

5. Egress savings compound with replication. 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.


Further Reading


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 github.com/HuskHoard/HuskHoard.