0001 =============================
0002 BTT - Block Translation Table
0003 =============================
0004
0005
0006 1. Introduction
0007 ===============
0008
0009 Persistent memory based storage is able to perform IO at byte (or more
0010 accurately, cache line) granularity. However, we often want to expose such
0011 storage as traditional block devices. The block drivers for persistent memory
0012 will do exactly this. However, they do not provide any atomicity guarantees.
0013 Traditional SSDs typically provide protection against torn sectors in hardware,
0014 using stored energy in capacitors to complete in-flight block writes, or perhaps
0015 in firmware. We don't have this luxury with persistent memory - if a write is in
0016 progress, and we experience a power failure, the block will contain a mix of old
0017 and new data. Applications may not be prepared to handle such a scenario.
0018
0019 The Block Translation Table (BTT) provides atomic sector update semantics for
0020 persistent memory devices, so that applications that rely on sector writes not
0021 being torn can continue to do so. The BTT manifests itself as a stacked block
0022 device, and reserves a portion of the underlying storage for its metadata. At
0023 the heart of it, is an indirection table that re-maps all the blocks on the
0024 volume. It can be thought of as an extremely simple file system that only
0025 provides atomic sector updates.
0026
0027
0028 2. Static Layout
0029 ================
0030
0031 The underlying storage on which a BTT can be laid out is not limited in any way.
0032 The BTT, however, splits the available space into chunks of up to 512 GiB,
0033 called "Arenas".
0034
0035 Each arena follows the same layout for its metadata, and all references in an
0036 arena are internal to it (with the exception of one field that points to the
0037 next arena). The following depicts the "On-disk" metadata layout::
0038
0039
0040 Backing Store +-------> Arena
0041 +---------------+ | +------------------+
0042 | | | | Arena info block |
0043 | Arena 0 +---+ | 4K |
0044 | 512G | +------------------+
0045 | | | |
0046 +---------------+ | |
0047 | | | |
0048 | Arena 1 | | Data Blocks |
0049 | 512G | | |
0050 | | | |
0051 +---------------+ | |
0052 | . | | |
0053 | . | | |
0054 | . | | |
0055 | | | |
0056 | | | |
0057 +---------------+ +------------------+
0058 | |
0059 | BTT Map |
0060 | |
0061 | |
0062 +------------------+
0063 | |
0064 | BTT Flog |
0065 | |
0066 +------------------+
0067 | Info block copy |
0068 | 4K |
0069 +------------------+
0070
0071
0072 3. Theory of Operation
0073 ======================
0074
0075
0076 a. The BTT Map
0077 --------------
0078
0079 The map is a simple lookup/indirection table that maps an LBA to an internal
0080 block. Each map entry is 32 bits. The two most significant bits are special
0081 flags, and the remaining form the internal block number.
0082
0083 ======== =============================================================
0084 Bit Description
0085 ======== =============================================================
0086 31 - 30 Error and Zero flags - Used in the following way::
0087
0088 == == ====================================================
0089 31 30 Description
0090 == == ====================================================
0091 0 0 Initial state. Reads return zeroes; Premap = Postmap
0092 0 1 Zero state: Reads return zeroes
0093 1 0 Error state: Reads fail; Writes clear 'E' bit
0094 1 1 Normal Block – has valid postmap
0095 == == ====================================================
0096
0097 29 - 0 Mappings to internal 'postmap' blocks
0098 ======== =============================================================
0099
0100
0101 Some of the terminology that will be subsequently used:
0102
0103 ============ ================================================================
0104 External LBA LBA as made visible to upper layers.
0105 ABA Arena Block Address - Block offset/number within an arena
0106 Premap ABA The block offset into an arena, which was decided upon by range
0107 checking the External LBA
0108 Postmap ABA The block number in the "Data Blocks" area obtained after
0109 indirection from the map
0110 nfree The number of free blocks that are maintained at any given time.
0111 This is the number of concurrent writes that can happen to the
0112 arena.
0113 ============ ================================================================
0114
0115
0116 For example, after adding a BTT, we surface a disk of 1024G. We get a read for
0117 the external LBA at 768G. This falls into the second arena, and of the 512G
0118 worth of blocks that this arena contributes, this block is at 256G. Thus, the
0119 premap ABA is 256G. We now refer to the map, and find out the mapping for block
0120 'X' (256G) points to block 'Y', say '64'. Thus the postmap ABA is 64.
0121
0122
0123 b. The BTT Flog
0124 ---------------
0125
0126 The BTT provides sector atomicity by making every write an "allocating write",
0127 i.e. Every write goes to a "free" block. A running list of free blocks is
0128 maintained in the form of the BTT flog. 'Flog' is a combination of the words
0129 "free list" and "log". The flog contains 'nfree' entries, and an entry contains:
0130
0131 ======== =====================================================================
0132 lba The premap ABA that is being written to
0133 old_map The old postmap ABA - after 'this' write completes, this will be a
0134 free block.
0135 new_map The new postmap ABA. The map will up updated to reflect this
0136 lba->postmap_aba mapping, but we log it here in case we have to
0137 recover.
0138 seq Sequence number to mark which of the 2 sections of this flog entry is
0139 valid/newest. It cycles between 01->10->11->01 (binary) under normal
0140 operation, with 00 indicating an uninitialized state.
0141 lba' alternate lba entry
0142 old_map' alternate old postmap entry
0143 new_map' alternate new postmap entry
0144 seq' alternate sequence number.
0145 ======== =====================================================================
0146
0147 Each of the above fields is 32-bit, making one entry 32 bytes. Entries are also
0148 padded to 64 bytes to avoid cache line sharing or aliasing. Flog updates are
0149 done such that for any entry being written, it:
0150 a. overwrites the 'old' section in the entry based on sequence numbers
0151 b. writes the 'new' section such that the sequence number is written last.
0152
0153
0154 c. The concept of lanes
0155 -----------------------
0156
0157 While 'nfree' describes the number of concurrent IOs an arena can process
0158 concurrently, 'nlanes' is the number of IOs the BTT device as a whole can
0159 process::
0160
0161 nlanes = min(nfree, num_cpus)
0162
0163 A lane number is obtained at the start of any IO, and is used for indexing into
0164 all the on-disk and in-memory data structures for the duration of the IO. If
0165 there are more CPUs than the max number of available lanes, than lanes are
0166 protected by spinlocks.
0167
0168
0169 d. In-memory data structure: Read Tracking Table (RTT)
0170 ------------------------------------------------------
0171
0172 Consider a case where we have two threads, one doing reads and the other,
0173 writes. We can hit a condition where the writer thread grabs a free block to do
0174 a new IO, but the (slow) reader thread is still reading from it. In other words,
0175 the reader consulted a map entry, and started reading the corresponding block. A
0176 writer started writing to the same external LBA, and finished the write updating
0177 the map for that external LBA to point to its new postmap ABA. At this point the
0178 internal, postmap block that the reader is (still) reading has been inserted
0179 into the list of free blocks. If another write comes in for the same LBA, it can
0180 grab this free block, and start writing to it, causing the reader to read
0181 incorrect data. To prevent this, we introduce the RTT.
0182
0183 The RTT is a simple, per arena table with 'nfree' entries. Every reader inserts
0184 into rtt[lane_number], the postmap ABA it is reading, and clears it after the
0185 read is complete. Every writer thread, after grabbing a free block, checks the
0186 RTT for its presence. If the postmap free block is in the RTT, it waits till the
0187 reader clears the RTT entry, and only then starts writing to it.
0188
0189
0190 e. In-memory data structure: map locks
0191 --------------------------------------
0192
0193 Consider a case where two writer threads are writing to the same LBA. There can
0194 be a race in the following sequence of steps::
0195
0196 free[lane] = map[premap_aba]
0197 map[premap_aba] = postmap_aba
0198
0199 Both threads can update their respective free[lane] with the same old, freed
0200 postmap_aba. This has made the layout inconsistent by losing a free entry, and
0201 at the same time, duplicating another free entry for two lanes.
0202
0203 To solve this, we could have a single map lock (per arena) that has to be taken
0204 before performing the above sequence, but we feel that could be too contentious.
0205 Instead we use an array of (nfree) map_locks that is indexed by
0206 (premap_aba modulo nfree).
0207
0208
0209 f. Reconstruction from the Flog
0210 -------------------------------
0211
0212 On startup, we analyze the BTT flog to create our list of free blocks. We walk
0213 through all the entries, and for each lane, of the set of two possible
0214 'sections', we always look at the most recent one only (based on the sequence
0215 number). The reconstruction rules/steps are simple:
0216
0217 - Read map[log_entry.lba].
0218 - If log_entry.new matches the map entry, then log_entry.old is free.
0219 - If log_entry.new does not match the map entry, then log_entry.new is free.
0220 (This case can only be caused by power-fails/unsafe shutdowns)
0221
0222
0223 g. Summarizing - Read and Write flows
0224 -------------------------------------
0225
0226 Read:
0227
0228 1. Convert external LBA to arena number + pre-map ABA
0229 2. Get a lane (and take lane_lock)
0230 3. Read map to get the entry for this pre-map ABA
0231 4. Enter post-map ABA into RTT[lane]
0232 5. If TRIM flag set in map, return zeroes, and end IO (go to step 8)
0233 6. If ERROR flag set in map, end IO with EIO (go to step 8)
0234 7. Read data from this block
0235 8. Remove post-map ABA entry from RTT[lane]
0236 9. Release lane (and lane_lock)
0237
0238 Write:
0239
0240 1. Convert external LBA to Arena number + pre-map ABA
0241 2. Get a lane (and take lane_lock)
0242 3. Use lane to index into in-memory free list and obtain a new block, next flog
0243 index, next sequence number
0244 4. Scan the RTT to check if free block is present, and spin/wait if it is.
0245 5. Write data to this free block
0246 6. Read map to get the existing post-map ABA entry for this pre-map ABA
0247 7. Write flog entry: [premap_aba / old postmap_aba / new postmap_aba / seq_num]
0248 8. Write new post-map ABA into map.
0249 9. Write old post-map entry into the free list
0250 10. Calculate next sequence number and write into the free list entry
0251 11. Release lane (and lane_lock)
0252
0253
0254 4. Error Handling
0255 =================
0256
0257 An arena would be in an error state if any of the metadata is corrupted
0258 irrecoverably, either due to a bug or a media error. The following conditions
0259 indicate an error:
0260
0261 - Info block checksum does not match (and recovering from the copy also fails)
0262 - All internal available blocks are not uniquely and entirely addressed by the
0263 sum of mapped blocks and free blocks (from the BTT flog).
0264 - Rebuilding free list from the flog reveals missing/duplicate/impossible
0265 entries
0266 - A map entry is out of bounds
0267
0268 If any of these error conditions are encountered, the arena is put into a read
0269 only state using a flag in the info block.
0270
0271
0272 5. Usage
0273 ========
0274
0275 The BTT can be set up on any disk (namespace) exposed by the libnvdimm subsystem
0276 (pmem, or blk mode). The easiest way to set up such a namespace is using the
0277 'ndctl' utility [1]:
0278
0279 For example, the ndctl command line to setup a btt with a 4k sector size is::
0280
0281 ndctl create-namespace -f -e namespace0.0 -m sector -l 4k
0282
0283 See ndctl create-namespace --help for more options.
0284
0285 [1]: https://github.com/pmem/ndctl