Home · ASPLOS 2023 · Storage & File Systems

SpecPMT: Speculative Logging for Resolving Crash Consistency Overhead of Persistent Memory

Chencheng Ye, Yuanchao Xu, Xipeng Shen, Yan Sha, Xiaofei Liao, Hai Jin and 1 other

Conference
ASPLOS 2023
Published
2023
Pages
762-777
Citations
13

Memory that survives a power cut lets a program keep its data structures directly in memory, but only if every update is made crash-atomic, and the standard way of doing that — write down the old value, force it to the device, wait, then modify — runs programs about five and a half times slower, which is why almost nobody uses it; this paper (Huazhong, NC State and UCF) observes that a record of a value written at the end of one transaction is already the undo record the next transaction needs, so logging can be moved earlier, most of the waiting disappears, and the overhead falls from over 400 percent to about 10.

The argument in one glance

  1. Problem

    A crash can land in the middle of an update

    Persistent memory keeps its contents through a power failure, which is what makes it interesting — a program can hold its durable data structures directly in memory and carry on after a crash rather than rebuilding from a file. It also creates a hazard that ordinary memory does not have. A crash can strike between two stores, and whatever reached the device is permanent, so a tree can survive with a pointer updated and its target not, in a state no code was ever written to read.

  2. What people did

    Wrap updates in transactions backed by an undo log

    The established answer is a transaction that is atomic with respect to crashes. Before modifying a location, write its old value into a log. If a crash interrupts the transaction, recovery walks the log and puts the old values back, so the program wakes to a state that is either entirely before or entirely after. This is what the industry-standard library does, and the programming model it offers is simple and general.

  3. Why it fell short

    The ordering has to be enforced, and enforcing it means waiting

    The log entry is only useful if it reaches the device before the data does, and the processor will not do that on its own — stores sit in volatile caches until something evicts them. So the program must flush the log line and then execute a fence that stops it until the flush has landed, and reaching the persistence domain takes thousands of cycles. That happens on every update. The standard implementation runs programs about six times slower, and the best research systems still leave 50 to 161 percent overhead.

  4. This paper's idea

    The record you need was already written a transaction ago

    This paper (Chencheng Ye, Yuanchao Xu, Xipeng Shen, Yan Sha, Xiaofei Liao, Hai Jin and Yan Solihin) notices that the two steps of undo logging do not have to happen inside the transaction. If the last transaction that touched a location recorded the value it wrote, that record is exactly the old value the next transaction needs. Logging can therefore be moved earlier, ride out on the previous commit, and cost no fence of its own — which is where nearly all the overhead was.

Why this problem is worth solving

The cost being attacked is not computation but waiting, and it helps to see exactly where.

A processor keeps stores in volatile caches, so a value written to persistent memory has not actually persisted — it sits in a cache line until something evicts it, which may be much later or never. Software therefore has to say explicitly when data must be made durable, using an instruction that writes a dirty cache line back towards memory. That instruction does not wait, so a second instruction, a fence, is used to stop the processor until the write-back has reached the persistence domain — the region, typically memory and the controller’s write queue, whose contents survive a power failure. Getting there takes thousands of cycles. Undo logging needs this pairing because correctness depends on order — the old value must be durable strictly before the new value is written, or a crash between the two leaves nothing to recover from. So the program stalls, on every logged update, and that stall is the crash consistency overhead.
Every design in this area is an attempt to remove fences or move persistence off the critical path.

The second thing to pin down is what the word speculative is doing here.

THE scheme records, at the end of each transaction, the NEW value of every location that transaction wrote. That record has no purpose for the transaction that just committed — its data is already correct. It exists on the bet that some later transaction will modify the same location, at which point the record is exactly that location’s old value and serves as the undo entry, already durable, with no fence needed. The speculation is about locality of updates, and persistent data structures reward it because a small set of hot locations tends to be modified repeatedly. When the bet is wrong the cost is not incorrect recovery but wasted space and write traffic — a log record for a location nobody touches again, which reclamation eventually removes. That is why the memory overhead, not the correctness, is the thing the paper then has to work hard to bound.
The technique therefore trades memory for the elimination of stalls.

