Skip to content

feat(server): disk usage accounting - #4488

Open
andreinnanu wants to merge 13 commits into
apache:masterfrom
andreinnanu:feat/disk-usage-accounting
Open

andreinnanu wants to merge 13 commits into
apache:masterfrom
andreinnanu:feat/disk-usage-accounting

Conversation

@andreinnanu

@andreinnanu andreinnanu commented Oct 11, 2026 •

Copy link
Copy Markdown

Which issue does this PR address?

Relates to #4140

This PR alone doesn't solve the issue. A following one will.

Rationale

Enables accounting disk usage, preparing the ground for the configurable disk usage limit discussed within #4140.

As agreed there, this is the first of two PRs: accounting here, the limit and the refusal of new messages in the follow-up.

What changed?

Iggy had no view of how full its data volume is, so the disk usage limit planned in #4140 had nothing to check against.
A periodic statvfs sample alone is not enough: a burst can fill the remaining space between two samples.

A process-wide estimate (DISK_USAGE) of the bytes used on the data volume was added. The write sites charge what they allocate into a total the sampler never touches, and the existing iggy-system-stats sampler re-bases the estimate on statvfs every second, so writes on the volume that Iggy does not control show up with the next sample. Used means total minus free blocks, as df reports it (/stats free is f_bavail, so the two differ by the root reserve).

Preallocated segments charge only what the reservation newly allocates, and writes into a segment are charged only past what its reservation actually covers, measured by the file's st_blocks. A reservation that fails, is unsupported, or succeeds without allocating (OpenZFS) covers nothing, so its writes are charged.

The write sites charge what they allocate, and the existing iggy-system-stats sampler seeds the estimate at boot and re-bases it from statvfs, so writes on the data volume which are not under Iggy's control are seen with every sample.

Two series are exported on /metrics:

  • disk_used_bytes (gauge): the estimated bytes used on the data volume, as total minus free blocks like df. Not exported until the first sample.
  • disk_allocated_bytes_total (counter): the bytes Iggy allocated on the data volume since the process started. Samples never move it, so its increase over an interval shows what Iggy wrote apart from everything else on the volume.

The estimate errs towards a fuller disk: frees (deleted segments, truncations) are seen only at the next sample, and a failed write or reservation stays counted until then. It assumes statvfs shows a finished write right away, as on ext4 and XFS. On btrfs and OpenZFS it can read low for up to one commit interval after a sample;

Open question based on #4140 discussion

How do we want to approach freezing the sampler?

The goal was to add a burst test with the sampler frozen, but currently there is no mechanism for that. Reusing sysinfo_print_interval doesn't work because its minimum value is 1s.

Local Execution

  • Passed: /metrics includes the disk_used_bytes gauge and its value and follows df while a producer writes messages
  • Pre-commit hooks pass

AI Usage

  • used Claude Code for generating tests & some of the functions
  • verified by manual tests & code review

@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer
  • /pin - exempt the PR from the stale bot, /unpin to undo

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Oct 11, 2026
@justinmclean

Copy link
Copy Markdown
Member

Thanks @andreinnanu for this. @hubcio, can you answer the open question on freezing the sampler for the burst test?

@hubcio

hubcio commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@andreinnanu right, the sampler interval is capped at 1 s whatever sysinfo_print_interval is. i'd drop the freeze i asked for anyway: it needs a server switch whose only job is to turn off the re-base PR2 relies on.

split the counter instead:

  • write sites fetch_add into a charged total the sampler never touches.
  • the sampler stores base = sample - charged (wrapping), with charged read before statvfs as now. used_bytes() returns base + charged.
  • /metrics exports the total as a counter, e.g. disk_charged_bytes (prometheus-client adds _total).

the burst test then compares the counter delta with the allocated_bytes(data_path) delta within a stated bound, reading both once the counter stops moving. keep it under the 128 MiB WAL checkpoint, because the counter never sees frees. the sampler keeps running and the test can run beside others. it also replaces the between-samples test, whose 1 MiB tolerance can't see a missing WAL charge.

also run it with preallocate_segments on. writes into a reserved segment are charged twice: preallocate_file charges the fallocate, then write_segment_bodies / write_frozen_chunked charge the same bytes again. on 89fb465 an 8 MiB burst into such a topic leaves disk_used_bytes 8 MiB above df until the next sample. charge only what lands past the reservation, and reset the reserved length on truncate, since ftruncate frees the reservation.

@hubcio

hubcio commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

