Skip to content

ZIL: Store aligned full-block sync writes indirectly#18746

Draft
MorganaFuture wants to merge 2 commits into
openzfs:masterfrom
MorganaFuture:zvol-fullblock-indirect
Draft

ZIL: Store aligned full-block sync writes indirectly#18746
MorganaFuture wants to merge 2 commits into
openzfs:masterfrom
MorganaFuture:zvol-fullblock-indirect

Conversation

@MorganaFuture

Copy link
Copy Markdown

Motivation and Context

Closes #17677.

On a zvol with sync=always (e.g. a VM disk image), a synchronous write that
exactly covers an aligned volblocksize block is copied into the ZIL
(WR_COPIED) and then written again at TXG commit — the data hits the pool
twice. For small-volblocksize zvols under a sync-heavy workload this doubles the
write volume.

Description

Store aligned full-block sync writes indirectly (WR_INDIRECT) so the block
reaches the pool only once. zil_write_state() gains offset/blk_fixed
parameters; for a zvol (fixed volblocksize) a write whose offset and length are
whole-block aligned takes the indirect path via dmu_sync(), and the log record
just references the block.

The existing heuristics are preserved for everything else: a dedicated SLOG
still keeps full-block writes in the log, sub-block and misaligned writes are
unchanged, and the ZPL path is untouched (blk_fixed = B_FALSE).

How Has This Been Tested?

New zvol_misc_fullblock ZTS case: full-block sync writes increase the objset's
zil_itx_indirect_count, sub-block writes do not, and a dedicated SLOG keeps
even full-block writes in the log. Because this changes which writes are logged
indirectly, the test also freezes a dedicated pool, writes full blocks, then
exports and re-imports it and checks the zvol is byte-for-byte identical — so the
WR_INDIRECT records are actually claimed and replayed correctly. Ran on Linux
(Ubuntu, aarch64, --enable-debug).

This is a draft — I'd like reviewer guidance on the design before polishing:

  • The indirect path is currently unconditional for full-block zvol sync writes,
    so it overrides zfs_immediate_write_sz / logbias=latency. On a small
    volblocksize zvol without a SLOG this trades halved write volume for more (and
    more random) main-pool IOPS. Should it be gated behind a module parameter or a
    size floor?
  • It also affects async full-block writes, and these writes bypass the DDT
    (dmu_sync disables dedup). Are those acceptable, or should the predicate be
    narrowed?

Types of changes

  • Performance enhancement (non-breaking change which improves efficiency)
  • Documentation (a change to man pages or other documentation)

Checklist:

  • My code follows the OpenZFS code style requirements.
  • I have updated the documentation accordingly.
  • I have read the contributing document.
  • I have added tests to cover my changes.
  • I have run the ZFS Test Suite with this change applied.
  • All commit messages are properly formatted and contain Signed-off-by.

zil_write_state() stores every synchronous write smaller than
zfs_immediate_write_sz in the ZIL (WR_COPIED, or WR_NEED_COPY for
O_DIRECT), even when the write exactly covers an aligned block.  Such
a write then reaches the pool twice: once into the log block and again
when the TXG commits.  On a zvol with a small volblocksize this
doubles the write volume of every synchronous full-block write.

A write that exactly covers one or more aligned blocks whose size can
no longer change is always safe to store indirectly.  dmu_sync()
performs no read-modify-write, the block is not rewritten at commit,
and a later rewrite of the same block cannot inflate it, so the data
is written to the pool only once.

Pass the write offset and a new blk_fixed flag to zil_write_state()
and, when the write is an aligned multiple of a fixed block size,
choose WR_INDIRECT regardless of zfs_immediate_write_sz.  Volumes set
blk_fixed=B_TRUE, since volblocksize is immutable; the file path
passes B_FALSE and keeps its current behavior.  The dedicated-SLOG and
special-vdev decisions are left in place, so a log device still keeps
these writes in the ZIL for latency.

Measured on a 16K-volblocksize zvol with sync=always, 64 aligned 16K
O_DIRECT sync writes: zil_itx_indirect_count rises from 0 to 63, and
the duplicate commit-time write is gone.  Partial writes and pools
with a dedicated SLOG are unaffected.

Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
Closes openzfs#17677
@github-actions github-actions Bot added the Status: Work in Progress Not yet ready for general review label Jul 5, 2026
@MorganaFuture MorganaFuture force-pushed the zvol-fullblock-indirect branch 2 times, most recently from 6d3952f to 7dc9b1a Compare July 5, 2026 15:39
Verify that a synchronous write covering an aligned volblocksize block
on a zvol is stored indirectly: full-block sync writes must increment
the objset's zil_itx_indirect_count, sub-block writes must not, and a
dedicated SLOG must keep even full-block writes in the log.

Because this changes which writes are logged indirectly, the test also
freezes a dedicated pool, writes full blocks, then exports and
re-imports it so the WR_INDIRECT records are claimed and replayed; the
zvol contents must be byte-for-byte identical afterwards.  This guards
the actual risk of the change: an indirectly logged block that cannot
be read back at import.

The test drives aligned block writes with dd oflag=direct, which is
Linux-only, so it is recorded as an expected skip on FreeBSD.

Exercises the fix for the zvol write amplification in openzfs#17677.

Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
Closes openzfs#17677
@MorganaFuture MorganaFuture force-pushed the zvol-fullblock-indirect branch from 7dc9b1a to 620a4bc Compare July 5, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Work in Progress Not yet ready for general review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ZFS write amplification when writing to zvols

1 participant