What people did before, and where it broke

  • Undo logging in place. The classic and the industry standard. Write the old value to a log, force it out, then modify the data where it sits. Recovery is simple and the data never moves.

  • The gap. A flush and a fence per logged update, each costing thousands of cycles of stall. The widely used implementation runs programs roughly six times slower, which is far outside what anyone will pay.

  • Keep a shadow copy of the data. Maintain a second, always-consistent replica so updates can proceed against the working copy without logging each one.

  • The gap. It doubles the durable footprint and still leaves substantial overhead — the version compared here is 232 percent slower than running without crash consistency at all.

  • Remove the log-to-data ordering in hardware. A line of architectural work eliminates the fence between logging and updating by making the hardware guarantee the order instead of making software wait for it.

  • The gap. It removes the ordering but not the persisting. The data still has to be forced out, and the measurement here shows that on an out-of-order processor the ordering alone was nearly free — a variant that removes only the fences performs about the same as the baseline.

  • Update out of place and apply later. Write new values to a separate region, commit quickly, and let a background process copy them into place.

  • The gap. Commit becomes fast but the background work does not disappear. The scheme compared here needs a dedicated core and 273 KB of on-chip storage per core, and its garbage collection periodically saturates the memory controller’s write queue, contending with the application it is meant to serve.

How a log record earns its keep twice

One transaction, with the logging moved out of the critical path

  1. new

  2. new

  3. new

  4. new

  5. new

The pivotal step is the fourth — the same record is a redo entry for the transaction that wrote it and an undo entry for the next transaction that touches the location.

The idea

One record does two jobs, which is why both fences disappear. Write down what a transaction wrote, at the moment it commits, and that record has two futures. Read forward, it is a redo entry — it says what the data should contain, so the data itself never needs to be flushed at commit, because a crash can be repaired by replaying the record. Read backward from the next transaction, it is an undo entry — it holds the value that existed before that transaction started, and it is already durable, so no fence is needed before modifying the location. Both of the stalls that undo logging imposes are gone for the same reason. Recovery then becomes unusually simple, because discarding the records of uncommitted transactions and replaying the rest in order simultaneously undoes what was interrupted and redoes what completed. The dependence on a compiler or programmer to place a logging call after every durable update is the quiet assumption underneath all of it, and the persistency race work covered elsewhere on this site is a long argument for treating exactly that assumption with suspicion, since what the compiler emits need not match what the source appears to say.

The bet is on hot data, and the failed bets cost memory. Speculatively logging every update means keeping a record for every location a program has recently written, whether or not it will be written again. That nearly triples the durable footprint in the software-only version and requires a background thread on a dedicated core to reclaim records whose data has since become durable. The hardware variant fixes this by only speculating where the bet is likely to pay — tracking update frequency per page with a small saturating counter in each translation buffer entry, using speculative logging for pages that cross the threshold and ordinary undo logging for the rest. That gives a knob: raise the hotness threshold and the log area shrinks. At 2.6 percent extra memory the speedup is 1.12 times, at 15 percent it is 1.36, at 20 percent it is 1.4. Deciding which data deserves the expensive treatment by measuring how often it is written is the same instinct as the persistent memory allocator work covered elsewhere on this site, which found that most of an allocator’s time went into flushing metadata and restructured the layout around that measurement.

Reclamation has to be cheap or it eats the gain. A scheme that produces a record per update per transaction produces a great many stale records, and cleaning them up is where a design of this kind usually falls over — the compared out-of-place system pays for its fast commits with garbage collection that saturates the write queue and contends with the application. The hardware version answers this by making the log ordered in time and cut into epochs, with the epoch a record belongs to stored in the translation buffer entry. Reclaiming an epoch is then one unprivileged instruction that clears the matching bits, plus freeing the space, and it runs in the foreground with no dedicated thread. Choosing an epoch size is a trade — the implementation starts a new one after 2 MB of records or 200 speculatively logged pages. Keeping the log sequential rather than indexed matters too, and the paper measures it — a design that stores exactly one record per location in a hash table saves memory and runs 3.2 times slower, because persistent memory is much worse at random writes than sequential ones, a property the persistent-memory graphics processor work covered elsewhere on this site also builds its logging around.

