Seedbox and Storage Box Disk Priority
Pulsed Media runs shared seedbox and storage box hosting on its own hardware in Finland. On a shared host, several customers share the same physical disks — so disk I/O priority decides how much disk bandwidth your account gets when the server is busy. This article explains how Pulsed Media handles that: what determines your share of the disk, how it relates to your account's read limit and your plan's sustained ceiling, how plan tiers map to priority, and — for the technically curious — the kernel-level machinery underneath, including a long-standing upstream issue Pulsed Media works around to make tiered priority actually function.
It is organized top-down: what it means for you first, the deep technical detail second.
What this means for you
Here is what you are buying on shared hardware: a disk scheduler that hands the disk to whoever has work for it, where your priority weight sets a share, not a speed limit. Reads also carry a per-account read limit, recalculated every five minutes, so a heavy reader is held to its own share instead of filling the queue that everyone's small reads wait in. Your priority is first claim on the disk when it is contested; your read limit and your plan's sustained ceiling bound how much you can pull at any time (all three are laid out below).
This works because the scheduler underneath is work-conserving: it never lets the disk sit partly idle while requests are waiting in its queue. It drives utilization toward 100% constantly, handing the full disk to whatever account is active right now. Priority weights only enter the picture during a genuine traffic jam — several accounts hammering the same platter in the same instant. Then, and only then, is the disk's bandwidth split by tier. The rest of the time, which is most of the time, the scheduler does not consult your priority weight, and you get what the hardware can give, up to your read limit for reads and your plan's sustained ceiling. In the scheduler, a higher priority weight does not get you more speed on a quiet disk; it gets you a bigger share of the disk during the moments it is genuinely fought over. (Where a higher plan is faster on a quiet disk, that comes from its higher sustained ceiling and its larger read limit, both covered next. The read limit's share follows the same priority weight.)
On a shared seedbox host, several customer accounts share the same physical disks. Most of the time this is invisible — when you are the only account doing heavy disk work, you get the full disk for writes, and reads up to your read limit. That is the single most important thing to understand:
When the disk is not busy, the scheduler does not use your priority weight. Nobody is held back to make room for a higher tier; every account runs at its own plan's full speed, with reads up to its read limit.
Tier priority only comes into play during contention — the moments when several accounts hammer the disk at the same instant (a mass re-check after a tracker rebalance, simultaneous imports, cross-seed bursts). During those windows, the disk's bandwidth is divided between the competing accounts in proportion to their priority weight. A higher tier gets a larger slice of that contested bandwidth; a lower tier gets a smaller slice. Once the burst clears, everyone returns to their own plan's full speed.
This is the right model for a seedbox: the scheduler's priority never throttles you when the disk is free and protects higher-tier customers' responsiveness when the disk is genuinely contested, and the read limit keeps one heavy reader from making everyone else's small reads wait.
Three controls: priority, the read limit and the plan's sustained ceiling
Most of this page is about one control: priority, your share of the disk when several accounts hit it at the same moment. It is not the only control. Every account also has a read limit on disk reads per second, and every plan carries a sustained ceiling: the upper bound on how much disk throughput the plan can pull continuously. The three are easy to mix up.
- Priority decides your slice of the disk under contention. When the disk is quiet it steps out of the way entirely and never throttles you; when the disk is busy, it keeps you from being shoved to the back of the line. This dial is what most of this page explains.
- Read limit is the most disk reads per second your account can make right now. It is recalculated every five minutes, applies whether or not other accounts are reading, and covers reads only. Its size follows your priority weight and your storage size. It is explained in its own section below.
- Sustained ceiling is the steady speed your plan tops out at. Unlike priority it is always present, busy disk or idle. Even alone on a completely quiet disk you will not go faster than your plan's ceiling. It is sized to the plan: the lowest-cost plans carry the smallest one, and a higher-grade plan is the way to a higher ceiling. SSD-backed plans are built for sustained throughput.
Priority never removes the ceiling, and the ceiling never changes your priority. So "full speed when the disk is quiet" on this page always means your plan's full speed, up to its sustained ceiling and, for reads, your read limit, not the raw physical limit of the drive. If you are alone on a quiet disk and still level off at a steady number, that is not priority at work; check the ceiling first. What the ceiling means for each plan, and how to get more sustained speed, is in the knowledge base article How Much Speed Does My Plan Include (And How to Get More).
Read limit
Accounts on Pulsed Media shared hosts have a read limit: the most disk reads per second the account can make at that moment. A shared disk answers requests from one queue. Without a limit, one account reading flat out, such as a media library scan or a full torrent recheck, can fill that queue, and every other account's small reads (a page of a web UI, a poster in a media server) wait behind it.
How the limit is set:
- Every five minutes the host sets a read budget from its typical read rate and how busy its disks have been recently. The typical rate is the highest of the host's daily 95th-percentile read rates over the past seven days. The budget is up to ten times that rate when the disks are quiet and twice it when they are busy. When the host's processors spend most of their time waiting on the disk, the budget is cut by up to a quarter.
- The budget is split between the accounts on the host. An account's share is its disk priority weight (plan, Free Bonus Disk percentage and the Disk IO priority extra) multiplied by its storage size relative to the host's median account.
- If an account's plan or configuration sets a fixed maximum, the account gets whichever is lower: that maximum or its share.
- The limit covers reads only. Writes are not limited.
- Unlike the scheduler's priority, the read limit applies whether or not other accounts are reading.
The info tab of the seedbox control panel shows the current value: "Disk reads for your account right now: up to N per second." When the account is reading at about that rate, the tab also says that further reads wait their turn until the disk is less busy or the account's reading slows.
To raise the limit, the Disk IO priority extra adds +100 to +400 to the priority weight (+300 on Dragon-R plans), from €9.99 a month on seedboxes and €19.99 on storage boxes, through the Upgrade Options link in the control panel. A larger plan carries a larger weight and more storage.
Hosts receive the read limit with their PMSS update. The line on the info tab appears once a host has it.
Measured effect, November 2026: on three of the first hosts to run read limits, where a small read had typically waited 7–16 ms, the hosts' own small-read latency probe (ioping, direct reads) fell 93–97%. The comparison was against hosts without read limits over the same clock hours, and the result held on two different baseline periods (late October, and the week before the change).
The implementation is /scripts/cron/cgroupIopsLimitApply.php in the public PMSS repository, described in its docs/cgroup.md.
Plan tiers and priority
Pulsed Media sells two product families on its shared hosts — active seedboxes and background-class storage boxes — and the plans group into a small set of priority bands. The band a plan sits in reflects two things: whether it is a storage box or an active seedbox, and where it sits in the price ladder.
At a glance, lowest to highest contested-disk priority:
| Band | Typical plans | Relative priority | Label |
|---|---|---|---|
| Idle floor | free trials, throwaway promos | 1 | Archive |
| Promo base | paid limited-edition promos | 5 | Archive |
| Storage | storage boxes, any size | 1–10 | Archive |
| Entry seedbox | cheapest active seedbox | 30 | Archive |
| Standard seedbox | mid-catalog seedboxes | 100 | Standard |
| Premium seedbox | upper-mid seedboxes | 250 | Standard |
| Pro / dedicated | top shared + dedicated-resource | 500 | Performance |
| Top dedicated | top dedicated seedbox | 700 | Performance |
The Label column is the tier name plans show next to their weight, on one ladder shared with CPU priority: Archive 1–99, Standard 100–499, Performance 500–999, and High 1000, which on disk is the kernel's maximum. No plan's base weight sits at 1000; an account's bonus multiplier can lift it there. The full ladder is on Seedbox and Storage Box Resource Tiers.
The step between adjacent paying tiers is mostly 2–3× per step, smaller at the very top (500 to 700). Each step up the catalog buys a markedly larger share of contended-disk priority: the top plan weight, 700, carries a 700-to-1 priority ratio against the idle floor during contention. The scale itself tops out at 1000.
Storage boxes deliberately sit low. A storage box is for holding data, not for winning a disk-bandwidth race against active seedboxes — so the whole storage line sits flat at a background priority. It is still offered the entire disk whenever the disk is uncontested (the work-conserving rule below), with reads up to its read limit; the low tier only matters during a genuine simultaneous-contention burst.
Two worked examples make the behavior concrete.
You alone on a quiet disk. You run your torrent client; nobody else is doing heavy disk work. The scheduler offers you the whole disk, and you run at your plan's full speed. The scheduler never consults your priority weight, because there is nothing to arbitrate; the bounds left standing are your read limit for reads and your plan's sustained ceiling.
Five accounts competing at peak. Suppose five accounts hit the disk simultaneously: two standard-tier (priority 100 each), one premium (250), one pro (500), one entry (30). Total contested weight is 980. During that contention window the pro account gets 500/980 ≈ 51% of bandwidth; the entry account gets 30/980 ≈ 3%; the two standard accounts get ~10% each. As soon as the burst eases — one account finishes, others go idle — the remaining accounts get more, up to their own plan's full speed when alone.
These are time-averaged shares over seconds-to-minutes. Over millisecond windows the scheduler hands each account a contiguous burst of disk service in turn, sized by its weight — this is what keeps a rotational disk from thrashing.
The point that ties both examples together: the scheduler never holds work back on a priority decision. Alone, your work goes straight to the disk. In contention, the competing accounts share it. The priority weight only decides whose work the disk serves during the contested window; the only thing priority changes is who wins the bandwidth during a genuine fight for it. If your seedbox feels slow and you want to know whether it is ordinary contention or something more, How to Tell if a Seedbox Is Oversold explains what to measure from your own account and what each number shows.
Every Pulsed Media seedbox and storage box runs on exactly this model — your plan's full speed when the platter is yours, protected priority when it is contested, and a read limit per account so that one heavy reader does not make everyone else wait, alongside the plan's own sustained ceiling. See the seedbox range and storage boxes, all on Pulsed Media's own hardware in Finland, from 1Gbps entry plans through 10Gbps performance tiers.
The deep technical detail
Everything below is the "dirt" — the kernel and systemd machinery that implements the priority model described above. Customers do not need it; sysadmins and the curious may want it.
What BFQ is, and why Pulsed Media uses it
The scheduler is BFQ — Budget Fair Queueing — designed by Paolo Valente, in the Linux kernel since 4.12. It is built for rotational HDD storage with many independent tenants sharing one disk.
The alternatives on a modern Linux host are typically mq-deadline (latency-bounded FIFO with light prioritization) and none (a passthrough that defers to hardware). Both suit single-tenant workloads. Neither implements proportional bandwidth sharing across cgroups during contention. Pulsed Media runs multi-tenant seedbox hosts where multiple accounts share rotational drives, so proportional sharing under contention is exactly the primitive needed.
BFQ provides three properties at once:
- Work-conserving — a single active account gets 100% of disk bandwidth regardless of weight. Weights only enforce ratios when multiple accounts compete at the same instant. (This is the "your priority weight does not matter on a quiet disk" property from above. The plan's sustained ceiling and the read limit are separate controls that sit outside the scheduler.)
- Budget-based dispatching — each cgroup gets a "budget" of sectors to dispatch before yielding to the next. Budget size is proportional to weight. This amortizes the seek cost across a chunk of contiguous-ish requests per cgroup — critical for HDD throughput.
- Proportional fairness over time-averaged windows — over seconds-to-minutes the bandwidth split converges to the weight ratio. Over milliseconds it does not; instantaneous slicing would seek-thrash the disk.
The trade-off is explicit: BFQ sacrifices strict instant proportionality for amortized throughput. On HDDs this trade is unambiguously correct. On NVMe SSDs the budget mechanism matters less — no seek cost — so weight ratio becomes a more direct bandwidth ratio.
Weight semantics: the kernel accepts 1 to 1000
Each cgroup carries a single integer weight in blkio.bfq.weight (cgroup v1) or io.bfq.weight (cgroup v2). The kernel accepts 1 to 1000, default 100.
The weight is a proportional-sharing coefficient. During contention, a cgroup with weight W gets a share of disk bandwidth proportional to W divided by the sum of weights across all actively-competing cgroups. If account A (weight 100) and account B (weight 400) are the only two competing, B gets 400/(100+400) = 80% and A gets 20%. At the extremes, an account at weight 200 competing with one at weight 2 gets 200/202, about 99% of the disk against about 1%: 100 times the share, for as long as both are busy.
Three properties matter:
- A high weight does not guarantee high bandwidth. It guarantees a high share if and only if there is contention. An idle high-weight account gets nothing extra.
- A low weight does not starve an account when the disk is uncontested.
- The relative ratio of weights matters, not absolute values. Weights 100 and 400 behave identically to 25 and 100 — same 1:4 split.
The systemd v252 cgroup-v1 translation chain
The kernel exposes the full 1–1000 range directly. But when systemd writes the weight (via the unit property IOWeight=N, any integer 1–10000), it goes through a translation step first.
On cgroup-v2 hosts, systemd writes io.bfq.weight directly via its set_bfq_weight() function, mapping the 1–10000 input onto the 1–1000 kernel range with its BFQ_WEIGHT() macro.
On cgroup-v1 hybrid hosts, systemd writes blkio.bfq.weight instead — and on this path, v252's source code chains two transformations:
cgroup_weight_io_to_blkio()first maps the systemd IOWeight (1–10000) onto the legacy CFQ-erablkio.weightspace (10–1000) usingIOWeight × 5, clamped to [10, 1000].BFQ_WEIGHT()then maps that 10–1000 value onto the kernelblkio.bfq.weightwith a piecewise linear curve.
The first transformation saturates at IOWeight = 200. Once IOWeight × 5 ≥ 1000, the clamp kicks in and the input to BFQ_WEIGHT() becomes constant 1000 regardless of the original IOWeight. BFQ_WEIGHT(1000) = 181, so every value with systemd IOWeight ≥ 200 lands at kernel bfq.weight = 181. Below 200, the chain produces a compressed mapping: IOWeight = 100 → kernel 136, IOWeight = 50 → kernel 113.
This was verified empirically by writing values via systemctl set-property and reading back blkio.bfq.weight from the kernel cgroup file. Every IOWeight from 200 upward produced the same kernel value: 181. Values below 200 produced a narrow band of 50–181.
The practical implication: any two systemd IOWeight values at or above 200 — say 3000 and 12500 — produce the same kernel bfq.weight = 181 at the layer that actually controls bandwidth. Differentiation expressed through the systemd IOWeight setting vanishes at the kernel. This is precisely why a direct-write approach is needed to deliver tiered priority on cgroup-v1 hosts.
The maintainers acknowledge this
This is not a misunderstanding. The systemd maintainers describe the cgroup-v1 BFQ situation in their own words. From systemd PR #20522 (August 2021), Lennart Poettering:
urks, bfq. we should have never merged support for that mess. systemd should not be the place to work around kernel politics. And it sucks we always make two writes to these files, of which one then fails.
From systemd issue #21187 (November 2021), Poettering again:
Honestly, I'd just drop the whole bfq stuff. It's clearly not ready if you ask me, I am really not sure why we bother at all.
In the same thread, Zbigniew Jędrzejewski-Szmek:
Yeah, it would have been wise to not step into this mess in the first place. But I think it's too late now: people are using bfq, and if we rip out support, people will be unhappy.
The exact bandwidth-mis-distribution issue — systemd issue #27622 — has been open since May 2023 with no fix queued. An alternate clamp-only proposal was not merged in favor of the current chained scaling, which the maintainers describe as preferring a uniform API. The translation chain is upstream-deliberate, not a Debian-specific patch.
Pulsed Media's bypass: direct kernel write with self-heal
The kernel accepts 1–1000 written directly to blkio.bfq.weight on cgroup-v1 hosts. PMSS deploys a small cron job that reads each account's intended weight from its per-user configuration, computes the desired kernel value, and writes it directly to the cgroup file — bypassing the systemd chain entirely.
The mechanism is /scripts/cron/cgroupBfqWeightApply.php in the public PMSS repository, GPL-3.0. Anyone running Debian 12 with systemd 252+ and cgroup-v1 hybrid mode can read or adapt it.
Behavior:
- Runs every five minutes via the PMSS root cron, plus once after reboot.
- Reads each per-user configuration and computes the target weight.
- Writes the target to
/sys/fs/cgroup/blkio/user.slice/user-N.slice/blkio.bfq.weightonly when the current kernel value differs. Idempotent — no writes on no-op cycles. - Self-heals systemd overwrite events. Several systemd operations (
daemon-reload,systemctl set-property, user-slice reactivation) re-apply the chain-derived value. The cron catches the overwrite within at most five minutes and restores the intended value. - Falls back to a deterministic formula when no explicit weight is set:
round(0.6905 × √ramMiB)clamped to [1, 250], a smooth curve from small accounts up through larger ones (250 is reached at 128 GiB). An account's bonus multiplier is applied on top, and the result is capped at the kernel maximum of 1000.
The five-minute cycle balances catching overwrites quickly against not generating unnecessary writes. In practice, systemd overwrite events are uncommon — most cycles write nothing.
Cgroup v1 versus v2
The translation chain is specific to cgroup-v1 hybrid mode. On cgroup-v2 unified hosts, systemd writes io.bfq.weight directly via set_bfq_weight(), which does not chain the two transformations — so the saturation-at-200 behavior does not occur there.
So why run cgroup-v1 at all, when Debian 12 defaults to cgroup-v2? It is a deliberate choice in service of a customer feature. Pulsed Media forces the legacy hierarchy (systemd.unified_cgroup_hierarchy=0 on the kernel command line) so that rootless Docker keeps working on customer seedboxes — rootless Docker on current kernels needs the v1 hierarchy and must coexist with the process-isolation hardening Pulsed Media runs on every host. That single decision — keep rootless Docker available to customers — is what places the whole fleet on the v1 path, and the v1 path is the one carrying the broken systemd BFQ translation chain. The direct-write cron is the price of that trade: it neutralizes the chain so customers get both rootless Docker and correct tiered disk priority.
Pulsed Media's current production hosts therefore run cgroup-v1 hybrid mode. The direct-write cron delivers correct kernel weights on that configuration at no cost to running services. Where a host runs cgroup-v2 unified mode, systemd already writes the correct value and the cron simply finds nothing to change on each cycle.
Understanding the weight on a host
The value that determines a cgroup's proportional disk-bandwidth share lives in blkio.bfq.weight under the account's slice. Remember the work-conserving rule: this weight only affects bandwidth during contention. When a disk is uncontested, the scheduler offers every account, regardless of weight, the full disk; what it then sustains is bounded by its read limit and its plan's ceiling. Different tiers carrying different weights changes nothing about the scheduler's behaviour when you are the only active workload on the disk; the read limit, which does use the weight, is a separate control.
For sysadmins running similar Debian 12 / systemd 252 / cgroup-v1 setups: the same translation chain affects you. The PMSS direct-write script (public source) is adaptable — the core logic is around 100 lines of PHP, portable to bash or any language. GPL-3.0.
References
- Linux kernel BFQ documentation:
Documentation/block/bfq-iosched.rst - Paolo Valente, "BFQ: Budget Fair Queueing storage-I/O scheduler" (algorithm source)
- systemd v252 source,
src/core/cgroup.candsrc/basic/cgroup-util.h - systemd PR #20522 (August 2021): https://github.com/systemd/systemd/pull/20522
- systemd issue #21187 (November 2021): https://github.com/systemd/systemd/issues/21187
- systemd issue #27622 (May 2023, open): https://github.com/systemd/systemd/issues/27622
- PMSS source, GPL-3.0: https://github.com/MagnaCapax/PMSS — see
scripts/cron/cgroupBfqWeightApply.php(priority) andscripts/cron/cgroupIopsLimitApply.php(read limit)