The Comprehensive Ceph Guide
فارسیGet started
On this page
Specialist Part · Chapter 37

Specialist Part (4): RBD — Layout on RADOS, Striping, Object Map and Fast-Diff, and the rbd Reference

17 min readEdit this page
TL;DR this chapter in a few lines
  • A format 2 image is rbd_id, an rbd_header.<id> omap and data objects rbd_data.<id>.<objectno>; metadata changes only through the rbd class.
  • Default striping is consecutive 4 MiB objects; custom striping spreads small sequential writes round-robin over stripe_count objects.
  • The object map keeps two bits per object, so fast-diff and rbd du need not query every object.
  • The chapter ends with an rbd command reference.

37-1 Before we begin#

Chapter 2 showed which objects a format 2 image has and how its id is generated (algorithm 2-1). This chapter opens that same layout key by key: exactly which omap keys an image header has, what the pool-level objects (rbd_directory, rbd_trash, rbd_mirroring, rbd_namespace and others) hold, how striping with stripe_unit and stripe_count maps an offset to an object, how the object map packs and stores its bits, and how fast-diff finds the changes between two snapshots without reading data. The chapter ends with a complete reference for the rbd command and rbd-nbd. Names have been checked against the main branch of the ceph/ceph repository (November 2026).

37-1-1 What questions does this chapter answer?#

  • What keys does an image header have, and who writes each of them?
  • How does the data object name differ with a separate data pool?
  • With stripe_unit=64K and stripe_count=4, which object holds offset 1000000?
  • How many bits does the object map keep per object, and in what order are the bytes on disk?
  • What does EXISTS_CLEAN mean, and when is it set?
  • Which states does fast-diff use to detect a change?
  • How are pools, namespaces, the trash and groups indexed in RADOS?

37-1-2 Prerequisites#

Chapter 2 (RBD), chapter 15 (cls), chapters 34 and 36 (omap, SnapContext and clones).

37-2 The objects of an image#

37-2-1 The header#

All the metadata of a format 2 image is in the omap of the object rbd_header.<id> and changes only through the methods of the rbd class (src/cls/rbd/cls_rbd.cc):

omap keyTypeContent
sizeu64the image size in bytes
orderu8the binary logarithm of the object size (22 means 4 MiB)
object_prefixstringthe prefix of the data objects, for example rbd_data.5f3a2b8c1d4e
featuresu64the feature bits (next table)
flagsu64OBJECT_MAP_INVALID (1) and FAST_DIFF_INVALID (2)
op_featuresu64operational features such as clone-parent, clone-child, group, snap-trash
snap_sequ64the largest snap id created for this image
snapshot_<id:016x>cls_rbd_snapthe name, size, parent, flags and namespace of each snapshot
snap_children_<id:016x>setthe format 2 clone children on that snapshot
parentcls_rbd_parentthe parent's pool, namespace, image and snap, and the overlap (for a clone)
stripe_unit, stripe_countu64only if striping is custom
data_pool_ids64only if there is a separate data pool
create_timestamp, access_timestamp, modify_timestamputimetimestamps
snap_limitu64the cap on the number of snapshots
metadata_<key>stringimage metadata; for example conf_rbd_cache for per-image settings
migrationcls_rbd_migrationthe live-migration state

Besides the omap, the xattr lock.rbd_lock (the lock class) holds the advisory lock that ManagedLock uses for exclusive-lock (chapter 38). This same header object is the watch target of all clients that have the image open.

FeatureBitDefault
layering1yes
striping2only if the stripe is custom
exclusive-lock4yes
object-map8yes
fast-diff16yes
deep-flatten32yes
journaling64no
data-pool128only if --data-pool is given
operations256internal
migrating512internal
non-primary1024internal, for a mirror destination image
dirty-cache2048internal, for the PWL persistent cache

The default sum (rbd_default_features) is 1+4+8+16+32 = 61.

37-2-2 Data objects and the object map#

Data objects are named <object_prefix>.<objectno:016x>. If the image is created with --data-pool, the header and metadata stay in the main pool (which must be replicated) and the data is written to the second pool (often EC with allow_ec_overwrites). In this case the prefix includes the main pool's id: rbd_data.<metadata-pool-id>.<image-id>, so that objects of images from several different pools do not collide in a shared data pool.

