Dave Chinner | a9a745d | 2010-05-14 21:43:11 +1000 | [diff] [blame] | 1 | XFS Delayed Logging Design |
| 2 | -------------------------- |
| 3 | |
| 4 | Introduction to Re-logging in XFS |
| 5 | --------------------------------- |
| 6 | |
| 7 | XFS logging is a combination of logical and physical logging. Some objects, |
| 8 | such as inodes and dquots, are logged in logical format where the details |
| 9 | logged are made up of the changes to in-core structures rather than on-disk |
| 10 | structures. Other objects - typically buffers - have their physical changes |
| 11 | logged. The reason for these differences is to reduce the amount of log space |
| 12 | required for objects that are frequently logged. Some parts of inodes are more |
| 13 | frequently logged than others, and inodes are typically more frequently logged |
| 14 | than any other object (except maybe the superblock buffer) so keeping the |
| 15 | amount of metadata logged low is of prime importance. |
| 16 | |
| 17 | The reason that this is such a concern is that XFS allows multiple separate |
| 18 | modifications to a single object to be carried in the log at any given time. |
| 19 | This allows the log to avoid needing to flush each change to disk before |
| 20 | recording a new change to the object. XFS does this via a method called |
| 21 | "re-logging". Conceptually, this is quite simple - all it requires is that any |
| 22 | new change to the object is recorded with a *new copy* of all the existing |
| 23 | changes in the new transaction that is written to the log. |
| 24 | |
| 25 | That is, if we have a sequence of changes A through to F, and the object was |
| 26 | written to disk after change D, we would see in the log the following series |
| 27 | of transactions, their contents and the log sequence number (LSN) of the |
| 28 | transaction: |
| 29 | |
| 30 | Transaction Contents LSN |
| 31 | A A X |
| 32 | B A+B X+n |
| 33 | C A+B+C X+n+m |
| 34 | D A+B+C+D X+n+m+o |
| 35 | <object written to disk> |
| 36 | E E Y (> X+n+m+o) |
| 37 | F E+F Yٍ+p |
| 38 | |
| 39 | In other words, each time an object is relogged, the new transaction contains |
| 40 | the aggregation of all the previous changes currently held only in the log. |
| 41 | |
| 42 | This relogging technique also allows objects to be moved forward in the log so |
| 43 | that an object being relogged does not prevent the tail of the log from ever |
| 44 | moving forward. This can be seen in the table above by the changing |
| 45 | (increasing) LSN of each subsquent transaction - the LSN is effectively a |
| 46 | direct encoding of the location in the log of the transaction. |
| 47 | |
| 48 | This relogging is also used to implement long-running, multiple-commit |
| 49 | transactions. These transaction are known as rolling transactions, and require |
| 50 | a special log reservation known as a permanent transaction reservation. A |
| 51 | typical example of a rolling transaction is the removal of extents from an |
| 52 | inode which can only be done at a rate of two extents per transaction because |
| 53 | of reservation size limitations. Hence a rolling extent removal transaction |
| 54 | keeps relogging the inode and btree buffers as they get modified in each |
| 55 | removal operation. This keeps them moving forward in the log as the operation |
| 56 | progresses, ensuring that current operation never gets blocked by itself if the |
| 57 | log wraps around. |
| 58 | |
| 59 | Hence it can be seen that the relogging operation is fundamental to the correct |
| 60 | working of the XFS journalling subsystem. From the above description, most |
| 61 | people should be able to see why the XFS metadata operations writes so much to |
| 62 | the log - repeated operations to the same objects write the same changes to |
| 63 | the log over and over again. Worse is the fact that objects tend to get |
| 64 | dirtier as they get relogged, so each subsequent transaction is writing more |
| 65 | metadata into the log. |
| 66 | |
| 67 | Another feature of the XFS transaction subsystem is that most transactions are |
| 68 | asynchronous. That is, they don't commit to disk until either a log buffer is |
| 69 | filled (a log buffer can hold multiple transactions) or a synchronous operation |
| 70 | forces the log buffers holding the transactions to disk. This means that XFS is |
| 71 | doing aggregation of transactions in memory - batching them, if you like - to |
| 72 | minimise the impact of the log IO on transaction throughput. |
| 73 | |
| 74 | The limitation on asynchronous transaction throughput is the number and size of |
| 75 | log buffers made available by the log manager. By default there are 8 log |
| 76 | buffers available and the size of each is 32kB - the size can be increased up |
| 77 | to 256kB by use of a mount option. |
| 78 | |
| 79 | Effectively, this gives us the maximum bound of outstanding metadata changes |
| 80 | that can be made to the filesystem at any point in time - if all the log |
| 81 | buffers are full and under IO, then no more transactions can be committed until |
| 82 | the current batch completes. It is now common for a single current CPU core to |
| 83 | be to able to issue enough transactions to keep the log buffers full and under |
| 84 | IO permanently. Hence the XFS journalling subsystem can be considered to be IO |
| 85 | bound. |
| 86 | |
| 87 | Delayed Logging: Concepts |
| 88 | ------------------------- |
| 89 | |
| 90 | The key thing to note about the asynchronous logging combined with the |
| 91 | relogging technique XFS uses is that we can be relogging changed objects |
| 92 | multiple times before they are committed to disk in the log buffers. If we |
| 93 | return to the previous relogging example, it is entirely possible that |
| 94 | transactions A through D are committed to disk in the same log buffer. |
| 95 | |
| 96 | That is, a single log buffer may contain multiple copies of the same object, |
| 97 | but only one of those copies needs to be there - the last one "D", as it |
| 98 | contains all the changes from the previous changes. In other words, we have one |
| 99 | necessary copy in the log buffer, and three stale copies that are simply |
| 100 | wasting space. When we are doing repeated operations on the same set of |
| 101 | objects, these "stale objects" can be over 90% of the space used in the log |
| 102 | buffers. It is clear that reducing the number of stale objects written to the |
| 103 | log would greatly reduce the amount of metadata we write to the log, and this |
| 104 | is the fundamental goal of delayed logging. |
| 105 | |
| 106 | From a conceptual point of view, XFS is already doing relogging in memory (where |
| 107 | memory == log buffer), only it is doing it extremely inefficiently. It is using |
| 108 | logical to physical formatting to do the relogging because there is no |
| 109 | infrastructure to keep track of logical changes in memory prior to physically |
| 110 | formatting the changes in a transaction to the log buffer. Hence we cannot avoid |
| 111 | accumulating stale objects in the log buffers. |
| 112 | |
| 113 | Delayed logging is the name we've given to keeping and tracking transactional |
| 114 | changes to objects in memory outside the log buffer infrastructure. Because of |
| 115 | the relogging concept fundamental to the XFS journalling subsystem, this is |
| 116 | actually relatively easy to do - all the changes to logged items are already |
| 117 | tracked in the current infrastructure. The big problem is how to accumulate |
| 118 | them and get them to the log in a consistent, recoverable manner. |
| 119 | Describing the problems and how they have been solved is the focus of this |
| 120 | document. |
| 121 | |
| 122 | One of the key changes that delayed logging makes to the operation of the |
| 123 | journalling subsystem is that it disassociates the amount of outstanding |
| 124 | metadata changes from the size and number of log buffers available. In other |
| 125 | words, instead of there only being a maximum of 2MB of transaction changes not |
| 126 | written to the log at any point in time, there may be a much greater amount |
| 127 | being accumulated in memory. Hence the potential for loss of metadata on a |
| 128 | crash is much greater than for the existing logging mechanism. |
| 129 | |
| 130 | It should be noted that this does not change the guarantee that log recovery |
| 131 | will result in a consistent filesystem. What it does mean is that as far as the |
| 132 | recovered filesystem is concerned, there may be many thousands of transactions |
| 133 | that simply did not occur as a result of the crash. This makes it even more |
| 134 | important that applications that care about their data use fsync() where they |
| 135 | need to ensure application level data integrity is maintained. |
| 136 | |
| 137 | It should be noted that delayed logging is not an innovative new concept that |
| 138 | warrants rigorous proofs to determine whether it is correct or not. The method |
| 139 | of accumulating changes in memory for some period before writing them to the |
| 140 | log is used effectively in many filesystems including ext3 and ext4. Hence |
| 141 | no time is spent in this document trying to convince the reader that the |
| 142 | concept is sound. Instead it is simply considered a "solved problem" and as |
| 143 | such implementing it in XFS is purely an exercise in software engineering. |
| 144 | |
| 145 | The fundamental requirements for delayed logging in XFS are simple: |
| 146 | |
| 147 | 1. Reduce the amount of metadata written to the log by at least |
| 148 | an order of magnitude. |
| 149 | 2. Supply sufficient statistics to validate Requirement #1. |
| 150 | 3. Supply sufficient new tracing infrastructure to be able to debug |
| 151 | problems with the new code. |
| 152 | 4. No on-disk format change (metadata or log format). |
| 153 | 5. Enable and disable with a mount option. |
| 154 | 6. No performance regressions for synchronous transaction workloads. |
| 155 | |
| 156 | Delayed Logging: Design |
| 157 | ----------------------- |
| 158 | |
| 159 | Storing Changes |
| 160 | |
| 161 | The problem with accumulating changes at a logical level (i.e. just using the |
| 162 | existing log item dirty region tracking) is that when it comes to writing the |
| 163 | changes to the log buffers, we need to ensure that the object we are formatting |
| 164 | is not changing while we do this. This requires locking the object to prevent |
| 165 | concurrent modification. Hence flushing the logical changes to the log would |
| 166 | require us to lock every object, format them, and then unlock them again. |
| 167 | |
| 168 | This introduces lots of scope for deadlocks with transactions that are already |
| 169 | running. For example, a transaction has object A locked and modified, but needs |
| 170 | the delayed logging tracking lock to commit the transaction. However, the |
| 171 | flushing thread has the delayed logging tracking lock already held, and is |
| 172 | trying to get the lock on object A to flush it to the log buffer. This appears |
| 173 | to be an unsolvable deadlock condition, and it was solving this problem that |
| 174 | was the barrier to implementing delayed logging for so long. |
| 175 | |
| 176 | The solution is relatively simple - it just took a long time to recognise it. |
| 177 | Put simply, the current logging code formats the changes to each item into an |
| 178 | vector array that points to the changed regions in the item. The log write code |
| 179 | simply copies the memory these vectors point to into the log buffer during |
| 180 | transaction commit while the item is locked in the transaction. Instead of |
| 181 | using the log buffer as the destination of the formatting code, we can use an |
| 182 | allocated memory buffer big enough to fit the formatted vector. |
| 183 | |
| 184 | If we then copy the vector into the memory buffer and rewrite the vector to |
| 185 | point to the memory buffer rather than the object itself, we now have a copy of |
| 186 | the changes in a format that is compatible with the log buffer writing code. |
| 187 | that does not require us to lock the item to access. This formatting and |
| 188 | rewriting can all be done while the object is locked during transaction commit, |
| 189 | resulting in a vector that is transactionally consistent and can be accessed |
| 190 | without needing to lock the owning item. |
| 191 | |
| 192 | Hence we avoid the need to lock items when we need to flush outstanding |
| 193 | asynchronous transactions to the log. The differences between the existing |
| 194 | formatting method and the delayed logging formatting can be seen in the |
| 195 | diagram below. |
| 196 | |
| 197 | Current format log vector: |
| 198 | |
| 199 | Object +---------------------------------------------+ |
| 200 | Vector 1 +----+ |
| 201 | Vector 2 +----+ |
| 202 | Vector 3 +----------+ |
| 203 | |
| 204 | After formatting: |
| 205 | |
| 206 | Log Buffer +-V1-+-V2-+----V3----+ |
| 207 | |
| 208 | Delayed logging vector: |
| 209 | |
| 210 | Object +---------------------------------------------+ |
| 211 | Vector 1 +----+ |
| 212 | Vector 2 +----+ |
| 213 | Vector 3 +----------+ |
| 214 | |
| 215 | After formatting: |
| 216 | |
| 217 | Memory Buffer +-V1-+-V2-+----V3----+ |
| 218 | Vector 1 +----+ |
| 219 | Vector 2 +----+ |
| 220 | Vector 3 +----------+ |
| 221 | |
| 222 | The memory buffer and associated vector need to be passed as a single object, |
| 223 | but still need to be associated with the parent object so if the object is |
| 224 | relogged we can replace the current memory buffer with a new memory buffer that |
| 225 | contains the latest changes. |
| 226 | |
| 227 | The reason for keeping the vector around after we've formatted the memory |
| 228 | buffer is to support splitting vectors across log buffer boundaries correctly. |
| 229 | If we don't keep the vector around, we do not know where the region boundaries |
| 230 | are in the item, so we'd need a new encapsulation method for regions in the log |
| 231 | buffer writing (i.e. double encapsulation). This would be an on-disk format |
| 232 | change and as such is not desirable. It also means we'd have to write the log |
| 233 | region headers in the formatting stage, which is problematic as there is per |
| 234 | region state that needs to be placed into the headers during the log write. |
| 235 | |
| 236 | Hence we need to keep the vector, but by attaching the memory buffer to it and |
| 237 | rewriting the vector addresses to point at the memory buffer we end up with a |
| 238 | self-describing object that can be passed to the log buffer write code to be |
| 239 | handled in exactly the same manner as the existing log vectors are handled. |
| 240 | Hence we avoid needing a new on-disk format to handle items that have been |
| 241 | relogged in memory. |
| 242 | |
| 243 | |
| 244 | Tracking Changes |
| 245 | |
| 246 | Now that we can record transactional changes in memory in a form that allows |
| 247 | them to be used without limitations, we need to be able to track and accumulate |
| 248 | them so that they can be written to the log at some later point in time. The |
| 249 | log item is the natural place to store this vector and buffer, and also makes sense |
| 250 | to be the object that is used to track committed objects as it will always |
| 251 | exist once the object has been included in a transaction. |
| 252 | |
| 253 | The log item is already used to track the log items that have been written to |
| 254 | the log but not yet written to disk. Such log items are considered "active" |
| 255 | and as such are stored in the Active Item List (AIL) which is a LSN-ordered |
| 256 | double linked list. Items are inserted into this list during log buffer IO |
| 257 | completion, after which they are unpinned and can be written to disk. An object |
| 258 | that is in the AIL can be relogged, which causes the object to be pinned again |
| 259 | and then moved forward in the AIL when the log buffer IO completes for that |
| 260 | transaction. |
| 261 | |
| 262 | Essentially, this shows that an item that is in the AIL can still be modified |
| 263 | and relogged, so any tracking must be separate to the AIL infrastructure. As |
| 264 | such, we cannot reuse the AIL list pointers for tracking committed items, nor |
| 265 | can we store state in any field that is protected by the AIL lock. Hence the |
| 266 | committed item tracking needs it's own locks, lists and state fields in the log |
| 267 | item. |
| 268 | |
| 269 | Similar to the AIL, tracking of committed items is done through a new list |
| 270 | called the Committed Item List (CIL). The list tracks log items that have been |
| 271 | committed and have formatted memory buffers attached to them. It tracks objects |
| 272 | in transaction commit order, so when an object is relogged it is removed from |
| 273 | it's place in the list and re-inserted at the tail. This is entirely arbitrary |
| 274 | and done to make it easy for debugging - the last items in the list are the |
| 275 | ones that are most recently modified. Ordering of the CIL is not necessary for |
| 276 | transactional integrity (as discussed in the next section) so the ordering is |
| 277 | done for convenience/sanity of the developers. |
| 278 | |
| 279 | |
| 280 | Delayed Logging: Checkpoints |
| 281 | |
| 282 | When we have a log synchronisation event, commonly known as a "log force", |
| 283 | all the items in the CIL must be written into the log via the log buffers. |
| 284 | We need to write these items in the order that they exist in the CIL, and they |
| 285 | need to be written as an atomic transaction. The need for all the objects to be |
| 286 | written as an atomic transaction comes from the requirements of relogging and |
| 287 | log replay - all the changes in all the objects in a given transaction must |
| 288 | either be completely replayed during log recovery, or not replayed at all. If |
| 289 | a transaction is not replayed because it is not complete in the log, then |
| 290 | no later transactions should be replayed, either. |
| 291 | |
| 292 | To fulfill this requirement, we need to write the entire CIL in a single log |
| 293 | transaction. Fortunately, the XFS log code has no fixed limit on the size of a |
| 294 | transaction, nor does the log replay code. The only fundamental limit is that |
| 295 | the transaction cannot be larger than just under half the size of the log. The |
| 296 | reason for this limit is that to find the head and tail of the log, there must |
| 297 | be at least one complete transaction in the log at any given time. If a |
| 298 | transaction is larger than half the log, then there is the possibility that a |
| 299 | crash during the write of a such a transaction could partially overwrite the |
| 300 | only complete previous transaction in the log. This will result in a recovery |
| 301 | failure and an inconsistent filesystem and hence we must enforce the maximum |
| 302 | size of a checkpoint to be slightly less than a half the log. |
| 303 | |
| 304 | Apart from this size requirement, a checkpoint transaction looks no different |
| 305 | to any other transaction - it contains a transaction header, a series of |
| 306 | formatted log items and a commit record at the tail. From a recovery |
| 307 | perspective, the checkpoint transaction is also no different - just a lot |
| 308 | bigger with a lot more items in it. The worst case effect of this is that we |
| 309 | might need to tune the recovery transaction object hash size. |
| 310 | |
| 311 | Because the checkpoint is just another transaction and all the changes to log |
| 312 | items are stored as log vectors, we can use the existing log buffer writing |
| 313 | code to write the changes into the log. To do this efficiently, we need to |
| 314 | minimise the time we hold the CIL locked while writing the checkpoint |
| 315 | transaction. The current log write code enables us to do this easily with the |
| 316 | way it separates the writing of the transaction contents (the log vectors) from |
| 317 | the transaction commit record, but tracking this requires us to have a |
| 318 | per-checkpoint context that travels through the log write process through to |
| 319 | checkpoint completion. |
| 320 | |
| 321 | Hence a checkpoint has a context that tracks the state of the current |
| 322 | checkpoint from initiation to checkpoint completion. A new context is initiated |
| 323 | at the same time a checkpoint transaction is started. That is, when we remove |
| 324 | all the current items from the CIL during a checkpoint operation, we move all |
| 325 | those changes into the current checkpoint context. We then initialise a new |
| 326 | context and attach that to the CIL for aggregation of new transactions. |
| 327 | |
| 328 | This allows us to unlock the CIL immediately after transfer of all the |
| 329 | committed items and effectively allow new transactions to be issued while we |
| 330 | are formatting the checkpoint into the log. It also allows concurrent |
| 331 | checkpoints to be written into the log buffers in the case of log force heavy |
| 332 | workloads, just like the existing transaction commit code does. This, however, |
| 333 | requires that we strictly order the commit records in the log so that |
| 334 | checkpoint sequence order is maintained during log replay. |
| 335 | |
| 336 | To ensure that we can be writing an item into a checkpoint transaction at |
| 337 | the same time another transaction modifies the item and inserts the log item |
| 338 | into the new CIL, then checkpoint transaction commit code cannot use log items |
| 339 | to store the list of log vectors that need to be written into the transaction. |
| 340 | Hence log vectors need to be able to be chained together to allow them to be |
| 341 | detatched from the log items. That is, when the CIL is flushed the memory |
| 342 | buffer and log vector attached to each log item needs to be attached to the |
| 343 | checkpoint context so that the log item can be released. In diagrammatic form, |
| 344 | the CIL would look like this before the flush: |
| 345 | |
| 346 | CIL Head |
| 347 | | |
| 348 | V |
| 349 | Log Item <-> log vector 1 -> memory buffer |
| 350 | | -> vector array |
| 351 | V |
| 352 | Log Item <-> log vector 2 -> memory buffer |
| 353 | | -> vector array |
| 354 | V |
| 355 | ...... |
| 356 | | |
| 357 | V |
| 358 | Log Item <-> log vector N-1 -> memory buffer |
| 359 | | -> vector array |
| 360 | V |
| 361 | Log Item <-> log vector N -> memory buffer |
| 362 | -> vector array |
| 363 | |
| 364 | And after the flush the CIL head is empty, and the checkpoint context log |
| 365 | vector list would look like: |
| 366 | |
| 367 | Checkpoint Context |
| 368 | | |
| 369 | V |
| 370 | log vector 1 -> memory buffer |
| 371 | | -> vector array |
| 372 | | -> Log Item |
| 373 | V |
| 374 | log vector 2 -> memory buffer |
| 375 | | -> vector array |
| 376 | | -> Log Item |
| 377 | V |
| 378 | ...... |
| 379 | | |
| 380 | V |
| 381 | log vector N-1 -> memory buffer |
| 382 | | -> vector array |
| 383 | | -> Log Item |
| 384 | V |
| 385 | log vector N -> memory buffer |
| 386 | -> vector array |
| 387 | -> Log Item |
| 388 | |
| 389 | Once this transfer is done, the CIL can be unlocked and new transactions can |
| 390 | start, while the checkpoint flush code works over the log vector chain to |
| 391 | commit the checkpoint. |
| 392 | |
| 393 | Once the checkpoint is written into the log buffers, the checkpoint context is |
| 394 | attached to the log buffer that the commit record was written to along with a |
| 395 | completion callback. Log IO completion will call that callback, which can then |
| 396 | run transaction committed processing for the log items (i.e. insert into AIL |
| 397 | and unpin) in the log vector chain and then free the log vector chain and |
| 398 | checkpoint context. |
| 399 | |
| 400 | Discussion Point: I am uncertain as to whether the log item is the most |
| 401 | efficient way to track vectors, even though it seems like the natural way to do |
| 402 | it. The fact that we walk the log items (in the CIL) just to chain the log |
| 403 | vectors and break the link between the log item and the log vector means that |
| 404 | we take a cache line hit for the log item list modification, then another for |
| 405 | the log vector chaining. If we track by the log vectors, then we only need to |
| 406 | break the link between the log item and the log vector, which means we should |
| 407 | dirty only the log item cachelines. Normally I wouldn't be concerned about one |
| 408 | vs two dirty cachelines except for the fact I've seen upwards of 80,000 log |
| 409 | vectors in one checkpoint transaction. I'd guess this is a "measure and |
| 410 | compare" situation that can be done after a working and reviewed implementation |
| 411 | is in the dev tree.... |
| 412 | |
| 413 | Delayed Logging: Checkpoint Sequencing |
| 414 | |
| 415 | One of the key aspects of the XFS transaction subsystem is that it tags |
| 416 | committed transactions with the log sequence number of the transaction commit. |
| 417 | This allows transactions to be issued asynchronously even though there may be |
| 418 | future operations that cannot be completed until that transaction is fully |
| 419 | committed to the log. In the rare case that a dependent operation occurs (e.g. |
| 420 | re-using a freed metadata extent for a data extent), a special, optimised log |
| 421 | force can be issued to force the dependent transaction to disk immediately. |
| 422 | |
| 423 | To do this, transactions need to record the LSN of the commit record of the |
| 424 | transaction. This LSN comes directly from the log buffer the transaction is |
| 425 | written into. While this works just fine for the existing transaction |
| 426 | mechanism, it does not work for delayed logging because transactions are not |
| 427 | written directly into the log buffers. Hence some other method of sequencing |
| 428 | transactions is required. |
| 429 | |
| 430 | As discussed in the checkpoint section, delayed logging uses per-checkpoint |
| 431 | contexts, and as such it is simple to assign a sequence number to each |
| 432 | checkpoint. Because the switching of checkpoint contexts must be done |
| 433 | atomically, it is simple to ensure that each new context has a monotonically |
| 434 | increasing sequence number assigned to it without the need for an external |
| 435 | atomic counter - we can just take the current context sequence number and add |
| 436 | one to it for the new context. |
| 437 | |
| 438 | Then, instead of assigning a log buffer LSN to the transaction commit LSN |
| 439 | during the commit, we can assign the current checkpoint sequence. This allows |
| 440 | operations that track transactions that have not yet completed know what |
| 441 | checkpoint sequence needs to be committed before they can continue. As a |
| 442 | result, the code that forces the log to a specific LSN now needs to ensure that |
| 443 | the log forces to a specific checkpoint. |
| 444 | |
| 445 | To ensure that we can do this, we need to track all the checkpoint contexts |
| 446 | that are currently committing to the log. When we flush a checkpoint, the |
| 447 | context gets added to a "committing" list which can be searched. When a |
| 448 | checkpoint commit completes, it is removed from the committing list. Because |
| 449 | the checkpoint context records the LSN of the commit record for the checkpoint, |
| 450 | we can also wait on the log buffer that contains the commit record, thereby |
| 451 | using the existing log force mechanisms to execute synchronous forces. |
| 452 | |
| 453 | It should be noted that the synchronous forces may need to be extended with |
| 454 | mitigation algorithms similar to the current log buffer code to allow |
| 455 | aggregation of multiple synchronous transactions if there are already |
| 456 | synchronous transactions being flushed. Investigation of the performance of the |
| 457 | current design is needed before making any decisions here. |
| 458 | |
| 459 | The main concern with log forces is to ensure that all the previous checkpoints |
| 460 | are also committed to disk before the one we need to wait for. Therefore we |
| 461 | need to check that all the prior contexts in the committing list are also |
| 462 | complete before waiting on the one we need to complete. We do this |
| 463 | synchronisation in the log force code so that we don't need to wait anywhere |
| 464 | else for such serialisation - it only matters when we do a log force. |
| 465 | |
| 466 | The only remaining complexity is that a log force now also has to handle the |
| 467 | case where the forcing sequence number is the same as the current context. That |
| 468 | is, we need to flush the CIL and potentially wait for it to complete. This is a |
| 469 | simple addition to the existing log forcing code to check the sequence numbers |
| 470 | and push if required. Indeed, placing the current sequence checkpoint flush in |
| 471 | the log force code enables the current mechanism for issuing synchronous |
| 472 | transactions to remain untouched (i.e. commit an asynchronous transaction, then |
| 473 | force the log at the LSN of that transaction) and so the higher level code |
| 474 | behaves the same regardless of whether delayed logging is being used or not. |
| 475 | |
| 476 | Delayed Logging: Checkpoint Log Space Accounting |
| 477 | |
| 478 | The big issue for a checkpoint transaction is the log space reservation for the |
| 479 | transaction. We don't know how big a checkpoint transaction is going to be |
| 480 | ahead of time, nor how many log buffers it will take to write out, nor the |
| 481 | number of split log vector regions are going to be used. We can track the |
| 482 | amount of log space required as we add items to the commit item list, but we |
| 483 | still need to reserve the space in the log for the checkpoint. |
| 484 | |
| 485 | A typical transaction reserves enough space in the log for the worst case space |
| 486 | usage of the transaction. The reservation accounts for log record headers, |
| 487 | transaction and region headers, headers for split regions, buffer tail padding, |
| 488 | etc. as well as the actual space for all the changed metadata in the |
| 489 | transaction. While some of this is fixed overhead, much of it is dependent on |
| 490 | the size of the transaction and the number of regions being logged (the number |
| 491 | of log vectors in the transaction). |
| 492 | |
| 493 | An example of the differences would be logging directory changes versus logging |
| 494 | inode changes. If you modify lots of inode cores (e.g. chmod -R g+w *), then |
| 495 | there are lots of transactions that only contain an inode core and an inode log |
| 496 | format structure. That is, two vectors totaling roughly 150 bytes. If we modify |
| 497 | 10,000 inodes, we have about 1.5MB of metadata to write in 20,000 vectors. Each |
| 498 | vector is 12 bytes, so the total to be logged is approximately 1.75MB. In |
| 499 | comparison, if we are logging full directory buffers, they are typically 4KB |
| 500 | each, so we in 1.5MB of directory buffers we'd have roughly 400 buffers and a |
| 501 | buffer format structure for each buffer - roughly 800 vectors or 1.51MB total |
| 502 | space. From this, it should be obvious that a static log space reservation is |
| 503 | not particularly flexible and is difficult to select the "optimal value" for |
| 504 | all workloads. |
| 505 | |
| 506 | Further, if we are going to use a static reservation, which bit of the entire |
| 507 | reservation does it cover? We account for space used by the transaction |
| 508 | reservation by tracking the space currently used by the object in the CIL and |
| 509 | then calculating the increase or decrease in space used as the object is |
| 510 | relogged. This allows for a checkpoint reservation to only have to account for |
| 511 | log buffer metadata used such as log header records. |
| 512 | |
| 513 | However, even using a static reservation for just the log metadata is |
| 514 | problematic. Typically log record headers use at least 16KB of log space per |
| 515 | 1MB of log space consumed (512 bytes per 32k) and the reservation needs to be |
| 516 | large enough to handle arbitrary sized checkpoint transactions. This |
| 517 | reservation needs to be made before the checkpoint is started, and we need to |
| 518 | be able to reserve the space without sleeping. For a 8MB checkpoint, we need a |
| 519 | reservation of around 150KB, which is a non-trivial amount of space. |
| 520 | |
| 521 | A static reservation needs to manipulate the log grant counters - we can take a |
| 522 | permanent reservation on the space, but we still need to make sure we refresh |
| 523 | the write reservation (the actual space available to the transaction) after |
| 524 | every checkpoint transaction completion. Unfortunately, if this space is not |
| 525 | available when required, then the regrant code will sleep waiting for it. |
| 526 | |
| 527 | The problem with this is that it can lead to deadlocks as we may need to commit |
| 528 | checkpoints to be able to free up log space (refer back to the description of |
| 529 | rolling transactions for an example of this). Hence we *must* always have |
| 530 | space available in the log if we are to use static reservations, and that is |
| 531 | very difficult and complex to arrange. It is possible to do, but there is a |
| 532 | simpler way. |
| 533 | |
| 534 | The simpler way of doing this is tracking the entire log space used by the |
| 535 | items in the CIL and using this to dynamically calculate the amount of log |
| 536 | space required by the log metadata. If this log metadata space changes as a |
| 537 | result of a transaction commit inserting a new memory buffer into the CIL, then |
| 538 | the difference in space required is removed from the transaction that causes |
| 539 | the change. Transactions at this level will *always* have enough space |
| 540 | available in their reservation for this as they have already reserved the |
| 541 | maximal amount of log metadata space they require, and such a delta reservation |
| 542 | will always be less than or equal to the maximal amount in the reservation. |
| 543 | |
| 544 | Hence we can grow the checkpoint transaction reservation dynamically as items |
| 545 | are added to the CIL and avoid the need for reserving and regranting log space |
| 546 | up front. This avoids deadlocks and removes a blocking point from the |
| 547 | checkpoint flush code. |
| 548 | |
| 549 | As mentioned early, transactions can't grow to more than half the size of the |
| 550 | log. Hence as part of the reservation growing, we need to also check the size |
| 551 | of the reservation against the maximum allowed transaction size. If we reach |
| 552 | the maximum threshold, we need to push the CIL to the log. This is effectively |
| 553 | a "background flush" and is done on demand. This is identical to |
| 554 | a CIL push triggered by a log force, only that there is no waiting for the |
| 555 | checkpoint commit to complete. This background push is checked and executed by |
| 556 | transaction commit code. |
| 557 | |
| 558 | If the transaction subsystem goes idle while we still have items in the CIL, |
| 559 | they will be flushed by the periodic log force issued by the xfssyncd. This log |
| 560 | force will push the CIL to disk, and if the transaction subsystem stays idle, |
| 561 | allow the idle log to be covered (effectively marked clean) in exactly the same |
| 562 | manner that is done for the existing logging method. A discussion point is |
| 563 | whether this log force needs to be done more frequently than the current rate |
| 564 | which is once every 30s. |
| 565 | |
| 566 | |
| 567 | Delayed Logging: Log Item Pinning |
| 568 | |
| 569 | Currently log items are pinned during transaction commit while the items are |
| 570 | still locked. This happens just after the items are formatted, though it could |
| 571 | be done any time before the items are unlocked. The result of this mechanism is |
| 572 | that items get pinned once for every transaction that is committed to the log |
| 573 | buffers. Hence items that are relogged in the log buffers will have a pin count |
| 574 | for every outstanding transaction they were dirtied in. When each of these |
| 575 | transactions is completed, they will unpin the item once. As a result, the item |
| 576 | only becomes unpinned when all the transactions complete and there are no |
| 577 | pending transactions. Thus the pinning and unpinning of a log item is symmetric |
| 578 | as there is a 1:1 relationship with transaction commit and log item completion. |
| 579 | |
| 580 | For delayed logging, however, we have an assymetric transaction commit to |
| 581 | completion relationship. Every time an object is relogged in the CIL it goes |
| 582 | through the commit process without a corresponding completion being registered. |
| 583 | That is, we now have a many-to-one relationship between transaction commit and |
| 584 | log item completion. The result of this is that pinning and unpinning of the |
| 585 | log items becomes unbalanced if we retain the "pin on transaction commit, unpin |
| 586 | on transaction completion" model. |
| 587 | |
| 588 | To keep pin/unpin symmetry, the algorithm needs to change to a "pin on |
| 589 | insertion into the CIL, unpin on checkpoint completion". In other words, the |
| 590 | pinning and unpinning becomes symmetric around a checkpoint context. We have to |
| 591 | pin the object the first time it is inserted into the CIL - if it is already in |
| 592 | the CIL during a transaction commit, then we do not pin it again. Because there |
| 593 | can be multiple outstanding checkpoint contexts, we can still see elevated pin |
| 594 | counts, but as each checkpoint completes the pin count will retain the correct |
| 595 | value according to it's context. |
| 596 | |
| 597 | Just to make matters more slightly more complex, this checkpoint level context |
| 598 | for the pin count means that the pinning of an item must take place under the |
| 599 | CIL commit/flush lock. If we pin the object outside this lock, we cannot |
| 600 | guarantee which context the pin count is associated with. This is because of |
| 601 | the fact pinning the item is dependent on whether the item is present in the |
| 602 | current CIL or not. If we don't pin the CIL first before we check and pin the |
| 603 | object, we have a race with CIL being flushed between the check and the pin |
| 604 | (or not pinning, as the case may be). Hence we must hold the CIL flush/commit |
| 605 | lock to guarantee that we pin the items correctly. |
| 606 | |
| 607 | Delayed Logging: Concurrent Scalability |
| 608 | |
| 609 | A fundamental requirement for the CIL is that accesses through transaction |
| 610 | commits must scale to many concurrent commits. The current transaction commit |
| 611 | code does not break down even when there are transactions coming from 2048 |
| 612 | processors at once. The current transaction code does not go any faster than if |
| 613 | there was only one CPU using it, but it does not slow down either. |
| 614 | |
| 615 | As a result, the delayed logging transaction commit code needs to be designed |
| 616 | for concurrency from the ground up. It is obvious that there are serialisation |
| 617 | points in the design - the three important ones are: |
| 618 | |
| 619 | 1. Locking out new transaction commits while flushing the CIL |
| 620 | 2. Adding items to the CIL and updating item space accounting |
| 621 | 3. Checkpoint commit ordering |
| 622 | |
| 623 | Looking at the transaction commit and CIL flushing interactions, it is clear |
| 624 | that we have a many-to-one interaction here. That is, the only restriction on |
| 625 | the number of concurrent transactions that can be trying to commit at once is |
| 626 | the amount of space available in the log for their reservations. The practical |
| 627 | limit here is in the order of several hundred concurrent transactions for a |
| 628 | 128MB log, which means that it is generally one per CPU in a machine. |
| 629 | |
| 630 | The amount of time a transaction commit needs to hold out a flush is a |
| 631 | relatively long period of time - the pinning of log items needs to be done |
| 632 | while we are holding out a CIL flush, so at the moment that means it is held |
| 633 | across the formatting of the objects into memory buffers (i.e. while memcpy()s |
| 634 | are in progress). Ultimately a two pass algorithm where the formatting is done |
| 635 | separately to the pinning of objects could be used to reduce the hold time of |
| 636 | the transaction commit side. |
| 637 | |
| 638 | Because of the number of potential transaction commit side holders, the lock |
| 639 | really needs to be a sleeping lock - if the CIL flush takes the lock, we do not |
| 640 | want every other CPU in the machine spinning on the CIL lock. Given that |
| 641 | flushing the CIL could involve walking a list of tens of thousands of log |
| 642 | items, it will get held for a significant time and so spin contention is a |
| 643 | significant concern. Preventing lots of CPUs spinning doing nothing is the |
| 644 | main reason for choosing a sleeping lock even though nothing in either the |
| 645 | transaction commit or CIL flush side sleeps with the lock held. |
| 646 | |
| 647 | It should also be noted that CIL flushing is also a relatively rare operation |
| 648 | compared to transaction commit for asynchronous transaction workloads - only |
| 649 | time will tell if using a read-write semaphore for exclusion will limit |
| 650 | transaction commit concurrency due to cache line bouncing of the lock on the |
| 651 | read side. |
| 652 | |
| 653 | The second serialisation point is on the transaction commit side where items |
| 654 | are inserted into the CIL. Because transactions can enter this code |
| 655 | concurrently, the CIL needs to be protected separately from the above |
| 656 | commit/flush exclusion. It also needs to be an exclusive lock but it is only |
| 657 | held for a very short time and so a spin lock is appropriate here. It is |
| 658 | possible that this lock will become a contention point, but given the short |
| 659 | hold time once per transaction I think that contention is unlikely. |
| 660 | |
| 661 | The final serialisation point is the checkpoint commit record ordering code |
| 662 | that is run as part of the checkpoint commit and log force sequencing. The code |
| 663 | path that triggers a CIL flush (i.e. whatever triggers the log force) will enter |
| 664 | an ordering loop after writing all the log vectors into the log buffers but |
| 665 | before writing the commit record. This loop walks the list of committing |
| 666 | checkpoints and needs to block waiting for checkpoints to complete their commit |
| 667 | record write. As a result it needs a lock and a wait variable. Log force |
| 668 | sequencing also requires the same lock, list walk, and blocking mechanism to |
| 669 | ensure completion of checkpoints. |
| 670 | |
| 671 | These two sequencing operations can use the mechanism even though the |
| 672 | events they are waiting for are different. The checkpoint commit record |
| 673 | sequencing needs to wait until checkpoint contexts contain a commit LSN |
| 674 | (obtained through completion of a commit record write) while log force |
| 675 | sequencing needs to wait until previous checkpoint contexts are removed from |
| 676 | the committing list (i.e. they've completed). A simple wait variable and |
| 677 | broadcast wakeups (thundering herds) has been used to implement these two |
| 678 | serialisation queues. They use the same lock as the CIL, too. If we see too |
| 679 | much contention on the CIL lock, or too many context switches as a result of |
| 680 | the broadcast wakeups these operations can be put under a new spinlock and |
| 681 | given separate wait lists to reduce lock contention and the number of processes |
| 682 | woken by the wrong event. |
| 683 | |
| 684 | |
| 685 | Lifecycle Changes |
| 686 | |
| 687 | The existing log item life cycle is as follows: |
| 688 | |
| 689 | 1. Transaction allocate |
| 690 | 2. Transaction reserve |
| 691 | 3. Lock item |
| 692 | 4. Join item to transaction |
| 693 | If not already attached, |
| 694 | Allocate log item |
| 695 | Attach log item to owner item |
| 696 | Attach log item to transaction |
| 697 | 5. Modify item |
| 698 | Record modifications in log item |
| 699 | 6. Transaction commit |
| 700 | Pin item in memory |
| 701 | Format item into log buffer |
| 702 | Write commit LSN into transaction |
| 703 | Unlock item |
| 704 | Attach transaction to log buffer |
| 705 | |
| 706 | <log buffer IO dispatched> |
| 707 | <log buffer IO completes> |
| 708 | |
| 709 | 7. Transaction completion |
| 710 | Mark log item committed |
| 711 | Insert log item into AIL |
| 712 | Write commit LSN into log item |
| 713 | Unpin log item |
| 714 | 8. AIL traversal |
| 715 | Lock item |
| 716 | Mark log item clean |
| 717 | Flush item to disk |
| 718 | |
| 719 | <item IO completion> |
| 720 | |
| 721 | 9. Log item removed from AIL |
| 722 | Moves log tail |
| 723 | Item unlocked |
| 724 | |
| 725 | Essentially, steps 1-6 operate independently from step 7, which is also |
| 726 | independent of steps 8-9. An item can be locked in steps 1-6 or steps 8-9 |
| 727 | at the same time step 7 is occurring, but only steps 1-6 or 8-9 can occur |
| 728 | at the same time. If the log item is in the AIL or between steps 6 and 7 |
| 729 | and steps 1-6 are re-entered, then the item is relogged. Only when steps 8-9 |
| 730 | are entered and completed is the object considered clean. |
| 731 | |
| 732 | With delayed logging, there are new steps inserted into the life cycle: |
| 733 | |
| 734 | 1. Transaction allocate |
| 735 | 2. Transaction reserve |
| 736 | 3. Lock item |
| 737 | 4. Join item to transaction |
| 738 | If not already attached, |
| 739 | Allocate log item |
| 740 | Attach log item to owner item |
| 741 | Attach log item to transaction |
| 742 | 5. Modify item |
| 743 | Record modifications in log item |
| 744 | 6. Transaction commit |
| 745 | Pin item in memory if not pinned in CIL |
| 746 | Format item into log vector + buffer |
| 747 | Attach log vector and buffer to log item |
| 748 | Insert log item into CIL |
| 749 | Write CIL context sequence into transaction |
| 750 | Unlock item |
| 751 | |
| 752 | <next log force> |
| 753 | |
| 754 | 7. CIL push |
| 755 | lock CIL flush |
| 756 | Chain log vectors and buffers together |
| 757 | Remove items from CIL |
| 758 | unlock CIL flush |
| 759 | write log vectors into log |
| 760 | sequence commit records |
| 761 | attach checkpoint context to log buffer |
| 762 | |
| 763 | <log buffer IO dispatched> |
| 764 | <log buffer IO completes> |
| 765 | |
| 766 | 8. Checkpoint completion |
| 767 | Mark log item committed |
| 768 | Insert item into AIL |
| 769 | Write commit LSN into log item |
| 770 | Unpin log item |
| 771 | 9. AIL traversal |
| 772 | Lock item |
| 773 | Mark log item clean |
| 774 | Flush item to disk |
| 775 | <item IO completion> |
| 776 | 10. Log item removed from AIL |
| 777 | Moves log tail |
| 778 | Item unlocked |
| 779 | |
| 780 | From this, it can be seen that the only life cycle differences between the two |
| 781 | logging methods are in the middle of the life cycle - they still have the same |
| 782 | beginning and end and execution constraints. The only differences are in the |
| 783 | commiting of the log items to the log itself and the completion processing. |
| 784 | Hence delayed logging should not introduce any constraints on log item |
| 785 | behaviour, allocation or freeing that don't already exist. |
| 786 | |
| 787 | As a result of this zero-impact "insertion" of delayed logging infrastructure |
| 788 | and the design of the internal structures to avoid on disk format changes, we |
| 789 | can basically switch between delayed logging and the existing mechanism with a |
| 790 | mount option. Fundamentally, there is no reason why the log manager would not |
| 791 | be able to swap methods automatically and transparently depending on load |
| 792 | characteristics, but this should not be necessary if delayed logging works as |
| 793 | designed. |