This document tracks the native engine storage path for large seedboxes. The target is explicit userspace I/O control for 10k-100k torrents and 200+ TB libraries without mmap as the primary data path.
The executable feature matrix for this storage branch lives in
STORAGE_NG_TEST_MATRIX.md.
Open storage and memory follow-up work is tracked in
STORAGE_MEMORY_GAP_REGISTER.md.
The current storage implementation map, including the split between the live
torrent hot path and the backend/runtime probe path, lives in
STORAGE_WORK_MAP.md.
The original native path used a simple per-block primitive:
open -> seek -> read/write -> closefor every block.create_dir_allbefore every download write.- No open-file cache or descriptor budget.
- No preallocation, so disk space and fragmentation failures appeared late.
- No
fdatasync/sync_datacheckpoint before trusting fastresume. - Piece verification hashed synchronously after async disk reads.
That was correct enough for small tests, but it created avoidable fd churn,
Tokio blocking-pool contention, HDD fragmentation, late ENOSPC, weak
crash-recovery trust, and poor seed-read locality.
The architecture follows the lessons from libtorrent-rasterbar's disk I/O design: separate disk work from protocol work, keep file descriptors pooled, bound queues and buffers, and make cache/scheduler behavior explicit. It also intentionally avoids the libtorrent 2.x mmap direction that was later rolled back for workloads where the client needs tighter control over disk pressure. The preallocation requirements mirror long-standing qBittorrent and Transmission behavior: sparse allocation is cheap and predictable for most filesystems, while full preallocation is reserved for storage where it helps most: rotational, non-CoW local filesystems.
rt-storage::MountScheduler now owns the live torrent disk layer beneath the
class semaphores:
StorageIoConfigcarries file-pool size, idle TTL, I/O worker count, queue depth, preallocation mode, durability mode, and peer-read readahead target.PreallocationMode::Autoresolves at scheduler construction time from the detected topology. Native[storage]TOML exposes these scheduler knobs so operators can tune them without code changes.scheduled_readandscheduled_writeremain compatibility wrappers, but call positionedread_at/write_at. Callers that can process borrowed data can usescheduled_read_owned/read_owned_atto keep exact backend reads in a pooledStorageRead::Frame. Upload assembly and sparse recheck extent reads use the owned path; the plainBytespath is retained for compatibility and tests, and consumes returned frames without copying payload bytes.- Disk syscalls are submitted through the probe-selected
DiskBackend(preadbaseline or explicit Linuxio_uring) whileMountSchedulerpreserves its per-class, per-mount, and per-device backpressure gates. - SHA-1 and BEP52 leaf/root hashing run on a separate bounded hashing pool.
- The open-file pool is keyed by normalized absolute path, tracks read/write mode, hits, misses, evictions, idle closes, and open count.
- File-pool capacity is clamped to a conservative fraction of
RLIMIT_NOFILEon Unix when the soft limit is available. - New schedulers can auto-detect HDD, SSD/NVMe, or network profiles from Linux
mount and sysfs topology when callers do not override the storage profile.
The same topology read records a stable
DeviceId, filesystem type, and whether the mount is likely CoW. Local device ids come from/sys/dev/blockparent block devices; network mounts use the mount source. - Reads open read-only with
create(false)and never create or truncate files. - Writes use positioned I/O and validate short writes.
prepare_filecreates parents and appliesPreallocationMode::{Off, Sparse, Full}before first write.Autois resolved toFullonly for rotational non-CoW local storage; SSD/NVMe, network, unknown, and CoW filesystems stay sparse.sync_dataandsync_all_open_filesprovide durability checkpoints.DurabilityMode::Strictsyncs after writes;Checkpointsyncs open torrent files before clean fastresume saves;Fastpreserves older relaxed behavior.StorageIoStatsexposes file-pool counters, queue depths, dirty file count, bytes and operations byIoClass, sync count, hash count, and preallocation fallback/failure counters. Queued disk bytes are actual short-lived payload leases held by queued or active disk, hash, and peer-read elevator jobs, not queue-depth estimates.- The live torrent hot path and
rt-storage::StorageRuntimeshare the same probe-selected backend layer:TNG_STORAGE_BACKEND=auto|pread|uringchooses between the portable positioned-I/O worker pool and Linuxio_uringpositioned reads, writes, and data sync.autocurrently selects the conservativepreadbaseline;uringmust be requested explicitly while correctness and hardware benchmarks mature. Both backend implementations use bounded internal queues and fail closed when saturated. The uring worker keeps a stable per-worker fixed-file table keyed by file identity and registers frame-pool-backed fixed read slots when the kernel accepts them. Metrics expose this asfixed_buffer_strategy=frame_pool_slots; kernels or containers that rejectio_uringfall back topreadwith an explicit diagnostic reason instead of silently changing behavior. Direct backend probes remain available for hardware certification and capability metrics. IoClass::PeerReaduses a small internal readahead cache when configured: the backend may read ahead within the same file, but callers receive exactly the requested byte range.DeviceElevatornow exists as a self-contained per-device scheduling policy: HDD/network queues can hold work for a short budget, dispatch sorted by file offset, coalesce adjacent reads, and promote deadline-expired, foreground, or choke-critical work. HDD peer reads are wired through the elevator whenpeer_read_elevator_budget_msis non-zero, and metrics expose enablement, queue depth, backend batches, and coalesced logical requests.- Schedulers that resolve to the same storage device share a process-level device queue semaphore before submitting positioned disk work, so separate mounts on the same spindle cannot each consume a full independent disk queue.
TorrentTask keeps a per-file preparation registry so parent directories and
file allocation are no longer in the per-block hot path. It also keeps
in-memory piece assembly buffers for active downloads, so completed-piece
validation hashes the assembled bytes directly when all blocks are present and
only falls back to disk verification when memory state is incomplete. These
buffers are bounded to 64 active pieces and 64 MiB per torrent task; when that
budget is exceeded, the least recently used incomplete piece buffer is evicted
and later validation falls back to the scheduled disk path. Pieces larger than
the byte budget skip in-memory assembly entirely.
Peer discovery and peer I/O pressure are also bounded. Tracker announce results are retained in a capped cache with drop counters, metadata peer retry state is deduplicated and pruned against the configured peer budget, and runtime stats surface peer receive/transmit buffer bytes so the resource governor can account for outstanding request memory alongside storage frames.
clean_shutdown = true means more than "the JSON state file was atomically
renamed." In checkpoint and strict modes it means data files were synced
according to the configured durability mode before the fastresume state was
saved. If that sync fails, the state is saved with clean_shutdown = false, so
startup falls back to verification instead of trusting stale piece state.
The following items were previously tracked as implementation targets and are now part of the release surface:
- Native move/import/delete API calls use the storage-plan job helpers. The storage executor accepts completed step indexes, and the engine persists storage-plan queue/start/checkpoint/complete state in the durable jobs table so interrupted multi-step plans can resume or be audited after process restart. The WebUI planner can provide affected torrent metadata, completed resume steps, operation templates, forward/rollback byte summaries, and active storage-plan job progress.
- Per-device latency observability includes bounded Prometheus histograms for read/write/sync/hash work labeled by resolved device/profile, plus the cumulative per-device totals. Aggregate fixed-bucket histograms and counters also cover file-pool activity, queue depth, dirty files, sync/hash/preallocate work, peer-read cache, logical/backend reads, and in-memory piece assembly pressure.
The following items are hardware evidence gates, not missing local storage implementation:
- Keep
io_uringexplicit opt-in until real-device reports show the registered frame-slot path beats the portablepreadbaseline on target hardware. The final uring graduation proof usesTNG_STORAGE_URING_REQUIRE_FRAME_POOL_SLOTS=1. - The release hardware report records real-device seed-read locality, bounded
hot-file descriptor reuse, recheck runtime progress, elevator throughput, and
optional syscall counts (
TNG_STORAGE_SYSCALLS=1) across one or more target mounts.scripts/storage_release_certification.shruns that report together withio_uringgraduation, real-root move/import evidence, and index generation.
- Safe path resolution remains owned by
SafeRelPathandPieceMap; peer input never becomes a raw filesystem path. - Reads must never create files.
- Writes create only for known torrent file regions.
- Positioned I/O treats short reads/writes as
StorageError::ShortIo. - Preallocation errors fail the write path before blocks or pieces are marked valid.
- Fastresume must not trust valid pieces after a configured durability sync failure.