WiredTiger存储引擎-WAL日志之数据结构

WiredTiger存储引擎-WAL日志之数据结构

说明

WiredTiger目前是Mongodb底层的默认存储引擎,它也是MongoDB4.0所带来的ACID特性支持的基石。

WAL日志是各种数据库存储引擎中非常重要的模块,这篇文章专门来详细分析WiredTiger存储引擎的日志实现。

由于知乎这个二逼网站的限制,一篇专栏字数不得超过一定的限制(虽然它也不告诉我限制到底是多少,Fuck,迟早药丸)我只能将其拆分为几个部分,第一部分写数据结构,第二部分写关键流程,真TNND恶心。

数据结构

WT_LOG

typedef struct __wt_log WT_LOG;

struct __wt_log {
    // 日志的最小单位,小于该值的都分配这么多
    // 定义为128
    uint32_t    allocsize;      /* Allocation alignment size */
    uint32_t    first_record;   /* Offset of first record in file */
    wt_off_t    log_written;    /* Amount of log written this period */
    
    ......

    uint16_t    log_version;    /* Version of log file */

    /*
     * System LSNs
     */
    WT_LSN      alloc_lsn;  /* Next LSN for allocation */
    WT_LSN      bg_sync_lsn;    /* Latest background sync LSN */
    WT_LSN      ckpt_lsn;   /* Last checkpoint LSN */
    WT_LSN      first_lsn;  /* First LSN */
    WT_LSN      sync_dir_lsn;   /* LSN of the last directory sync */
    WT_LSN      sync_lsn;   /* LSN of the last sync */
    WT_LSN      trunc_lsn;  /* End LSN for recovery truncation */
    WT_LSN      write_lsn;  /* End of last LSN written */
    WT_LSN      write_start_lsn;/* Beginning of last LSN written */

    .....
#define WT_SLOT_POOL    128
    WT_LOGSLOT  *active_slot;           /* Active slot */
    WT_LOGSLOT   slot_pool[WT_SLOT_POOL];   /* Pool of all slots */
    int32_t      pool_index;        /* Index into slot pool */
    size_t       slot_buf_size;     /* Buffer size for slots */

/* AUTOMATIC FLAG VALUE GENERATION START */
#define WT_LOG_FORCE_NEWFILE    0x1u    /* Force switch to new log file */
#define WT_LOG_OPENED       0x2u    /* Log subsystem successfully open */
#define WT_LOG_TRUNCATE_NOTSUP  0x4u    /* File system truncate not supported */
/* AUTOMATIC FLAG VALUE GENERATION STOP */
    uint32_t    flags;
};

alloc_lsn:全局的log sn,注意这个变量会在slot切换时被更新(如__log_newfile),而每个日志项的LSN会在从Slot内分配空间时生成。当正在写入的slot如果被写满关闭时,会更新全局的alloc_lsn为该slot的最后一个LSN,详见函数__log_slot_close

WT_LOG_RECORD

typedef struct __wt_log_record WT_LOG_RECORD;

struct __wt_log_record {
    uint32_t    len;        /* 00-03: Record length including hdr */
    uint32_t    checksum;   /* 04-07: Checksum of the record */

#define WT_LOG_RECORD_COMPRESSED    0x01u   /* Compressed except hdr */
#define WT_LOG_RECORD_ENCRYPTED     0x02u   /* Encrypted except hdr */
    uint16_t    flags;      /* 08-09: Flags */
    uint8_t     unused[2];  /* 10-11: Padding */
    uint32_t    mem_len;    /* 12-15: Uncompressed len if needed */
    uint8_t     record[0];  /* Beginning of actual data */
};

该结构定义了存储在WAL日志文件内的日志项格式。主要包括日志项头部和内容两部分。record[0]之前的都是日志项头部,从record[0]开始都是日志项内容。

日志文件

WiredTiger的日志文件由日志文件头和WAL日志项组成,其中日志文件头的结构定义为:

typedef struct __wt_log_desc WT_LOG_DESC;

struct __wt_log_desc {
#define WT_LOG_MAGIC        0x101064u
    uint32_t    log_magic;  /* 00-03: Magic number */
#define WT_LOG_VERSION  3
    uint16_t    version;    /* 04-05: Log version */
    uint16_t    unused;     /* 06-07: Unused */
    uint64_t    log_size;   /* 08-15: Log file size */
};

日志文件头部也是一个日志项,日志项包括日志项的标记、crc、日志项内容等主要信息。而日志文件header就是日志项的内容,具体可参考:__log_file_header。每个日志文件最大4GB。

WT_LSN

typedef union __wt_lsn WT_LSN;

union __wt_lsn {
    struct {
#ifdef  WORDS_BIGENDIAN
        uint32_t file;
        uint32_t offset;
#else
        uint32_t offset;
        uint32_t file;
#endif
    } l;
    uint64_t file_offset;
};

每个WAL日志都有一个全局唯一的ID,LSN由两部分组成:

  • file_id:日志文件id
  • offset:日志文件内偏移

而最终的WAL日志项LSN计算原则是:file_id * (1<< 32) + offset

WT_LOGSLOT

SLOT是在内存中缓存写入WAL内容的结构。每个Slot存储多个事务的更新日志,事务提交时也必须按照Slot为单位批量写入,可提升IO吞吐。

每次事务提交时,会先将事务涉及的WAL日志提交,提交的过程涉及分配Slot、将日志拷贝至Slot以及将Slot数据写入日志文件等步骤,我们会在接下来一一描述。

在WiredTiger目前实现中,每个Slot大小为256KB。为了加速Slot的创建,WiredTiger设计了一个全局的Slot pool,可容纳128个Slot,每次需要新Slot的时候直接从该Slot pool分配空闲的即可。

typedef struct __wt_logslot WT_LOGSLOT;

struct __wt_logslot {
    WT_CACHE_LINE_PAD_BEGIN
    volatile int64_t slot_state;    /* Slot state */
    int64_t  slot_unbuffered;   /* Unbuffered data in this slot */
    int  slot_error;        /* Error value */
    wt_off_t slot_start_offset; /* Starting file offset */
    // 记录slot内已写入的最后一个WAL日志项的offset
    // 在__wt_log_slot_release内会更新该值
    wt_off_t slot_last_offset;  /* Last record offset */
    // ???
    WT_LSN   slot_release_lsn;  /* Slot release LSN */
    // slot内起始WAL日志的SN
    WT_LSN   slot_start_lsn;    /* Slot starting LSN */
    // slot内结束WAL日志的SN
    WT_LSN   slot_end_lsn;      /* Slot ending LSN */
    // 底层物理日志文件句柄
    WT_FH   *slot_fh;       /* File handle for this group */
    // buffer,用来缓存WAL日志
    WT_ITEM  slot_buf;      /* Buffer for grouped writes */


    uint32_t flags;
    WT_CACHE_LINE_PAD_END
};

Slot最核心的成员是slot_state,这是实现Slot无锁分配的关键。slot_state为8字节整形,其中编码了三个字段:

flag:暂时不管 joined:表示slot当前已分配的缓冲区offset(按顺序分配); released:表示slot当前已经写入的日志项的总大小,因此,一旦slot被冻结(不再join)且joined = released就表明了该slot内日志项数据已经写入完整了

WT_MY_SLOT

WT_MY_SLOT负责记录每个事务从Slot中为WAL申请的缓冲区位置信息。

typedef struct __wt_myslot WT_MYSLOT;

struct __wt_myslot {
    WT_LOGSLOT  *slot;      /* Slot I'm using */
    wt_off_t     end_offset;    /* My end offset in buffer */
    wt_off_t     offset;    /* Slot buffer offset */

    uint32_t flags;
};

其中:

offset:为WAL日志在WT_LOGSLOT内的起始偏移 end_offset:为WAL日志在WT_LOGSLOT的结束偏移

因此,WAL-N的lsn为:

slot_start_lsn + my_slot->offset
发布于 2018-08-26

文章被以下专栏收录