/skill team-review-slim

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: disk_used_bytes overstates usage by the bytes written between samples when a topic sets preallocate_segments, because the segment reservation and every write into it both charge the same blocks; the review also flags an unused harness helper and a redundant length sum. A reported #[serial]​ isolation gap in the new tests was dropped as unproven.

Counts: critical 0, warning 1, nit 0, simplification 2


This review was generated by Claude Code 2.1.284 on deepseek-flash[1m]. Review the output before you act on it.

Comment thread core/partitions/src/messages_writer.rs Resolved
Comment thread core/integration/src/harness/disk.rs Resolved
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Oct 12, 2026
Comment thread core/partitions/src/state_transfer.rs Resolved
Comment thread core/journal/src/durable_storage.rs Resolved
Comment thread core/server_common/src/fs_utils.rs Resolved
Comment thread core/server/src/http/metrics.rs Resolved
Comment thread core/server_common/src/disk_usage.rs Resolved
@andreinnanu

Copy link
Copy Markdown
Author

Tested by manually freezing the sampler interval (adding a big extra sleep in the binary for testing purposes):

Test scripts:

  • measuring script, with a 0.2s interval, checking the /metrics endpoint and du for data dir size

  • 5 producers, producing data over 5 different topics

sending 32 x 65536 B per request to disk-lab/{burst-0, burst-1, burst-2, burst-3, burst-4}, partition 0, rate 64.00 MiB/s
batches     976   sent    1952.00 MiB   rate    16.27 MiB/s
done: 976 requests, 1952.00 MiB in 120.01 s (16.27 MiB/s)
  • 10 consumers, consuming the produced data
10 consumers polling disk-lab/burst-0..4, 1 partition(s) each, 16 messages per poll
polls     8246   messages    131936   read    8246.00 MiB      68.52 MiB/s
done: 8246 polls, 131936 messages, 8246.00 MiB in 120.34 s

Results:

  • Preallocation off
largest overcount (estimate grew more than the data directory):       34.18 MiB
largest undercount (estimate grew less than the data directory):       0.05 MiB
  • Preallocation on
largest overcount (estimate grew more than the data directory):      209.39 MiB
largest undercount (estimate grew less than the data directory):       0.04 MiB

Because of different frees that are not taken into account, the overcount has the tendency to grow between samples, when it gets reseted. Is this something we should consider at this point?

Also, the large overcounts are usually spikes created by the measuring script, the diff looks like this over time:

23:28:42.155     983.45     966.20      17.25
23:28:42.475     983.50     966.22      17.28
23:28:42.772     983.59     966.32      17.27
23:28:43.033     983.64     966.38      17.26
23:28:43.369    1175.90     966.52     209.39
23:28:43.768    1175.99    1158.70      17.30
23:28:44.053    1176.04    1158.79      17.26
23:28:44.397    1176.19    1158.93      17.26
23:28:44.659    1176.24    1158.99      17.26
23:28:44.947    1176.35    1159.09      17.26

@andreinnanu
andreinnanu requested a review from hubcio October 19, 2026 20:08
@hubcio

hubcio commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

@andreinnanu i did a quite a big change to server and all related crates in PR #4561. could you please rebase this PR?

@andreinnanu

andreinnanu commented Oct 20, 2026 •

Copy link
Copy Markdown
Author

@andreinnanu i did a quite a big change to server and all related crates in PR #4561. could you please rebase this PR?

Yes, i can take care of that. Rebasing with current master should be good enough, right @hubcio?

@hubcio

hubcio commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

yes @andreinnanu

@andreinnanu
andreinnanu force-pushed the feat/disk-usage-accounting branch from 61da1db to f3d01a9 Compare October 20, 2026 13:39
@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.96154% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.04%. Comparing base (55ae03c) to head (0985b9b).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
core/partitions/src/offset_storage.rs 87.50% 6 Missing ⚠️
core/server_common/src/fs_utils.rs 95.06% 2 Missing and 2 partials ⚠️
core/journal/src/partition_journal.rs 92.30% 3 Missing ⚠️
core/server_common/src/disk_usage.rs 98.42% 3 Missing ⚠️
core/server/src/http/metrics.rs 96.61% 2 Missing ⚠️
core/journal/src/partition_journal/segments.rs 98.07% 0 Missing and 1 partial ⚠️
core/partitions/src/segment_recovery.rs 80.00% 0 Missing and 1 partial ⚠️
core/server/src/sysinfo_probe.rs 85.71% 0 Missing and 1 partial ⚠️

