Chapter 14: Deduplication Tables and Block Cloning¶
Source:
include/sys/ddt.h,include/sys/ddt_impl.h,include/sys/brt.h,include/sys/brt_impl.h,include/sys/dmu.h,include/sys/fs/zfs.h,module/zfs/ddt.c,module/zfs/ddt_log.c,module/zfs/ddt_zap.c,module/zfs/ddt_stats.c,module/zfs/brt.c,module/zcommon/zfeature_common.c
OpenZFS has two distinct reference-tracking mechanisms for shared blocks:
- DDT (Deduplication Table): checksum-keyed, automatic dedup.
- BRT (Block Reference Table): offset-keyed, explicit block cloning.
Both are on-disk metadata systems and can coexist on one pool.
14.1 DDT Key Format (ddt_key_t)¶
A DDT key is:
ddk_cksum(zio_cksum_t, 32 bytes)ddk_prop(uint64_t, 8 bytes)
Total key size: 40 bytes.
ddk_prop bit layout:
63 40 39 38 32 31 16 15 0
+-----------------------------+--+------------+------------+-------------+
| 0 |X | COMPRESS | PSIZE | LSIZE |
+-----------------------------+--+------------+------------+-------------+
LSIZEandPSIZEare encoded inSPA_MINBLOCKSHIFTunits.COMPRESSis the compression algorithm id.Xis the crypt flag (DDK_GET_CRYPT), indicating block-pointer crypt semantics (BP_USES_CRYPT), including encrypted/authenticated crypt handling.
14.2 DDT Value Formats (ddt_univ_phys_t)¶
ddt_univ_phys_t has two on-disk payload modes.
Traditional payload (ddp_trad)¶
- Four 64-byte slots (
DDT_PHYS_DITTO,SINGLE,DOUBLE,TRIPLE) - Total value size: 256 bytes
Each slot layout:
| Offset | Size | Field |
|---|---|---|
0x00 |
48 | ddp_dva[3] |
0x30 |
8 | ddp_refcnt |
0x38 |
8 | ddp_phys_birth |
Notes:
DDT_PHYS_DITTOis obsolete and not generated for new entries.- Class selection is based on total refcount (
UNIQUEfor1,DUPLICATEfor>1).
Flat payload (ddp_flat, feature@fast_dedup)¶
Single value layout (72 bytes):
| Offset | Size | Field |
|---|---|---|
0x00 |
48 | ddp_dva[3] |
0x30 |
8 | ddp_refcnt |
0x38 |
8 | ddp_phys_birth |
0x40 |
8 | ddp_class_start (realtime seconds) |
In flat mode, new entries have one physical payload (DDT_PHYS_FLAT).
14.3 DDT Objects in MOS¶
DDT metadata is per-checksum (sha256, sha512, skein, edonr, blake3).
Legacy layout (DDT_VERSION_LEGACY)¶
Objects are referenced directly from DMU_POOL_DIRECTORY_OBJECT with names:
DDT-<checksum>-<type>-<class>(DMU_POOL_DDT)
Current type is zap, classes are ditto, duplicate, unique.
Fast dedup layout (DDT_VERSION_FDT, feature@fast_dedup)¶
Each checksum gets a directory object in MOS:
DDT-<checksum>(DMU_POOL_DDT_DIR)
Directory entries include:
version(DDT_DIR_VERSION)flags(DDT_DIR_FLAGS)- per-object links named
DDT-<checksum>-<type>-<class> - optional dedup log object links (
DDT-log-<checksum>-0/1, created lazily on first log use)
Version/flags mapping in current OpenZFS:
DDT_VERSION_LEGACY=> no flagsDDT_VERSION_FDT=>DDT_FLAG_FLAT | DDT_FLAG_LOG
DMU_POOL_DDT_STATS (DDT-statistics) stores histogram payloads (ddt_histogram_t) keyed by DDT object name.
Allocation routing note:
- DDT objects (
DMU_OT_DDT_ZAP) are routed by allocation class policy: dedicated dedup class first (if present), then special class when DDT-on-special is enabled, otherwise normal class.
14.4 DDT Log On Disk (DDT_FLAG_LOG)¶
Fast dedup uses an append log to avoid heavy random ZAP rewrites every txg.
Each log object:
- Type:
DMU_OTN_UINT64_METADATA - Data block size:
SPA_OLD_MAXBLOCKSIZE - Bonus:
ddt_log_header_t(64 bytes)
Log object dnodes have their storage type set to DMU_OT_DDT_ZAP
(dnode_set_storage_type), so log blocks follow the same dedup/special
allocation-class routing as the DDT ZAP objects (see 14.3).
ddt_log_header_t:
| Offset | Size | Field |
|---|---|---|
0x00 |
8 | dlh_info (version + flags) |
0x08 |
8 | dlh_length |
0x10 |
8 | dlh_first_txg |
0x18 |
40 | dlh_checkpoint (ddt_key_t) |
dlh_info bits:
- Bits
7:0: log version (currently1) - Bits
15:8: log flags (DDL_FLAG_FLUSHING,DDL_FLAG_CHECKPOINT)
Log records are packed entries with ddt_log_record_t headers (dlr_info) plus payload.
For DLR_ENTRY records:
- record type in bits
7:0 - record length in bits
23:8(16-bit field) - entry type in bits
55:48 - entry class in bits
63:56
Record payload is ddt_log_record_entry_t:
dlre_key(ddt_key_t)dlre_phys(ddt_univ_phys_tpayload sized for current DDT mode)
Typical record sizes (8-byte aligned):
- flat mode: 120 bytes
- traditional mode: 304 bytes
In current OpenZFS FDT mode (DDT_FLAG_FLAT | DDT_FLAG_LOG), dedup log records are flat-format.
14.5 DDT ZAP Entry Encoding¶
DDT storage type zap uses uint64-array keys and compressed values:
- Key:
ddt_key_tinterpreted asuint64[5] - Value: compressed phys payload (
72or256bytes before compression)
A one-byte value prefix stores:
- compression function id (
DDT_ZAP_COMPRESS_FUNCTION_MASK) - host byteorder bit (
DDT_ZAP_COMPRESS_BYTEORDER_MASK)
Compression path uses ZLE by default, with fallback to uncompressed (ZIO_COMPRESS_OFF) if no gain.
14.6 BRT On-Disk Objects (Block Cloning)¶
BRT tracks explicit clone references by physical offset, per top-level vdev.
MOS directory anchor¶
For each top-level vdev id N with active BRT entries:
com.fudosecurity:brt:vdev:<N>-> object id of that vdev's BRT metadata object
(BRT_OBJECT_VDEV_PREFIX in DMU_POOL_DIRECTORY_OBJECT.)
Per-vdev metadata object¶
Object type: DMU_OTN_UINT64_METADATA
- Data area:
uint16entry-count array - Data block size:
BRT_BLOCKSIZE(32 KiB) - Bonus:
brt_vdev_phys_t(56 bytes)
brt_vdev_phys_t layout:
| Offset | Size | Field |
|---|---|---|
0x00 |
8 | bvp_mos_entries (entries ZAP object id) |
0x08 |
8 | bvp_size (number of uint16 counters) |
0x10 |
8 | bvp_byteorder |
0x18 |
8 | bvp_totalcount |
0x20 |
8 | bvp_rangesize |
0x28 |
8 | bvp_usedspace |
0x30 |
8 | bvp_savedspace |
Entry-count array semantics:
- One
uint16counter per region of sizebvp_rangesize. - Default range size is
BRT_RANGESIZE(16 MiB). - Counter value is the number of BRT entries whose offsets fall in that region.
Per-vdev entries ZAP¶
bvp_mos_entries points to a ZAP object keyed by offset:
- Key: one
uint64(first DVA offset within this top-level vdev) - Value:
uint64reference count
This object is created with ZAP_FLAG_HASH64 | ZAP_FLAG_UINT64_KEY.
Its DMU object type is DMU_OTN_ZAP_METADATA, and its dnode storage type
is set to DMU_OT_DDT_ZAP.
BRT object lifecycle is lazy:
- Created on first real clone references for that top-level vdev
- Destroyed when the per-vdev BRT entry count drops to zero
14.7 feature@block_cloning_endian¶
Early block cloning pools stored BRT ZAP value metadata with legacy integer layout.
The feature@block_cloning_endian fix changes new BRT ZAP values to native uint64 layout.
On-disk compatibility behavior in brt.c:
- Pools without active endian-fix feature use legacy lookup/update parameterization.
- Pools with active fix use corrected uint64 parameterization.
This preserves readability of older pools while fixing new writes.
14.8 DDT and BRT Interaction¶
When cloning a block whose BP has dedup (D) bit set, BRT defers to DDT:
brt_pending_apply()callsddt_addref().- If DDT entry exists, no BRT entry is created.
- If DDT entry is missing (e.g., pruned), BRT can still track it via normal BRT path.
This avoids maintaining duplicate reference state in both tables for the same block.
14.9 Feature Flags and On-Disk Impact¶
| Feature | Description | On-Disk Impact |
|---|---|---|
feature@fast_dedup (com.klarasystems:fast_dedup) |
Flat + logged DDT format | Enables per-checksum DDT-<checksum> directory objects, version/flags, flat phys payloads, and DDT-log-* objects |
feature@block_cloning (com.fudosecurity:block_cloning) |
Block Reference Tables | Adds per-vdev BRT objects anchored by com.fudosecurity:brt:vdev:<id> and offset->refcount ZAP entries |
feature@block_cloning_endian (com.truenas:block_cloning_endian) |
BRT ZAP endianness fix | Switches BRT ZAP value encoding path to corrected uint64 layout for new pools/entries |
Notes:
- Dedup itself is not a standalone
feature@dedupflag in OpenZFS. The base dedup mechanism predates feature flags; modern format upgrades are represented byfeature@fast_dedup. - DDT and BRT both remain backward-compatible with older on-disk entries through format/version checks at load time.