The object map is stored in the object rbd_object_map.<id> for the head and rbd_object_map.<id>.<snapid:016x> for each snapshot.

37-2-3 Pool-level objects#

Objectomap or dataUse
rbd_id.<name>data: the image idfinding the id from the name
rbd_directoryname_<name> → id and id_<id> → namethe list of images in the pool or namespace
rbd_trashid_<id> → name, deletion time, earliest restore time, sourceimages in the trash
rbd_childrenkey: parent spec, value: childrenonly for format 1 clones
rbd_infothe omap conf_<option> and a pool validation markerpool-level settings (rbd config pool set) and the result of checking overwrite support in the data pool
rbd_namespacename_<ns>the list of the pool's namespaces
rbd_mirroringmirror_mode, mirror_uuid, mirror_peer_<uuid>, image_<id>, global_<gid>, status_global_<gid>, instance_<id>, image_map_<gid>the mirroring state (chapter 39)
rbd_group_directory and rbd_group_header.<id>omapconsistency groups
rbd_taskomapthe mgr's background task queue (rbd task add flatten and the like)

A namespace in RBD is the same as a namespace in RADOS (chapter 34). All the objects listed above are created separately in each namespace. With the cap profile rbd pool=P namespace=N a client can be restricted to one namespace without creating a separate pool for each tenant.

bashReef+
# example: reading the header directly (for inspection only; never change it with rados)
rados -p rbd listomapkeys rbd_header.5f3a2b8c1d4e
rados -p rbd getomapval rbd_header.5f3a2b8c1d4e object_prefix
text
example (output):
access_timestamp
create_timestamp
features
flags
modify_timestamp
object_prefix
order
size
snap_seq
snapshot_0000000000000004
value (25 bytes) :
00000000  15 00 00 00 72 62 64 5f  64 61 74 61 2e 35 66 33  |....rbd_data.5f3|
00000010  61 32 62 38 63 31 64 34  65                       |a2b8c1d4e|

The first four bytes (15 00 00 00, that is 21) are the string length in little-endian; all strings in Ceph's encoding have this format.

Figure 37-1: The layout of an image with a separate data pool. The replicated metadata pool holds rbd_id, rbd_directory, the header and the object maps, and the EC data pool holds the rbd_data objects prefixed with the metadata pool's id. Clients watch the header, and the exclusive lock is on an xattr of that same header
Figure 37-1: The layout of an image with a separate data pool. The replicated metadata pool holds rbd_id, rbd_directory, the header and the object maps, and the EC data pool holds the rbd_data objects prefixed with the metadata pool's id. Clients watch the header, and the exclusive lock is on an xattr of that same header

37-3 Striping#

By default stripe_unit equals the object size and stripe_count is 1, so the image is simply divided into consecutive 4 MiB pieces. With custom striping, data is spread round-robin across stripe_count objects, so that small sequential writes go to several OSDs at once.

∑ Algorithm: Mapping an image offset to (object, offset within the object)

Source in code: src/osdc/Striper.cc :: Striper::file_to_extents

Complexity: O(len / stripe_unit) pieces

text
pseudocode
su = stripe_unit ; sc = stripe_count ; os = object_size
if sc == 1: su = os
stripes_per_object = os / su
while left > 0:
    blockno     = cur / su
    stripeno    = blockno / sc
    stripepos   = blockno % sc
    objectsetno = stripeno / stripes_per_object
    objectno    = objectsetno * sc + stripepos
    x_offset    = (stripeno % stripes_per_object) * su + cur % su
    x_len       = min(su - cur % su, left)
    emit (objectno, x_offset, x_len) ; cur += x_len ; left -= x_len

Numerical example. object_size = 4 MiB, stripe_unit = 64 KiB, stripe_count = 4, a write of 300000 bytes from offset 1000000. Each object has 64 stripes and one "object set" of 4 objects covers 16 MiB.

blockstripestripeposobjectoffset within objectlength
1533321356848576
1640026214465536
1741126214465536
1842226214465536
1943326214454816

The 300 KB write reaches all four objects. With default striping, the same write would touch only object 0 (offsets 1000000 to 1300000). Striping helps a single-stream sequential load with a low queue depth, but for a random 4K load it only increases the number of objects involved. krbd supports custom striping from kernel 4.17 (chapter 29).

Figure 37-2: Striping with stripe_unit=64K and stripe_count=4. Each row is a stripe and the four columns are the four objects of an object set. Blocks 15 to 19, which make up the example write, are colored
Figure 37-2: Striping with stripe_unit=64K and stripe_count=4. Each row is a stripe and the four columns are the four objects of an object set. Blocks 15 to 19, which make up the example write, are colored
Lab

RBD striping mapper

Set an image's object size, stripe_unit and stripe_count and an I/O's offset and length: see which rbd_data objects it touches and where.

2560objects when full
4 MiBobject_size
16 MiBobject set size
4objects touched by this I/O
blockno = ⌊off / su⌋ · stripeno = ⌊blockno / sc⌋ · stripepos = blockno % sc · objectsetno = ⌊stripeno / (os / su)⌋ · objectno = objectsetno × sc + stripepos · offset in object = (stripeno % (os / su)) × su + off % su
object set 0 · objects 0…3 · 2 stripes per row
0123
s0…
s2…
s4…
s6…
s8…
s10…
s12…
s14…
s16…
s18…
s20…
s22…
s24…
s26…
s28…
s30…
s32…
s34…
s36…
s38…
s40…
s42…
s44…
s46…
s48…
s50…
s52…
s54…
s56…
s58…
s60…
s62…
ObjectOffset in objectLengthbuffer_extents
rbd_data.10226b8b4567.000000000000000026214465536[48576, 65536]
rbd_data.10226b8b4567.000000000000000126214465536[114112, 65536]
rbd_data.10226b8b4567.000000000000000226214465536[179648, 65536]
rbd_data.10226b8b4567.0000000000000003213568103392[0, 48576] [245184, 54816]
rbd create pool/img --size 10G --object-size 4M --stripe-unit 64K --stripe-count 4

Follows:

  • src/osdc/Striper.cc :: Striper::file_to_extents an image range to object extents
  • Striper.cc :: get_num_objects, get_file_offset, object_truncate_size object counts and the reverse mapping
  • src/librbd/ImageCtx.cc :: ImageCtx::init (format_string "%s.%016llx") the data object name
  • src/include/rbd_types.h :: RBD_DATA_PREFIX "rbd_data." the name prefix
  • src/test/test_striper.cc :: the upstream test vectors used by tests/sims the vectors the tests compare against
A fixed snapshot of the simulator; the interactive version is in the web edition's lab. Interactive lab: RBD striping mapper

37-4 The object map#

37-4-1 States and bit packing#

The object map has 2 bits per object (BitVector<2>):

ValueConstantMeaning
0OBJECT_NONEXISTENTthe object does not exist; a read returns zeros and there is no need to go to the OSD
1OBJECT_EXISTSthe object exists and has changed since the last snapshot
2OBJECT_PENDINGa deletion is in progress (discard or resize)
3OBJECT_EXISTS_CLEANthe object exists and has not changed since the last snapshot

Each byte holds four objects, and the object with the lower number is in the higher bits: shift = (3 − objectno % 4) × 2. The on-disk encoding has three parts: a header (length, version and element count), the raw data, and a footer containing the crc32c of the header and one crc32c for each 4096-byte block of data. So updating one object rewrites only one 4 KiB block and its crc (the object_map_update method in cls_rbd).

Figure 37-3: The object map encoding of a ten-gigabyte image with 4 MiB objects (2560 objects), byte by byte. The eighteen-byte header contains the length, struct_v and compat, and the element count; then 640 bytes of data, each byte holding four objects with two bits each, and finally the footer with the crcs
Figure 37-3: The object map encoding of a ten-gigabyte image with 4 MiB objects (2560 objects), byte by byte. The eighteen-byte header contains the length, struct_v and compat, and the element count; then 640 bytes of data, each byte holding four objects with two bits each, and finally the footer with the crcs

37-4-2 Who changes the state?#

  • A write to an object that is NONEXISTENT or EXISTS_CLEAN updates the object map to EXISTS before the write is sent (first in memory, then with object_map_update on RADOS). If the object is already EXISTS, it costs nothing.
  • A discard or deletion first sets the state to PENDING, deletes the object, and then sets NONEXISTENT.
  • Taking a snapshot copies the head map to rbd_object_map.<id>.<snapid> and then turns all EXISTS entries of the head into EXISTS_CLEAN (object_map_snap_add in cls_rbd).
  • Deleting a snapshot merges the EXISTS bits of that snapshot's map into the map of the next snapshot (or the head), so the "changed" information is not lost (object_map_snap_remove).

The object map is valid only with exclusive-lock, because only the lock owner may change it. If the client fails in the middle of an update, the OBJECT_MAP_INVALID flag is set in flags and rbd object-map rebuild must be run.

37-4-3 fast-diff#

∑ Algorithm: Computing each object's change state between two snapshots

Source in code: src/librbd/object_map/DiffRequest.cc :: DiffRequest::handle_load_object_map

Complexity: O(N × S) for N objects and S loaded maps; with no data reads at all

text
pseudocode
# for each snapshot from from_snap to end_snap (and head), its map is loaded and combined with the previous state
first map (from_snap):  state[o] = HOLE if NONEXISTENT else DATA (PENDING → DATA_UPDATED)
each later map m:
    HOLE:         if m[o] != NONEXISTENT → DATA_UPDATED     (diff-iterate: only in the final map)
    DATA:         if m[o] == NONEXISTENT → HOLE_UPDATED
                  elif m[o] != EXISTS_CLEAN → DATA_UPDATED
    HOLE_UPDATED: if m[o] != NONEXISTENT → DATA_UPDATED
    DATA_UPDATED: if m[o] == NONEXISTENT → HOLE_UPDATED
objects beyond the previous size (image grew) → HOLE or DATA_UPDATED

The key point is EXISTS_CLEAN: if an object existed at from_snap and stayed EXISTS_CLEAN in all later maps, it had no writes after from_snap and does not appear in the diff. rbd diff --whole-object, rbd du and rbd export-diff (and snapshot mode in rbd-mirror) all use this calculation. If FAST_DIFF_INVALID is set, librbd falls back to list_snaps on each individual object, which is much slower.

Numerical example. A 10 GiB image with 2560 objects. After snap1, twenty objects have been written and three have been discarded. With fast-diff, rbd export-diff --from-snap snap1 reads only two 640-byte maps (plus header and footer) and finds the 23 objects. Without it, 2560 list_snaps calls would be needed.

37-5 rbd reference#

37-5-1 Images and snapshots#

CommandUse
rbd create P/I --size 10G [--object-size 4M] [--stripe-unit 64K --stripe-count 4] [--data-pool DP] [--image-feature F]creating an image
rbd ls [-l] P[/NS] / rbd info P/I / rbd status P/Ilisting, details, watchers
rbd resize P/I --size N [--allow-shrink]resizing
rbd feature {enable,disable} P/I Fturning a feature on and off
rbd object-map {rebuild,check} P/I[@S]rebuilding and checking the object map
rbd du P/I / rbd diff [--from-snap S] [--whole-object] P/Iusage and changes
rbd snap {create,ls,rm,purge,rollback,rename,limit set}snapshots
rbd snap {protect,unprotect} P/I@Sonly for format 1 clones
rbd clone P/I@S P2/C [--rbd-default-clone-format 2] / rbd children P/I@S / rbd flatten P2/Clayering (chapter 38)
rbd export P/I FILE / import / export-diff / import-diff / merge-diffbackup
rbd deep cp P/I P2/I2 / rbd cpcopying (deep, with snapshots)
rbd trash {mv,ls,restore,rm,purge}the trash
rbd migration {prepare,execute,commit,abort}live migration
rbd image-meta {list,get,set,remove} P/Iper-image metadata and settings
rbd config {pool,image,global} {get,set,list}pool-level or image-level settings
rbd namespace {create,ls,remove} P/NSnamespaces
rbd group {create,image add,snap create}consistency groups
rbd lock {ls,add,rm}advisory locks (not exclusive-lock)
rbd bench P/I --io-type {read,write,readwrite} --io-size 4K --io-threads 16 --io-pattern randperformance testing
rbd perf image {iostat,iotop}live statistics from the mgr
rbd pool init P / rbd pool stats Ppreparing a pool and its statistics

37-5-2 Mapping#

CommandUse
rbd device map [-t krbd] P/I [-o OPTS] / rbd device unmap / rbd device listkrbd
rbd device map -t nbd P/I or rbd-nbd map P/I [--try-netlink] [--timeout N]rbd-nbd (librbd in user space)
rbd-nbd list-mapped / rbd-nbd unmap /dev/nbdXlisting and unmapping
rbd device map -t ubbd P/Iubbd, if its package is installed
bashReef+
# example: creating an image with striping and a data pool, and inspecting it
rbd create rbd/vm01 --size 10G --stripe-unit 64K --stripe-count 4 --data-pool rbd-ec
rbd info rbd/vm01
text
example (output):
rbd image 'vm01':
	size 10 GiB in 2560 objects
	order 22 (4 MiB objects)
	snapshot_count: 0
	id: 5f3a2b8c1d4e
	data_pool: rbd-ec
	block_name_prefix: rbd_data.3.5f3a2b8c1d4e
	format: 2
	features: layering, striping, exclusive-lock, object-map, fast-diff, deep-flatten, data-pool
	op_features:
	flags:
	create_timestamp: Sun Nov 15 09:41:07 2026
	access_timestamp: Sun Nov 15 09:41:07 2026
	modify_timestamp: Sun Nov 15 09:41:07 2026
	stripe unit: 64 KiB
	stripe count: 4

block_name_prefix is the object_prefix, and the number 3 in it is the id of the rbd pool, because we have a separate data pool.

37-6 Tuning and troubleshooting#

  • Slow first writes on a new image: each first write to an object also involves an object_map_update. Once the image is filled, this cost disappears. For performance testing, fill the image first.
  • rbd du or export-diff is slow: look at rbd info; if it shows flags: object map invalid, fast diff invalid, run rbd object-map rebuild (separately on the snapshots too).
  • An image with --data-pool on EC cannot be created: check allow_ec_overwrites on the EC pool and that rbd pool init has been run on both pools.
  • Deleting a large image is slow: deletion depends on the number of existing objects; with the object map only existing objects are deleted. For long jobs use rbd trash mv followed by rbd trash rm or ceph rbd task add remove.
  • Never change the header with rados setomapval: cls_rbd checks for inconsistencies, but open clients are not told about the change (no notify is sent).

37-7 Supplementary and up to date#

  • Format 2 clones (the default rbd_default_clone_format: auto chooses format 2 if all clients are Mimic or newer) do not need protect and keep the children in snap_children_* of the parent's own header.
  • The op_features value in new versions has bits for snapshots in the trash and for groups; an old client that sees an unknown op_feature does not open the image.
  • In the main branch, rbd-nbd with netlink and --try-netlink is more stable than the ioctl method; ubbd support depends on the distribution's packaging.

37-8 Hands-on exercise#

  1. 1. Create an image and read all the header keys with rados listomapkeys and getomapval. Then take a snapshot and find the new key.
  2. 2. Create an image with --stripe-unit 64K --stripe-count 4, write 300000 bytes at offset 1000000 (rbd bench or dd on rbd-nbd) and see with rados ls | grep which objects were created. Compare with the example table.
  3. 3. Take rados get rbd_object_map.<id> /tmp/om and with xxd match the first four data bytes (after the eighteen-byte header) against the states of objects 0 to 15.
  4. 4. After a snapshot, write a few objects and time rbd diff --from-snap with and without fast-diff (after rbd feature disable ... fast-diff).

37-9 Review questions#

  1. 1. Which header keys are needed to build the names of the data objects?
  2. 2. With a separate data pool, how do the data object names change, and why?
  3. 3. What two kinds of key does rbd_directory have?
  4. 4. In the striping example, which object and which offset hold the byte at offset 1100000?
  5. 5. How many bits does the object map keep per object, and in which bits of the first byte is object 0?
  6. 6. Why does the object map footer have a separate crc for each 4 KiB block?
  7. 7. When is EXISTS_CLEAN set and when is it cleared?
  8. 8. In fast-diff, if an object is DATA at from_snap and EXISTS in the head, what is the result?
  9. 9. Why does the object map depend on exclusive-lock?
  10. 10. How is a namespace implemented in RBD, and what advantage does it have?

Answers to these questions are in Appendix E: Answers to Review Questions. Answers to this chapter's questions →

37-10 Sources for this chapter#

The Comprehensive Ceph Guide · 1.6 · November 2026
© 2026 Ramin Najarbashi · CC BY-NC-SA 4.0

Privacy: this site sets no cookies. Visits are counted with Cloudflare Web Analytics, which is cookie-less and does not track individuals.