❌ Your patch check has failed because the patch coverage (30.39%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #4488      +/-   ##
============================================
+ Coverage     91.03%   91.04%   +0.01%     
+ Complexity     3877     3876       -1     
============================================
  Files          1561     1562       +1     
  Lines        403726   404216     +490     
  Branches     337601   338091     +490     
============================================
+ Hits         367518   368022     +504     
+ Misses        28849    28821      -28     
- Partials       7359     7373      +14     
Components Coverage Δ
Rust Core 92.28% <95.96%> (+<0.01%) ⬆️
Java SDK 79.38% <ø> (-0.01%) ⬇️
C# SDK 84.47% <ø> (+0.04%) ⬆️
Python SDK 86.51% <ø> (ø)
PHP SDK 92.23% <ø> (ø)
Node SDK 96.62% <ø> (+0.02%) ⬆️
Go SDK 71.79% <ø> (+0.08%) ⬆️
C++ SDK 86.84% <ø> (ø)
Files with missing lines Coverage Δ
core/journal/src/durable_storage.rs 83.37% <100.00%> (+0.26%) ⬆️
core/journal/src/file_storage.rs 77.35% <100.00%> (+0.43%) ⬆️
core/journal/src/prepare_journal.rs 94.83% <100.00%> (+<0.01%) ⬆️
core/partitions/src/iggy_index_writer.rs 93.80% <100.00%> (ø)
core/partitions/src/messages_writer.rs 92.90% <100.00%> (+0.54%) ⬆️
core/partitions/src/state_transfer.rs 85.18% <100.00%> (+0.01%) ⬆️
core/partitions/src/wal_storage.rs 70.80% <100.00%> (+0.96%) ⬆️
core/journal/src/partition_journal/segments.rs 87.37% <98.07%> (+0.75%) ⬆️
core/partitions/src/segment_recovery.rs 89.69% <80.00%> (+0.01%) ⬆️
core/server/src/sysinfo_probe.rs 91.56% <85.71%> (-0.84%) ⬇️
... and 5 more

... and 50 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@andreinnanu

Copy link
Copy Markdown
Author

yes @andreinnanu

rebased

@andreinnanu

Copy link
Copy Markdown
Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Oct 21, 2026
@hubcio

hubcio commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

could you please rebase this PR?

@hubcio hubcio added the S-waiting-on-author PR is waiting on author response label Nov 4, 2026
@hubcio hubcio removed the S-waiting-on-review PR is waiting on a reviewer label Nov 4, 2026
Keep disk accounting compatible with the updated base. Preserve
master's staging-file replacement safety and metric label escaping,
alongside the disk usage collectors and test isolation.

Forward allocation charges through master's WAL storage adapter so
workspace and simulator-enabled builds keep charging disk writes.

@hubcio hubcio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main open points are the btrfs/openzfs statvfs lag after rebase, writes left uncharged when fallocate fails or does nothing, and the silent no-op charge_allocation default.

pub fn rebase<T>(&self, measure: impl FnOnce() -> Option<(T, u64)>) -> Option<T> {
// Pairs with the release in `Charge::drop`: every write settled here
// finished before the volume is measured.
let settled = self.settled.load(Ordering::Acquire);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: btrfs and openzfs move f_bfree only at the next commit (30 s on btrfs), so after rebase drops settled charges the gauge reads low until then. keep settled charges past a commit on those filesystems, or document the ext4/xfs assumption.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not consider multiple types of filesystems. Is there an official list that Iggy supports?
I tried to look in the docs, but no luck so far.
I would like to cover all the supported ones.

@hubcio hubcio Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just stick to popular ones - ext4/btrfx/xfs/zfs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After working on it for a while, I chose to document the assumption and fix it in a future issue.
Should I write it?

Comment thread core/partitions/src/messages_writer.rs Resolved
Comment thread core/journal/src/durable_storage.rs Resolved
#[inline]
pub fn charge(&self, bytes: u64) -> Charge<'_> {
if bytes > 0 {
self.charged.fetch_add(bytes, Ordering::Relaxed);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: all shards bump the same two global atomics on every charge, so these cache lines bounce between cores. profile a many-shard small-batch run before deciding whether per-shard slots are needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is one of the things I was trying to avoid with the initial proposal.
I will do the profiling and come back with an answer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure - thats why i treat it as a nit. i dont think there will be visible impact until we hit 10M+++ calls/s

Comment thread core/server/src/http/metrics.rs Resolved
Comment thread core/server_common/src/fs_utils.rs Resolved
Comment thread core/server_common/src/fs_utils.rs Resolved
Comment thread core/server/src/http/metrics.rs Resolved

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author PR is waiting on author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants