ArchTM阅读笔记

ArchTM: Architecture-Aware, High Performance Transaction for Persistent Memory

ArchTM is a variant of copy-on-write (CoW) system to reduce write traffic to PM.

Introduction

现有的保证数据crash一致性的方法多是基于logging的(问题是双重写入)和基于CoW的(问题是metadata更新会带来很多的微小写入,导致CoW更新的开销太大)。

一些优化PM事务的思路:

  1. 减少data copying;
  2. 减少持久化开销;
    但是这些思路并未考虑PM的微体系结构,而且这对PM的表现还是有非常大(3x-58x)的影响。

所以综合PM的微体系结构,提出了两个设计要点;

  1. 避免256bytes(Optane PM write block)以下的写入;
  2. 鼓励合并写,特别是顺序写;

所以ArchTM也遵循这些设计要点,而且是CoW的变体,目的是减少重复写。为了减少微小写入,ArchTM把metadata的内存管理和数据存储放在DRAM上,虽然会有很高的性能,但是还是会带来crash一致性的问题。这个问题的本质是metadata是事务状态和数据对象(文件)之间唯一的关联。ArchTM引入了一个注释机制来解决这个问题。这个注释机制的基本设计是:把数据对象元数据和TxID放到数据对象中,并且把TxID放到事务元数据中。这里的TxID是一个持久的,并不是动态分配的,而且可以起到连接数据对象和事务状态的作用。所以通过TxID,数据对象ID和数据大小,就可以很容易的定义数据对象,并且检测一致性。

为了鼓励顺序合并写,ArchTM尽可能保证连续的内存分配,因为根据观察,连续分配的数据对象很可能一起更新,但是目前的内存分配策略是管理一系列free list,每个list保存一定的连续内存空间,这样的分配可以避免大量的内存碎片,因此这是一个内存碎片和连续空间之间的tradeoff。所以ArchTM只使用一个free list,同时实现了一个尽可能避免碎片的机制。对于内存分配,使用单一的free list和一个recycle list来进行收集和合并空闲内存快。对于避免碎片,原文说的是:

For defragmentation, ArchTM aggregates data objects in highly fragmented memory regions to create large and contiguous memory blocks.

也不知道是怎么"aggregates data objects"的。。。

总结一下这篇文章的贡献:

  1. 解释PM的微体系结构与PM的事务表现有着非常大的关系;
  2. 提出了两个设计要点以及两个tradeoff,并设计了一个CoW的变体:ArchTM;
  3. 比较了一下,很牛。。。

Background

PM事务

主要两种范式:logging和CoW。

PM事务中的内存管理

目前比较优秀的PM内存分配器都是维护thread-local的free lists和全局的free lists。对于分配请求,现在local free lists上尝试,不行再转发到global free lists上,对于回收请求,空闲的内存块先挂到local free lists上,避免全局free lists出现竞态条件。在PM上的内存碎片的影响要比易失内存上严重,因为他的影响一直存在,此外PM分配器还需要保证metadata保持一致性。

PM架构

PM与CPU之间的数据交换粒度是64B(cacheline size),但是Optane的内部事务块大小是256B的,因此一个cacheline的更新会导致256B的写入(写放大),NVDIMM中存在一个16KB的buffer,来整合顺序写入。如果CPU的连续写入是连续的256B块,那么多个写入可以整合成一个事务。

Performance Characterization

Transaction Performance Study

四种事务系统:

  1. PMDK - undo logging
  2. Romulus - redo logging
  3. DudeTM - redo logging
  4. Oracle - CoW

一个事务中的写操作包括持久化数据,日志(如果用了)以及更新metadata。而且根据实验发现,metadata更新是small writes的主要开销。总的来说,事务系统分为四种类型的metadata:

  1. 事务运行时的metadata:比如事务状态(TxID、事务状态比如commit,prepare等等)
  2. 内存分配的metadata:关于内存消费
  3. Log metadata:log index
  4. metadata for persistent objects:保存持久性对象的指针。

因此容易得出,CoW的系统更容易带来小的写入,因为异地更新,CoW的每次更新都会复制一份数据,remapping pointer并且回收旧数据,因此会带来大量的小写入。

Performance Study of PM Writes

我们知道,每个write都会包括一个或者多个cacheline的刷回。而且基于PM和DRAM之间的读写性能差异,我们希望尽可能减少PM的写,这对提高PM事务的性能非常重要。

一些PM的设备特性:

  1. 顺序写优于随机写(因为Optane的粒度大,会导致write amplification),顺序写对应CoW,随机写对应Logging;
  2. 在写大小等于PM内部的写粒度的倍数时,写带宽会出现一个峰值,是因为PM内部存在一个write combining buffer,这个buffer就是合并64B的写入组成一个256B的写入。就是说,如果我们想要利用write combining buffer,需要尽可能的顺序写

总结

总结一下,这一节就说了两个事:

  1. 小的写入会对PM事务的表现产生影响,而小的写入的主要来源是metadata的更新
  2. PM的顺序写要优于随机写,而且为了利用PM内部的硬件,需要尽可能的用顺序写,避免随机写。

Design Principles and Major Techniques

总的来说,有两大设计原则和5个主要技术。下面分别介绍:

  • 避免小写入
  1. Logless:使用CoW

  2. 最小化PM上的metadata修改,并保证crash consistency

在DRAM上保存metadata,防止metadata在PM上的频繁修改;同时使用一个注释机制关联PM事务状态和持久化数据对象,使得ArchTM可以检测PM上的数据一致性并且可以自动恢复。

  1. Scalable persistent object referencing:ArchTM在DRAM上使用一个scalable object lookup table快速定位PM数据对象的最新副本。
  • 鼓励顺序合并写
  1. 连续的分配请求会分配连续的内存块。

  2. 减少内存碎片。实现了一个GC线程整理内存碎片。

logless

使用CoW来减少PM上的写流量,CoW在commit改变的时候,连续写入连续的内存地址增加了combining buffer的使用概率。但是如果在PM上直接使用CoW会导致额外的metadata更新、remapping以及额外的配置管理。所以把metadata存储在DRAM上。

minimize metadata modification on PM

ArchTM使用一个scalable lookup table查询持久数据的最新修改。但是如果把metadata存在DRAM上,会在把旧的pointer改成新的pointer的时候会出现崩溃一致性的问题,所以,ArchTM使用一个注释机制保证数据一致性。

注释机制:就是在事务的metadata(事务的状态等)中加入一个事务ID。当一个事务状态改为start的时候,这个事务ID立即持久化。同时ArchTM对每个数据对象也进行注释,最重要的是添加TxID以及数据大小,这两种信息可以用来检测数据一致性,而且ArchTM使用TxID来检查这个持久化数据最新的copy、回收那些旧数据以及放弃那些未生效的修改。

scalable object referencing

直接看数据结构部分吧。。。文字比较不好理解。。。

Contiguous Memory Allocations

ArchTM自定义了内存分配以及PM上的事务负载。ArchTM中对大内存的分配使用常规的分配方式(JEMalloc等),对于小内存的分配,使用单一free list和全局的回收进程来优化。

  • single free list

多个free list会导致连续的内存分配请求分配到不同位置,丧失了连续性,为了增加并发性,ArchTM为每个线程分配了free list不同的使用区域,为了减少竞争条件。

  • recycle and merge memory blocks globally

现在的回收方式是每个thread直接回收空闲的内存块,之后同步到global的free list上,这样对空闲内存块的一致性有损害,会导致空闲内存块的地址不连续,所以ArchTM实现了一个helper线程,整理每个thread中的空闲内存块,排序之后再同步到global的free list上。虽然涉及大量的排序等工作,但是这个操作并不在内存回收的关键路径上,因此不会影响内存回收的效率。

Reduce Memory Fragmentation

single free list的局限在于大量的内存碎片,ArchTM通过一个用户态的线上回收机制来减少内存碎片。

Implementation

Data Structures

请添加图片描述

Persistent Data Structures on PM
  • root object:

  • Tx State variables:

记录全部正在进行的事务的状态。TxID是事务开始的硬件时间戳,CommitID是事务结束的硬件时间戳。事务的状态分为BEGIN, COMMITTED, END or ABORT.

  • checkpoint field:

stores a persistent checkpoint of the object lookup table to speedup recovery.

  • checkpoint-diff field:

stores the list of memory blocks preallocated to each thread. 通过一个数组实现,数组元素由以下三个部分组成:使用这个segment的正在进行的事务的TxID,segment的起始地址以及segment大小。

  • user data area

每个object都有一个header,header中包括:objID, size和TxID。

Transient Data Structures on DRAM
  • object lookup table & write-set(Tx-private):
    ArchTM维护一个object lookup table和一个write set(Tx-private)。

一个object lookup table entry包括:object的old和new指针,修改new指向的object的事务对应的TxState(writer)以及一个write lock。
一个write-set(Tx-private)记录了一个正在进行的事务修改的全部objID(objID是object lookup table的index)。事务提交之前,write-set中记录的objIDs对应的object都必须要持久化。

  • two allocators

第一个allocator在创建object时为object分配object lookup table中的一个entry。allocator维护一个free id的list(global)。分配的时候先看free id list(thread-local)中是否存在id,如果存在就重复使用free id(thread-local),如果不存在再从global free id list中新分配ids。
第二个allocator分配持久objects。对于那些常规的分配,重用了JEMalloc中metadata的结构;对于那些小的写入的分配(small writes),ArchTM维护了一个global free list(memory block)和一个global recycle list,为了防止争用,每个thread分别使用global free list的一部分。而global recycle list管理全部threads的空闲memory blocks,而且allocator中维护了每个thread的allocation和deallocation list,每个thread的空闲的memory blocks被整合进global recycle list中。

详细的memory allocation在后文详述。

Background Threads

后台线程对应用层透明。

  • GC manager (for CoW)

GC manager 回收PM objects。

  • fragmentation manager

检验内存区域的使用以及内存块的聚合。(5.4)

Transaction Operations

ArchTM实现了五种关键的操作:begin, read, write, commit & postcommit。

begin的伪代码:

function APT_TX_BEGIN
    volatile TxID = GLOBALTIMESTAMP()
    TxState.ATOMIC_STORE(TxID, BEGIN)
    Fence()
end function

语义比较清晰,说一下一些需要注意的点:

  1. TxState存储在PM的Metadata部分;
  2. TxID是全局的时间戳,通过硬件获取。

read的伪代码:

function APT_TX_READ(TxState, ObjID)
    obj = objLookUpTable[objID]
    if obj.new == NULL then return obj.old
    end if
    if obj.writer -> TxID == TxState.TxID then return obj.new
    end if
    if obj.writer -> State == COMMITTED and obj.writer -> CommitID <= TxState.TxID
    then return obj.new
    end if
    return obj.old
end function

read的3个if判断:

  1. 如果没被修改;
  2. 如果被当前事务修改;
  3. 如果在当前事务开始之前,有其他事务提交;

write的伪代码:

function APT_TX_WRITE(TxState, objID)
    obj <- objLookUpTable[objID]
    if obj.new != NULL and obj.writer -> TxID == TxState.TxID then
        return obj.new
    end if
    if LOCK(obj.writer) then
        obj.writer = &TxState
        obj.new = ALLOC(obj.old)
    else ABORT_AND_RETRY()
    end if
    obj.new = DUPLICATE(obj.old)
    obj.new.header.TxID = TxState.TxID
    # append the object to write-set
    write_set.insert(objID)
    return obj.new
end function

如果已经分配了一个new copy,而且最近的修改是当前事务修改的,那么直接返回obj.new;否则分配一个新的copy,需要先拿到write lock,并且在最后把objID放到write_set中,在commit之前需要保证全部的objID对应的obj都是已经持久化的。

commit的伪代码:

function APT_TX_ON_COMMIT(TxState)
    if EMPTY(write_set) then return
    end if # read-only Tx
    for each obj in write_set do FLUSH(obj.new)
    end for Fence() # persist all modified objects
    volatile CommitID = GLOBALTIMESTAMP()
    TxState.ATOMIC_STORE(COMMITID, CommitID)
    Fence()
    APT_TX_POST_COMMIT(TxState)
end function

语义比较清晰,不再赘述。

postcommit的伪代码:

function APT_TX_POST_COMMIT(TxState)
    for each tx in Ongoing_TXs do
        if tx.TxID < TxState.CommitID then WAIT_FOR(tx)
        end if
    end for
    while obj <- write_set.pop() do
        FREE(obj.old)
        obj.old = obj.new
        obj.new = NULL
        obj.writer = NULL
        UNLOCK(obj.writer)
    end while
    TxState.ATOMIC_STORE(END, INF)
    Fence()
end function

post-commit的作用是:clean up a commited transaction
首先,遍历所有正在进行的事务,如果开始时间(TxID)小于当前事务的提交时间(CommitID),那么需要等待之前的事务完成,因为不确定当前事务的old copy是否被其他事务需要。

随后,回收obj.old(到thread-local deallocation list),之后把当前的new变成old,并且unlock writer。

最后,事务状态转换成END,CommitID变成INF

Memory Management for Transactions

ArchTM使用客制的PM allocator,对内存的分配分为:locality-aware data path和regular data path,regular data path基本是对JEMalloc的复用,本节重点介绍locality-aware的内存分配,主要包括以下三个操作:

  • Allocation

首先在thread-local中的allocation free list中查找合适大小的空闲memory block;
如果thread-local找不到,就在global free list中每次fetch一个segment到thread-local;
如果还找不到,就在global recycle list中补充空闲memory block到global free list。

需要注意的是,每次的fetch操作或者补充操作都存储在CHKT-diff中,每个CHKT-diff数组元素包括:TxID,segment的起始地址(fetch from address),segment大小。

  • Deallocation(GC)

回收没人使用的object。GC manager线程负责的是周期性的从thread-local deallocation lists回收到global recycle list。global recycle list是排好序的。排序的开销不大,因为释放的内存块被添加到global recycle list时,内存块基本是有序的。

  • Defragmentation

defragmentation监控线程在global recycle list中每个内存区域的碎片比例(除以4KB),如果低于 f f f(本文中是50%),就认为未充分使用。ArchTM会自动聚合objects到未充分使用的内存区域,并且把他们通过一个mock write Tx迁移到一块新的内存区域,迁移完成后,原始的数据区域被deallocation。

Recovery Management

recovery分成两个步骤:

一是检测未提交的事务。把所有状态不是COMMITTED, END的TxID记录下来到一个buffer中。同时必须回收这些TxID对应的object的copy。
二是rebuild object lookup table。ArchTM检测PM上的user data area,找到持久的object并在合适的位置(objID->object lookup table index)放入他们的信息(pointers等)。通过比较TxID的大小(时间)来确定最新的object,回收旧的object。

关于crash consistency:

  1. 所有未提交的修改被放弃(recovery时保证);
  2. 所有提交的修改被持久化(事务提交时保证);
  3. 只有最新的object被保存(在设计上保证:更新同一object的一个事务的TxID不得早于另一个事务的CommitID,也就是说,在事务未提交之前,数据对应用是不可见的)。

基于以上三点,认为crash consistency得到保证。

Reduction of Recovery Time

increment checkpoint技术加快recovery的速度。就是把PM最新的修改放到checkpoint中,出现crash时直接从checkpoint恢复。increment checkpoint的具体设计是说在执行完一次checkpoint之后,先暂停所有的事务,并把DRAM上object lookup table中对应的页面设置为write-protection,之后继续事务,那么这个时候那些被写的页面就会触发page fault,此时checkpoint记录下这些page fault对应的页面,在下次执行checkpoint的时候,只copy这些被修改过的页面。(注意,page fault触发一次之后我们就把write-protection解除)

仅仅使用checkpoint加速是不够的,因为如果crash,那么两个checkpoint之间的修改就完全丢失了,所以使用checkpoint-diff这个区域来记录内存segment的获取,以及修改记录。

总结

  1. 一些频繁修改的data可以放到DRAM上,与PM分开存储;
  2. PM的存储架构特性对于事务的表现影响很大;

实现难点:
实现一个memory allocator并且需要区分小的写入(小于64B的)与大的写入。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值