Two transactions, four snapshots

How the log for one transaction becomes the safety net for the next

Two consecutive transactions that both update locations a and b, watching what is durable at each moment.

Step 1 of 5

The first transaction commits

Both locations have been updated in place, and records holding their new values have been appended to the log. The commit flushed those records and executed one fence. The data itself may still be sitting in volatile cache.

That is safe because the records say what the data should be. A crash here loses nothing — recovery replays the records and the locations end up with the values the committed transaction gave them.

Why it works

  • A record of what was written is a record of what was there. Logging the new value at commit → the next transaction to modify that location finds its old value already logged and already durable → the fence between logging and updating disappears, which was one of the two stalls per update.
  • A durable record makes the data itself unnecessary to flush. The same record describes what the location should contain → a crash before the data is written back is repairable by replay → the second stall, forcing data out at commit, disappears too, which is where the software version gets most of its gain.
  • Recovery unifies undo and redo. Discarding uncommitted records and replaying the rest oldest to newest → completed transactions are redone and interrupted ones are effectively undone by the previous transaction’s records → one simple pass, with stale leftovers harmless because a fresher record supersedes them.
  • Speculating only on hot pages bounds the cost. A small saturating counter per translation buffer entry marks pages written often enough to be worth it → cold pages keep ordinary undo logging → memory overhead becomes a tunable knob, from 2.6 percent for 1.12 times speedup to 20 percent for 1.4 times.
  • Sequential logs beat space-optimal ones on this device. Appending records rather than replacing them through an index → writes stay sequential, which persistent memory handles far better than random ones → the space-efficient alternative was measured 3.2 times slower.

What it costs (trade-offs and scope)

  • Memory, substantially. The software-only design nearly triples the durable footprint and needs a core running a background reclamation thread whose trigger level has to be tuned. The hardware design exists mainly to make that bounded.
  • New hardware for the good version. The low-overhead variant needs status bits in translation buffer entries, two flags per first-level cache line, a bulk copy engine, and two new instructions. It is modest as architecture proposals go, but it is not something you can run today.
  • The logging call must be placed correctly. Every durable update needs its record, inserted by the programmer or a compiler pass. That is the same trust surface every logging scheme has, and it is the one that has historically produced subtle bugs.
  • Data written by other programs needs special handling. A location with no speculative record has no protection, so durable data produced elsewhere, or by an earlier run, must be snapshotted before it can be updated under this scheme.
  • The hardware results are simulated. The software design runs on a real machine with real persistent memory modules, but the hardware variant is evaluated in a full-system simulator, and its comparisons against other architectural proposals inherit that.

The evaluation

PropertyWhat was measuredThe resultWhat it tells you
Software design against the standard
Where the software gain comes from
Hardware design against architectural proposals
A negative result worth noting
Memory cost as a dial
On two benchmarks the hardware design beats the no-logging bound outright, because it replaces scattered writes to data with sequential writes to a log, which this memory technology prefers.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. Why does classic undo logging require a fence on every logged update?

  2. 2. What does a speculative log record contain, and what is the bet?

  3. 3. A space-optimal design would keep exactly one record per location, indexed by address. Why was the sequential append chosen instead?

  4. 4. In hardware, a variant that removed the log-to-data fences but still persisted data at commit performed about the same as the baseline. What does that tell you?

Sources

SpecPMT: Speculative Logging for Resolving Crash Consistency Overhead of Persistent Memory. Chencheng Ye, Yuanchao Xu, Xipeng Shen, Yan Sha, Xiaofei Liao, Hai Jin, Yan Solihin. ASPLOS 2023, Volume 2, pages 762 to 777. DOI 10.1145/3575693.3575696. Full text read from the authors’ copy hosted at the second author’s academic page.

Related papers on Storage & File Systems

See all 64 →