精华内容
参与话题
问答
  • RocksDB

    2021-01-08 05:19:57
    <p>Default values taken from: https://github.com/facebook/rocksdb/blob/189f0c27aaecdf17ae7fc1f826a423a28b77984f/utilities/leveldb_options/leveldb_options.cc</p> <p>Descriptions taken from: ...
  • RocksDB: A Persistent Key-Value Store for Flash and RAM Storage RocksDB is developed and maintained by Facebook Database Engineering Team. It is built on earlier work on LevelDB by Sanjay Ghemawat...
  • rocksdb

    千次阅读 2014-02-19 15:56:39
    1. key随机插入导致rocksdb有较大的写放大。 6MB的网络吞吐量,50+MB的磁盘写入。(原因待查)  改为顺序插入,6MB的网络吞吐量,10-20+的磁盘写入。   2. 初始化传参很重要,文件数、cache及队列大小等。...

    1. key随机插入导致rocksdb有较大的写放大。 6MB的网络吞吐量,50+MB的磁盘写入。(原因待查)

        改为顺序插入,6MB的网络吞吐量,10-20+的磁盘写入。

     

    2. 初始化传参很重要,文件数、cache及队列大小等。(细节待总结)

     

    3. 多线程写入效率待查。

     

    4.大数量条目的DB,启动时自检时间较长。第一次重启时,10,000,000需要10s,,50,000,000需要2min,100,000,000需要1小时。

     

    =====================================================

     

    1、初始化参数: class Options(负责数据存储的组织),  class Env/PosixEnv(负责文件操作的调度)。db_bench的参考意义很大。

    level0_file_num_compaction_trigger   4
    level0_slowdown_writes_trigger       16(-1)
    level0_stop_writes_trigger           64


    target_file_size_base  64M
    target_file_size_multiplier 1

    max_bytes_for_level_base 512M
    max_bytes_for_level_multiplier 10

    disableDataSync    load数据时候用

    max_background_compactions 20
    max_write_buffer_number   3
    rate_limit_delay_max_milliseconds 1000

    arena_block_size  write_buffer_size / 10
    write_buffer_size   128M

    open_files    500000
    block_cache_size  16G 初始化时指定的

    delete_obsolete_files_period_micros 300s


    *******************************
    statistics
    stats_interval
    stats_per_interval

     

     

    ***********************************
    min_level_to_compress
    merge_operator
    mmap_read
    mmap_write
    sync (maybe write option)
    table_cache_numshardbits
    use_fsync
    use_multiget
    max_background_flushes

    //write-ahead logs (WAL)
    wal_size_limit_MB
    wal_ttl_seconds
    disable_seek_compaction  //TODO
    disable_wal

    block_cache
    block_cache_compressed

    compression_level
    compression_ratio

    CompactionOptionsUniversal=======
    universal_compression_size_percent
    universal_max_merge_width
    universal_max_size_amplification_percent
    universal_min_merge_width
    universal_size_ratio

    table_cache_numshardbits 4
    min_write_buffer_number_to_merge (1 default????)

    use_adaptive_mutex (false default。节省系统和用户切换)

    max_bytes_for_level_multiplier_additional  1(向量)

    hard_rate_limit   0.0
    soft_rate_limit   0.0

    ********************************************
    db_bench*********************************
    histogram
    memtablerep
    statistics
    threads

    num
    numdistinct
    read_range
    readonly
    reads
    readwritepercent
    use_existing_db
    value_size
    warn_missing_keys
    writes
    writes_per_second

     

    2、get对压缩线程的运行很敏感,性能抖动很大。但是压缩线程是必须的,负责维持level的平衡。

    3、读写性能的平衡难于把握。目前的配置下read:update:  50:50, 25000ops,  95:5, 30000ops
     

    Dell T5600

    CPU: Intel(R) Xeon(R) CPU E5-2650 @ 2.00GHz/8 cores

    RAM: 64GB

    OS: CentOS 6.4

     
     write_threads: 1
     read_threads: 8

    # in GB
     cache_size: 48
     # in KB
     block_size: 32
     # in MB
     write_buffer_size: 128
     # yes|no
     compression: yes
     # default 200
     max_open_files: 5000
     # in MB
     target_file_size_base: 64
     # in MB
     max_bytes_for_level_base: 512
     # yes|no
     disableDataSync: no
     #Speed strategy
     level0_file_num_compaction_trigger: 4
     level0_slowdown_writes_trigger: 16
     level0_stop_writes_trigger: 64
     #just for bulk loading data: yes|no
     disableDataSync:  no
     max_background_compactions: 8
     max_write_buffer_number: 8
     rate_limit_delay_max_milliseconds: 1000
     # in KB, write_buffer_size / 10
     arena_block_size: 12800
     # in second
     delete_obsolete_files_period_micros: 300

     

    展开全文
  • <div><p>Adding recipe for rocksdb : https://github.com/facebook/rocksdb python-rocksdb : https://github.com/twmht/python-rocksdb</p>该提问来源于开源项目:conda-forge/staged-recipes</p></div>
  • <div><p>~~Blocked on https://github.com/rust-rocksdb/rust-rocksdb/issues/401.~~ ~~Blocked on https://github.com/rust-rocksdb/rust-rocksdb/issues/411.~~</p><p>该提问来源于开源项目:paritytech/...
  • <p>rocksdb.rocksdb w9 [ fail ] Test ended at 2015-09-16 21:23:21 <p>CURRENT_TEST: rocksdb.rocksdb --- /data/users/jenkins/workspace/github-mysql-nightly/BUILD_TYPE/ASan/CLIENT_MODE/Async/PAGE_SIZE/32...
  • rocksdb写放大_Rocksdb

    2020-12-22 05:54:54
    Apache Flink中的RocksDB状态后端 RocksDB 的 compaction 策略,并且提到了读放大、写放大和空间放大的概念,对 RocksDB 的调优本质上就是在这三个因子之间取得平衡。而在 Flink 作业这种注重实时性的场合,则要重点...

    Apache Flink中的RocksDB状态后端

    RocksDB 的 compaction 策略,并且提到了读放大、写放大和空间放大的概念,对 RocksDB 的调优本质上就是在这三个因子之间取得平衡。而在 Flink 作业这种注重实时性的场合,则要重点考虑读放大和写放大。

    70adaa8c24d53648254cfb199da45bd9.png

    Tuning MemTable

    memtable 作为 LSM Tree 体系里的读写缓存,对写性能有较大的影响。以下是一些值得注意的参数。为方便对比,下文都会将 RocksDB 的原始参数名与 Flink 配置中的参数名一并列出,用竖线分割。

    • write_buffer_size | state.backend.rocksdb.writebuffer.size
      单个 memtable 的大小,默认是64MB。当 memtable 大小达到此阈值时,就会被标记为不可变。一般来讲,适当增大这个参数可以减小写放大带来的影响,但同时会增大 flush 后 L0、L1 层的压力,所以还需要配合修改 compaction 参数,后面再提。
    • max_write_buffer_number | state.backend.rocksdb.writebuffer.count
      memtable 的最大数量(包含活跃的和不可变的),默认是2。当全部 memtable 都写满但是 flush 速度较慢时,就会造成写停顿,所以如果内存充足或者使用的是机械硬盘,建议适当调大这个参数,如4。
    • min_write_buffer_number_to_merge | state.backend.rocksdb.writebuffer.number-to-merge
      在 flush 发生之前被合并的 memtable 最小数量,默认是1。举个例子,如果此参数设为2,那么当有至少两个不可变 memtable 时,才有可能触发 flush(亦即如果只有一个不可变 memtable,就会等待)。调大这个值的好处是可以使更多的更改在 flush 前就被合并,降低写放大,但同时又可能增加读放大,因为读取数据时要检查的 memtable 变多了。经测试,该参数设为2或3相对较好。

    Tuning Block/Block Cache

    block 是 sstable 的基本存储单位。block cache 则扮演读缓存的角色,采用 LRU 算法存储最近使用的 block,对读性能有较大的影响。

    • block_size | state.backend.rocksdb.block.blocksize
      block 的大小,默认值为4KB。在生产环境中总是会适当调大一些,一般32KB比较合适,对于机械硬盘可以再增大到128~256KB,充分利用其顺序读取能力。但是需要注意,如果 block 大小增大而 block cache 大小不变,那么缓存的 block 数量会减少,无形中会增加读放大。
    • block_cache_size | state.backend.rocksdb.block.cache-size
      block cache 的大小,默认为8MB。由上文所述的读写流程可知,较大的 block cache 可以有效避免热数据的读请求落到 sstable 上,所以若内存余量充足,建议设置到128MB甚至256MB,读性能会有非常明显的提升。

    Tuning Compaction

    compaction 在所有基于 LSM Tree 的存储引擎中都是开销最大的操作,弄不好的话会非常容易阻塞读写。建议看官先读读前面那篇关于 RocksDB 的 compaction 策略的文章,获取一些背景知识,这里不再赘述。

    • compaction_style | state.backend.rocksdb.compaction.style
      compaction 算法,使用默认的 LEVEL(即 leveled compaction)即可,下面的参数也是基于此。
    • target_file_size_base | state.backend.rocksdb.compaction.level.target-file-size-base
      L1层单个 sstable 文件的大小阈值,默认值为64MB。每向上提升一级,阈值会乘以因子 target_file_size_multiplier(但默认为1,即每级sstable最大都是相同的)。显然,增大此值可以降低 compaction 的频率,减少写放大,但是也会造成旧数据无法及时清理,从而增加读放大。此参数不太容易调整,一般不建议设为256MB以上。
    • max_bytes_for_level_base | state.backend.rocksdb.compaction.level.max-size-level-base
      L1层的数据总大小阈值,默认值为256MB。每向上提升一级,阈值会乘以因子 max_bytes_for_level_multiplier(默认值为10)。由于上层的大小阈值都是以它为基础推算出来的,所以要小心调整。建议设为 target_file_size_base 的倍数,且不能太小,例如5~10倍。
    • level_compaction_dynamic_level_bytes | state.backend.rocksdb.compaction.level.use-dynamic-size
      这个参数之前讲过。当开启之后,上述阈值的乘法因子会变成除法因子,能够动态调整每层的数据量阈值,使得较多的数据可以落在最高一层,能够减少空间放大,整个 LSM Tree 的结构也会更稳定。对于机械硬盘的环境,强烈建议开启。

    Generic Parameters

    • max_open_files | state.backend.rocksdb.files.open
      顾名思义,是 RocksDB 实例能够打开的最大文件数,默认为-1,表示不限制。由于sstable的索引和布隆过滤器默认都会驻留内存,并占用文件描述符,所以如果此值太小,索引和布隆过滤器无法正常加载,就会严重拖累读取性能。
    • max_background_compactions/max_background_flushes | state.backend.rocksdb.thread.num
      后台负责 flush 和 compaction 的最大并发线程数,默认为1。注意 Flink 将这两个参数合二为一处理(对应 DBOptions.setIncreaseParallelism() 方法),鉴于 flush 和 compaction 都是相对重的操作,如果 CPU 余量比较充足,建议调大,在我们的实践中一般设为4。
      在深入了解配置参数之前,让我们首先重新讨论在flink中如何使用RocksDB来进行状态管理。当您选择RocksDB作为状态后端时,您的状态将被序列化成字节存在堆外内存或本地磁盘中。RocksDB是一个键值存储,它被组织为一个日志结构的合并树(LMS树)。当用于在Flink中存储Keyed状态时,Key由<Keygroup,Key,Namespace>的序列化字节组成,而value由序列化之后的state的字节组成。每次注册keyed状态时,它都会映射到column family(类似于传统数据库中的表),并且键值对将作为序列化字节存储在RocksDB中。这意味着每次READ或WRITE操作都不得不对数据进行序列化/反序列化,

    使用RocksDB作为状态后端有许多优点:它不受垃圾回收的影响,与堆中的对象相比,它通常会有较低的内存开销,并且它是目前唯一支持增量检查点的选项。 此外,使用RocksDB,您的状态大小仅受限于可用本地磁盘空间大小,最适合依赖大型状态操作的Flink应用程序。

    如果你不熟悉RocksDB,下图说明了其基本的READ和WRITE操作。

    RocksDB中的写操作将数据存储在当前活动的内存表(Active MemTable)中。当内存表已满时,它将变为READ ONLY MemTable,并被一个新的、空闲的active状态的MemTable替换。READ ONLY MemTable会被后台线程周期性地flush到磁盘,成为按照key排序的的只读文件 – 即所谓的SSTables。反过来,SSTables是不可变的,通过后台日志压缩将他们整合到一起(SSTables的多路归并)。如前所述,使用RocksDB,每个注册状态都是一个column family,这意味着每个状态都包含自己的MemTables和SSTables。

    2425591cb58e3f619f18f732daa84384.png

    在RocksDB中的READ操作首先访问Active Memory Table以响应查询。 如果找不到要搜索的key,则READ操作会根据key从最新到最旧READ ONLY MemTables依次查找,直到找到要搜索的key。 如果在任何MemTable中都找不到该key,则READ操作将再次从最新的位置开始访问SSTable。 SSTable文件可以从BlockCache、(如果它包含未压缩的表文件)从操作系统的文件高速缓存获得,或者在最坏的情况下从本地磁盘获得。 像SST级别的bloom filters的可选索引可以帮助避免命中磁盘。

    3种配置来管理您的RocksDB内存消耗

    现在我们已经使用Apache Flink建立了基于RocksDB的一些功能,让我们来看看可以帮助您更有效地管理RocksDB内存大小的配置选项。 请注意,以下选项并非是全面的,您可以使用Apache Flink 1.6中引入的State TTL(Time-To-Live)功能管理Flink应用程序的状态大小。 以下三个配置是帮助您有效管理RocksDB资源消耗的良好起点:

    1.block_cache_size

    此配置将最终控制在内存中缓存的未压缩的最大的块数。 随着块数的增加,内存大小也会增加 - 因此,通过预先配置它,您可以保持特定的内存消耗级别。

    2.write_buffer_size

    此配置建立并控制RocksDB中MemTable的最大大小。 Active MemTables和READ ONLY MemTables最终将影响RocksDB中的内存大小,因此尽早调整它可能会为您节省一些麻烦。

    3.max_write_buffer_number

    在RocksDB将state作为SS Tables刷新到本地磁盘之前,此配置决定并控制内存中保留的最大MemTable的数量。 这实际上也决定了在内存中 READ ONLY 状态的MemTables的最大数量。

    除了上面提到的配置之外,您还可以选择性的配置消耗额外内存空间的索引和 bloom filters ,以及侧边的table cache。 表缓存不仅会占用RocksDB中的额外内存,它还会保存打开文件描述符到默认情况下不受限的SST文件,如果配置不正确,可能会和操作系统的配置发生冲突。

    我们刚刚引导您完成了一些用RocksDB作为Flink中的状态后端的的配置选项,这将帮助我们有效的管理内存大小。有关更多配置选项,我们建议您查看RocksDB调优指南或Apache Flink文档。

    1. RocksDB 简介

    • RocksDB 是由 Facebook 基于 LevelDB 开发的一款提供键值存储与读写功能的数据库软件,旨在充分实现快存上存储数据的服务能力。
    • RocksDB是一个c++库,可以用来存储keys和values,且keys和values可以是任意的字节流,支持原子的读和写;
    • RocksDB是一个基于LSM-Tree 存储引擎实现的数据库架构,LSM 通过 将磁盘的随机写转化为顺序写来提高写性能,而付出的代价就是牺牲部分读性能 、 写放大;
    • RocksDB 所有的数据在引擎中是有序存储,可以支持Get(key)、Put(Key)、Delete(Key)和NewIterator()。
    • RocksDB的基本组成是memtable、sstfile和logfile。

    2. RocksDB 数据格式

    2.1 RocksDB表记录的存储格式

    d0fe63889765d58df6628823e36d459c.png

    以上面的例子为基础,我们来逐一分解其数据的存储格式。

    首先,Key 是一个行的唯一标识“rowid”,由于表没有主键, 系统会产生一个 bigint 类型的 rowid 作为主键, 占用 8 个字节。

    然后,Value 的最前面部分就是存放记录的 null 信息根据记录中可以为空字段的个数, 确认需要占用的字节数, 如果小于 8 个, 则只需要一个字节。例子中, c1,c3,c4,c6 均可以为空, 因此需要 4 个 bit, 所以用 1 个 byte 表示 Null-flag 即可。对于null, 无论是定长还是非定长数据类型, 都不占用真实的存储空间, 只需要一个 bit 位来表示为 null 即可;对于定长字段, 则会补全;

    对于变长字段, 如果数据长度小于 256,len 只需要占用一个 byte; 如果 len 大于 255, 且小于 65536, 则需要占用 2 个字节, 对于 longblob 类型, 则需要占 4 个字节;

    最后,meta 主要是 SequenceID, 这个 SequenceID 在事务提交时产生, 主要用于 RocksDB 实现并发事务控制。

    2.2 RocksDB索引的存储格式

    RocksDB 中, 所有的数据都是通过索引来组织, 每个索引有一个全局唯一的索引标识“index_id” 。

    索引主要包括两类: 主键索引和二级索引, 他们的数据结构一样, 都包括 key, value, meta 三部分。但是 value 中不包含任何数据, 只是包含 checksum(检查数据文件的校验位) 信息。具体格式如下所示:

    表2.2.1 主健索引

    daac726acf22e5b94ff5659fb85db48b.png

    表2.2.2 二级索引

    18f902dba36aa94eae5db812f9e52485.png

    对于索引长度限制也有所不同, 对于 InnoDB 引擎来说, 索引中单列长度不能超过 767 个字节, 而 RocksDB 引擎单列长度不超过 2048 个字节。因为id字段是主键索引,当查询条件是 where id = XX 或者where id in ( XX,XX,XX )的时候,可以根据Rocksdb的get或者mget接口,高效的获取某一条和某几条数据。当查询条件是 where id > XX and id < XX 这样的range查询的时候,我们可以创建一个迭代器。这个操作需要一个seek随机读和一个scan顺序读操作,也有很高的读取性能。

    3. RocksDB 整体架构及读写分析

    3 .1 RocksDB 基础架构组成

    RocksDB的基本组成是 WAL、 memtable、sstfile 。

    • WAL 是一个顺序写的文件。写请求会先将数据写到 WAL。
    • memtable是一种内存数据结构,当WAL写入之后,数据会写到memtable中。
    • sstfile是持久化数据文件,内存表中数据溢出或定期写入sstfile。

    如图3.1所示Rocksdb的基础架构。Rocksdb中引入了ColumnFamily(列族, CF)的概念,所谓列族也就是一系列kv组成的数据集,所有的读写操作都需要先指定列族。写操作先写WAL,再写memtable,memtable达到一定阈值后切换为Immutable Memtable,只能读不能写。后台Flush线程负责按照时间顺序将Immutable Memtable刷盘,生成level0层的有序文件(SST)。后台合并线程负责将上层的SST合并生成下层的SST。Manifest负责记录系统某个时刻SST文件的视图,Current文件记录当前最新的Manifest文件名。每个ColumnFamily有自己的Memtable, SST文件,所有ColumnFamily共享WAL、Current、Manifest文件。

    图3.1 RocksDB 基础架构图

    175f542275158da21b88aff7a1c18893.png

    3.2 RocksDB 数据写入分析

    图 3.2.1 RocksDB 写入流程图

    17e3de2b22d8148130b03d8d5522d436.png

    WAL文件结构如下图,按照写入的顺序来存储变长的K-V,按照固定长度来分组存储(可能一个K-V跨多个分组)的目的是便于读取

    9c85366716cb26a1531ff9e142023547.png

    支持几种SST文件结构

    fcb0e6e4c0d68cea9cfc22f26c8be50b.png

    上图为按照多块来存储的结构。每块的K-V都是有序的,而多块也是有序的。文件中包含元数据相关的信息,包括数据压缩字典、过滤器等。会按照数据块所属的K-V范围来创建索引,为提升查询性能会给索引分片。

    521650ef6419cddcb94a9c923684ed7c.png

    另外一种结构是每个K-V来存储。它的索引比较特殊,由hash结构和二进制查找缓存两部分组成。依然按照key的前缀做hash,如果桶对应的K-V记录很少,则直接指向第一个key(有多个key属于该桶)的记录位置。如果属于桶的K-V记录多于16条,或者包含多于一个前缀的记录,则先指向二进制查找缓存(先二分查找),而后指向第一个key的记录位置。

    随着K-V的写入,会生成很多的SST文件。这部分文件需要被合并到一起,从而降低打开文件数量,并且移除已经不存在的记录。

    如图所示,任何的写入都会先写到 WAL,然后在写入 Memory Table(Memtable)。当然为了性能,也可以不写入 WAL,但这样就可能面临崩溃丢失数据的风险。Memory Table 通常是一个能支持并发写入的 skiplist,但 RocksDB 同样也支持多种不同的 skiplist,用户可以根据实际的业务场景进行选择。当一个 Memtable 写满了之后,就会变成 immutable 的 Memtable,RocksDB 在后台会通过一个 flush 线程将这个 Memtable flush 到磁盘,生成一个 Sorted String Table(SST) 文件,放在 Level 0 层。当 Level 0 层的 SST 文件个数超过阈值之后,就会通过 Compaction 策略将其放到 Level 1 层,以此类推。

    图 3.2.2 RocksDB Compaction

    e7835ceddf827935af7c0c0469bb4f62.png

    关于Compaction,如图所示,处于L0的SST文件是无需的,重叠的。而经过 Compaction 处理之后的 L1、L2 ….LN 层的SST文件是被有序并且逐步合并为大文件的过程。

    如果没有 Compaction,那么写入是非常快的,但会造成读性能降低,同样也会造成很严重的空间放大问题。为了平衡写入,读取,空间这些问题,RocksDB 会在后台执行 Compaction,将不同 Level 的 SST 进行合并。但 Compaction 并不是没有开销的,它也会占用 I/O,所以势必会影响外面的写入和读取操作。对于 RocksDB 来说,他有三种 Compaction 策略,一种就是默认的 Leveled Compaction,另一种就是 Universal Compaction,还有一种就是 FIFO Compaction。

    接下来,我们来针对RocksDB写的特点进行性能的定性分析:

    ( 1 ) . 所有的刷盘操作都采用append方式,这种方式对磁盘和SSD是相当有诱惑力的;

    ( 2 ) . 写操作写完WAL和Memtable就立即返回,写效率非常高。

    ( 3 ) . 由于最终的数据是存储在离散的SST中,SST文件的大小可以根据kv的大小自由配置。

    简而言之,在RocksDB中的读取需要处理的最核心的一个问题就是如何读取最新的数据,这是由于RocksDB是基于LSM,因此在RocksDB中,对于数据的delete以及update,它并不会立即去执行对应的动作,而只是插入一条新的数据,而数据的最终更新(last-write-win)以及删除是在compact的时候来做的.

    3.3 RocksDB 数据读出分析

    如图所示,相对于写流程,读取的流程还是相对而言比较简单的,结合读取流程图,我们这里简单的梳理一下读取经过哪几个步骤,看看RocksDB的数据读取是怎么样进行处理的。

    图 3.3.1 RocksDB 读取流程图

    44fe0e1b523d9648824ea1259b44d808.png

    首先,获取当前时刻的SuperVersion。SuperVersion是RocksDB内针对于所有SST文件列表以及内存中的Memtable和Immutable memtable的一个版本。

    接着,获取当前的序号来决定当前读操作依赖的数据快照。

    然后,尝试从第一步SuperVersion中引用的Memtable以及Immutable memtable中获取对应的值。

    再次,尝试从Block cache中读取

    最后,尝试从sst文件中获取。

    整个流程其实是比较清晰的,主要是涉及到Memtable的查找,以及SST内数据的查找。对于Memtable内部的查找,相对而言还是有很多细节的,篇幅原因不做展开。我们主要针对SST文件的查找来做进一步展开分析:

    在Level0(L0)中的数据文件是可能会有数据重叠部分的,所以在L0中查找一个key,我们需要依次查找所有的SST文件;而对于非L0层级,每个文件都是按顺序存放的。首先我们需要遍历搜索Level 0(L0)层所有的文件,假设没有找到则继续。到了L1层,该层数据文件是有序的,每一个文件都会有一个FileMetaData保存着该文件的最小key和最大key,同时还有IndexUnit(记录关键KEY值的数据结构)。对L1层文件进行二分查找来找到第一个largest key大于要查找key的文件。我们继续对此SST文件内的key进行查找,假如没有找到,则继续。理论上到L1层我们可以再次进行二分查找来找到对应的key,但是借助IndexUnit我们可以快速过滤不需要查找的文件,提升查找速度。

    4 RocksDB的适用场景分析

    4.1 RocksDB 的特性分析

    RocksDB 虽然在代码层面上是在LevelDB原有的代码上进行开发的,但却借鉴了Apache HBase的一些好的思想。LevelDB则是一个比较单一的存储引擎,也是因为LevelDB的单一性,在做具体的应用的时候一般需要对其作进一步扩展。相对于LevelDB而言,RocksDB在很多地方做了改进:

    (1). RocksDB支持一次获取多个K-V,还支持Key范围查找。

    (2) . RocksDB 除了简单的Put、Delete操作,还提供了一个Merge操作,为了对多个Put操作进行合并(站在引擎实现者的角度来看,相比其带来的价值,其实现的成本要昂贵很多)。

    (3). RocksDB 支持多线程合并。LSM型的数据结构,最大的性能问题就出现在其合并的时间损耗上,在多CPU的环境下,多线程合并那是 LevelDB所无法比拟的。

    (4). RocksDB增加了合并时过滤器,对一些不再符合条件的K-V进行丢弃。

    (5) . RocksDB可采用多种压缩算法,除了LevelDB用的snappy,还有zlib、bzip2。LevelDB里面按数据的压缩率(压缩后低于75%)判断是否对数据进行压缩存储,而RocksDB典型的做法是Level 0-2不压缩,最后一层使用zlib,而其它各层采用snappy。

    (6) . RocksDB 支持管道式的Memtable,也就说允许根据需要开辟多个Memtable,以解决写入与压缩速度差异的性能瓶颈问题。

    由于框架一开始的定位就是需要支持强一致性分布式存储,所以如何实现分布式事务成为一个大挑战。作者学习了CockroachDB及TiDB等数据库的实现方式后,决定参考TiDB的实现方式,但不同于使用乐观方式而是采用悲观锁方式,遇到事务冲突采用排队的方式而不是重启事务。

    一、二阶段(2PC)递交流程:

    参考下图举例说明一下流程:

    baf14c5a2e8c19290c95e367c0157c0b.png
    1. 业务服务开始事务,其所在的节点作为事务协调者新建一个事务实例(使用HLC作为事务开始时间戳);
    2. 协调者将命令1加入事务命令列表(如果是第一个命令则作为事务主记录),同时向表1的RaftLeader发送命令,表1状态机处理命令时上锁成功后返回(包含当前节点的HLC);
    3. 同步骤2,但记录事务主记录是哪个,用于检测事务状态;
    4. 业务服务处理完后通知协调者递交事务,协调者选取所有事务参与者节点最大的HLC作为事务递交时间戳;
    5. 协调者发送事务递交命令至事务主记录所在表1的RaftLeader,在表1RaftGroup持久化事务主记录状态后通知协调者立即向业务服务返回事务是否成功递交;
    6. 事务主记录所在的RaftLeader向其他参与者的RaftLeader异步通知事务递交。

    每个RaftGroup's Leader都有定时器进行事务状态检测:

    • 如果检测到挂起的命令是事务主记录,则与协调者通信检测其是否存活;
    • 如果检测到挂起的命令非事务主记录,则与事务主记录所在的RaftLeader通信检测事务状态。

    二、上锁及冲突检测流程:

    简单说明一下每个Raft节点的状态机的处理流程:

    1. Apply事务命令的RaftLog时,先检测当前锁列表是否存在冲突,如果没有冲突上锁成功持久化锁信息后返回;如果存在冲突则排入已上锁队列并持久化锁信息,等待上级锁事务递交后再返回;
    2. Apply事务递交命令时,从当前锁列表内找到对应的命令,持久化写入命令对应的KV数据至底层的RocksDB内,如果当前锁有等待队列,则依次将队列重新尝试上锁;
    3. 接收到读命令时,如果与当前锁冲突,则根据事务开始时间戳判断是通知冲突事务向后更新递交时间戳,还是忽略该冲突。
    如果Raft节点意外崩溃后重新启动时,会先从存储加载锁信息恢复当前锁列表。

    三、性能测试:

    根据作者的经验,锁并不是影响并发性能的原因,冲突才是,所以做了个简单的并发测试。

    1. 并发更新同一条记录(冲突激烈):

    虚拟机(I74C8G) wrk -t2 -c120 测试约3900tps

    对比参照(MacbookPro13 I74C16G):

    • PostgreSql: 64线程: 调用640000次共耗时: 179295毫秒 平均每秒调用: 3569
    • Cockroach: 64线程: 调用6400次共耗时: 90784毫秒 平均每秒调用: 70

    2. 事务插入两条记录(无冲突):

    64线程: 调用64000次共耗时: 4008毫秒 平均每秒调用: 15968

    四、小结:

    本篇介绍了框架集成的分布式存储引擎是如何实现分布式事务的,当然还有很多优化待做(如单分区事务递交优化等)。如果您有问题或Bug报告,请留言或在[GitHub]提交Issue,另外您的关注与点赞将是作者最大的动力。

    RocksDB事务实现TransactionDB分析

    8995cbfb3be254a1375753a8e8ca1fbf.png

    Winsdons 2017-11-21 14:51:47

    71d0e36d447513f2238e0076d71b0dd4.png

    4787

    fa6505c984434a80d639ee27ee855b0c.png

    收藏 3

    基本概念
    1. LSN (log sequence number)
    RocksDB中的每一条记录(KeyValue)都有一个LogSequenceNumber(后面统称lsn),从最初的0开始,每次写入加1。该值为逻辑量,区别于InnoDB的lsn为redo log物理写入字节量。
    我有几张阿里云幸运券分享给你,用券购买或者升级阿里云相应产品会有特惠惊喜哦!把想要买的产品的幸运券都领走吧!快下手,马上就要抢光了。
    这个lsn在RocksDB内部的memtable中是单调递增的,在WriteAheadLog(WAL)中以WriteBatch为单位递增(count(batch.records)为单位)。
    WriteBatch是一次RocksDB::Put()的原子操作集合,不同的WriteBatch间是遵循ACID特性(要么完全成功要么完全失败,并且相互隔离),结构如下:


    1. WriteBatch :=
    2. sequence: fixed64

    3. count: fixed32
    4. data: record[count]


    从RocksDB外部能看到的LSN是按WriteBatch递增的(LeaderWriter(或LastWriter)最后一次性更新),所以进行snapshot读时,使用的就是此lsn。
    注意: 在WAL中每条WriteBatch的lsn并不严格满足以下公式(比如2pc情况下):lsn(WriteBatch[n]) < lsn(WriteBatch[n+1]),可能相等
    2. Snapshot
    Snapshot是RocksDB的快照,实际存储的就是一个lsn.

    1. class SnapshotImpl {
    2. public:

    3. // 当前的lsn

    4. SequenceNumber number_;
    5. private:

    6. SnapshotImpl* prev_;

    7. SnapshotImpl* next_;

    8. SnapshotList* list_;

    9. // unix时间戳
    10. int64_t unix_time_;

    11. // 是否属于Transaction(用于写冲突)
    12. bool is_write_conflict_boundary_;

    13. };


    查询时如果设置了snapshot为某个lsn, 那么对于此snapshot的读来说,只能看到lsn(key)<=lsn(snapshot)的key,大于该lsn的key是不可见的。
    snapshot的创建和删除都需要由一个全局的DoubleLinkList (DBImpl::SnapshotList)管理,天然的根据创建时间(同样也是lsn大小)的关系排序,使用之后需要通过DBImpl::ReleaseSnapshot释放。snapshot还用于在RocksDB事务中实现不同的隔离级别。

    1.1 LSM 与 WriteBatch


    参考文档5提到RocksDB 是一个快速存储系统,它会充分挖掘 Flash or RAM 硬件的读写特性,支持单个 KV 的读写以及批量读写。RocksDB 自身采用的一些数据结构如 LSM/SKIPLIST 等结构使得其有读放大、写放大和空间使用放大的问题。

    89d8d900f6093d1ea42a67cff1fe5387.png

    LSM 大致结构如上图所示。LSM 树而且通过批量存储技术规避磁盘随机写入问题。 LSM 树的设计思想非常朴素, 它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。磁盘中的树定期可以做 merge 操作,合并成一棵大树,以优化读性能【读数据的过程可能需要从内存 memtable 到磁盘 sstfile 读取多次,称之为读放大】。RocksDB 的 LSM 体现在多 level 文件格式上,最热最新的数据尽在 L0 层,数据在内存中,最冷最老的数据尽在 LN 层,数据在磁盘或者固态盘上。RocksDB 还有一种日志文件叫做 manifest,用于记录对 sstfile 的更改,可以认为是 RocksDB 的 GIF,后面将会详述。

    LSM-Tree(Log-Structured-Merge-Tree)LSM从命名上看,容易望文生义成一个具体的数据结构,一个tree。但LSM并不是一个具体的数据结构,也不是一个tree。LSM是一个数据结构的概念,是一个数据结构的设计思想。实际上,要是给LSM的命名断句,Log和Structured这两个词是合并在一起的,LSM-Tree应该断句成Log-Structured、Merge、Tree三个词汇,这三个词汇分别对应以下三点LSM的关键性质:

    • 将数据形成Log-Structured:在将数据写入LSM内存结构之前,先记录log。这样LSM就可以将有易失性的内存看做永久性存储器。并且信任内存上的数据,等到内存容量达到threshold再集体写入磁盘。将数据形成Log-Structured,也是将整体存储结构转换成了“内存(in-memory)”存储结构。
    • 将所有磁盘上数据不组织成一个整体索引结构,而组织成有序的文件集:因为磁盘随机读写比顺序读写慢3个数量级,LSM尽量将磁盘读写转换成顺序读写。将磁盘上的数据组织成B树这样的一个整体索引结构,虽然查找很高效,但是面对随机读写,由于大量寻道导致其性能不佳。而LSM用了一种很有趣的方法,将所有数据不组织成一个整体索引结构,而组织成有序的文件集。每次LSM面对磁盘写,将数据写入一个或几个新生成的文件,顺序写入且不能修改其他文件,这样就将随机读写转换成了顺序读写。LSM将一次性集体写入的文件作为一个level,磁盘上划分多level,level与level之间互相隔离。这就形成了,以写入数据时间线形成的逻辑上、而非物理上的层级结构,这也就是为什么LSM被命名为”tree“,但不是“tree”。
    • 将数据按key排序,在合并不同file、level上的数据时类似merge-join:如果一直保持生成新的文件,不仅写入会造成冗余空间,而且也会大量降低读的性能。所以要高效的、周期性合并不同file、level。而如果数据是乱序的,根本做不到高效合并。所以LSM要将数据按key排序,在合并不同file、level上的数据时类似 merge-join。

    很明显,LSM牺牲了一部分读的性能和增加了合并的开销,换取了高效的写性能。那LSM为什么要这么做?实际上,这就关系到对于磁盘写已经没有什么优化手段了,而对于磁盘读,不论硬件还是软件上都有优化的空间。通过多种优化后,读性能虽然仍是下降,但可以控制在可接受范围内。实际上,用于磁盘上的数据结构不同于用于内存上的数据结构,用于内存上的数据结构性能的瓶颈就在搜索复杂度,而用于磁盘上的数据结构性能的瓶颈在磁盘IO,甚至是磁盘IO的模式。

    以上三段摘抄自参考文档20。个人以为,除了将随机写合并之后转化为顺写之外,LSM 的另外一个关键特性就在于其是一种自带数据 Garbage Collect 的有序数据集合,对外只提供了 Add/Get 接口,其内部的 Compaction 就是其 GC 的关键,通过 Compaction 实现了对数据的删除、附带了 TTL 的过期数据地淘汰、同一个 Key 的多个版本 Value 地合并。RocksDB 基于 LSM 对外提供了 Add/Delete/Get 三个接口,用户则基于 RocksDB 提供的 transaction 还可以实现 Update 语义。

    展开全文
  • <div><p>This adds support to run RocksDB with a custom <a href="https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File">options file</a>. <p>This also updates the RocksDB Maven artifact version...
  • <div><p>1) Add rocksdb lz4 compression support, can be activate via <code>--db-enable-compression</code> argument 2) Option to use rocksdb as local blockchain cache (replaces blocks.bin/sqlite), ...
  • RocksDB作为一个开源的存储引擎支持事务的ACID特性,而要支持ACID中的I(Isolation),并发控制这块是少不了的,本文主要讨论RocksDB的锁机制实现,细节会涉及到源码分析,希望通过本文读者可以深入了解RocksDB并发控制...

    RocksDB作为一个开源的存储引擎支持事务的ACID特性,而要支持ACID中的I(Isolation),并发控制这块是少不了的,本文主要讨论RocksDB的锁机制实现,细节会涉及到源码分析,希望通过本文读者可以深入了解RocksDB并发控制原理。文章主要从以下4方面展开,首先会介绍RocksDB锁的基本结构,然后我会介绍RocksDB行锁数据结构设计下,锁空间开销,接着我会介绍几种典型场景的上锁流程,最后会介绍锁机制中必不可少的死锁检测机制。

    1.行锁数据结构

    RocksDB锁粒度最小是行,对于KV存储而言,锁对象就是key,每一个key对应一个LockInfo结构。所有key通过hash表管理,查找锁时,直接通过hash表定位即可确定这个key是否已经被上锁。但如果全局只有一个hash表,会导致这个访问这个hash表的冲突很多,影响并发性能。RocksDB首先按Columnfamily进行拆分,每个Columnfamily中的锁通过一个LockMap管理,而每个LockMap再拆分成若干个分片,每个分片通过LockMapStripe管理,而hash表(std::unordered_map<:string lockinfo>)则存在于Stripe结构中,Stripe结构中还包含一个mutex和condition_variable,这个主要作用是,互斥访问hash表,当出现锁冲突时,将线程挂起,解锁后,唤醒挂起的线程。这种设计很简单但也带来一个显而易见的问题,就是多个不相关的锁公用一个condition_variable,导致锁释放时,不必要的唤醒一批线程,而这些线程重试后,发现仍然需要等待,造成了无效的上下文切换。对比我们之前讨论的InnoDB锁机制,我们发现InnoDB是一个page里面的记录复用一把锁,而且复用是有条件的,同一个事务对一个page的若干条记录加锁才能复用;而且锁等待队列是精确等待,精确到记录级别,不会导致的无效的唤醒。虽然RocksDB锁设计比较粗糙,但也做了一定的优化,比如在管理LockMaps时,通过在每个线程本地缓存一份拷贝lock_maps_cache_,通过全局链表将每个线程的cache链起来,当LockMaps变更时(删除columnfamily),则全局将每个线程的copy清空,由于columnfamily改动很少,所以大部分访问LockMaps操作都是不需要加锁的,提高了并发效率。

    相关数据结构如下:

    struct LockInfo {

    bool exclusive; //排它锁或是共享锁

    autovector txn_ids; //事务列表,对于共享锁而言,同一个key可以对应多个事务

    // Transaction locks are not valid after this time in us

    uint64_t expiration_time;

    }

    struct LockMapStripe {

    // Mutex must be held before modifying keys map

    std::shared_ptr stripe_mutex;

    // Condition Variable per stripe for waiting on a lock

    std::shared_ptr stripe_cv;

    // Locked keys mapped to the info about the transactions that locked them.

    std::unordered_map<:string lockinfo> keys;

    }

    struct LockMap {

    const size_t num_stripes_; //分片个数

    std::atomic lock_cnt{0}; //锁数目

    std::vector lock_map_stripes_; //锁分片

    }

    class TransactionLockMgr {

    using LockMaps = std::unordered_map>;

    LockMaps lock_maps_;

    // Thread-local cache of entries in lock_maps_. This is an optimization

    // to avoid acquiring a mutex in order to look up a LockMap

    std::unique_ptr lock_maps_cache_;

    }

    2.行锁空间代价

    由于锁信息是常驻内存,我们简单分析下RocksDB锁占用的内存。每个锁实际上是unordered_map中的一个元素,则锁占用的内存为key_length+8+8+1,假设key为bigint,占8个字节,则100w行记录,需要消耗大约22M内存。但是由于内存与key_length正相关,导致RocksDB的内存消耗不可控。我们可以简单算算RocksDB作为MySQL存储引擎时,key_length的范围。对于单列索引,最大值为2048个字节,具体可以参考max_supported_key_part_length实现;对于复合索引,索引最大长度为3072个字节,具体可以参考max_supported_key_length实现。假设最坏的情况,key_length=3072,则100w行记录,需要消耗3G内存,如果是锁1亿行记录,则需要消耗300G内存,这种情况下内存会有撑爆的风险。因此RocksDB提供参数配置max_row_locks,确保内存可控,默认RDB_MAX_ROW_LOCKS设置为1G,对于大部分key为bigint场景,极端情况下,也需要消耗22G内存。而在这方面,InnoDB则比较友好,hash表的key是(space_id, page_no),所以无论key有多大,key部分的内存消耗都是恒定的。前面我也提到了InnoDB在一个事务需要锁大量记录场景下是有优化的,多个记录可以公用一把锁,这样也间接可以减少内存。

    3.上锁流程分析

    前面简单了解了RocksDB锁数据结构的设计以及锁对内存资源的消耗。这节主要介绍几种典型场景下,RocksDB是如何加锁的。与InnoDB一样,RocksDB也支持MVCC,读不上锁,为了方便,下面的讨论基于RocksDB作为MySQL的一个引擎来展开,主要包括三类,基于主键的更新,基于二级索引的更新,基于主键的范围更新等。在展开讨论之前,有一点需要说明的是,RocksDB与InnoDB不同,RocksDB的更新也是基于快照的,而InnoDB的更新基于当前读,这种差异也使得在实际应用中,相同隔离级别下,表现有所不一样。对于RocksDB而言,在RC隔离级别下,每个语句开始都会重新获取一次快照;在RR隔离级别下,整个事务中只在第一个语句开始时获取一次快照,所有语句共用这个快照,直到事务结束。

    3.1.基于主键的更新

    这里主要接口是TransactionBaseImpl::GetForUpdate

    1).尝试对key加锁,如果锁被其它事务持有,则需要等待

    2).创建snapshot

    3).调用ValidateSnapshot,Get key,通过比较Sequence判断key是否被更新过

    4).由于是加锁后,再获取snapshot,所以检查一定成功。

    5).执行更新操作

    这里有一个延迟获取快照的机制,实际上在语句开始时,需要调用acquire_snapshot获取快照,但为了避免冲突导致的重试,在对key加锁后,再获取snapshot,这就保证了在基于主键更新的场景下,不会存在ValidateSnapshot失败的场景。

    堆栈如下:

    1-myrocks::ha_rocksdb::get_row_by_rowid

    2-myrocks::ha_rocksdb::get_for_update

    3-myrocks::Rdb_transaction_impl::get_for_update

    4-rocksdb::TransactionBaseImpl::GetForUpdate

    {

    //加锁

    5-rocksdb::TransactionImpl::TryLock

    6-rocksdb::TransactionDBImpl::TryLock

    7-rocksdb::TransactionLockMgr::TryLock

    //延迟获取快照,与acquire_snapshot配合使用

    6-SetSnapshotIfNeeded()

    //检查key对应快照是否过期

    6-ValidateSnapshot

    7-rocksdb::TransactionUtil::CheckKeyForConflict

    8-rocksdb::TransactionUtil::CheckKey

    9-rocksdb::DBImpl::GetLatestSequenceForKey //第一次读取

    //读取key

    5-rocksdb::TransactionBaseImpl::Get

    6-rocksdb::WriteBatchWithIndex::GetFromBatchAndDB

    7-rocksdb::DB::Get

    8-rocksdb::DBImpl::Get

    9-rocksdb::DBImpl::GetImpl //第二次读取

    }

    3.2.基于主键的范围更新

    1).创建Snapshot,基于迭代器扫描主键

    2).通过get_row_by_rowid,尝试对key加锁

    3).调用ValidateSnapshot,Get key,通过比较Sequence判断key是否被更新过

    4).如果key被其它事务更新过(key对应的SequenceNumber比Snapshot要新),触发重试

    5).重试情况下,会释放老的快照并释放锁,通过tx->acquire_snapshot(false),延迟获取快照(加锁后,再拿snapshot)

    5).再次调用get_for_update,由于此时key已经被加锁,重试一定可以成功。

    6).执行更新操作

    7).跳转到1,继续执行,直到主键不符合条件时,则结束。

    3.3.基于二级索引的更新

    这种场景与3.2类似,只不过多一步从二级索引定位主键过程。

    1).创建Snapshot,基于迭代器扫描二级索引

    2).根据二级索引反向找到主键,实际上也是调用get_row_by_rowid,这个过程就会尝试对key加锁

    3).继续根据二级索引遍历下一个主键,尝试加锁

    4).当返回的二级索引不符合条件时,则结束

    3.4 与InnoDB加锁的区别

    前面我们说到了RocksDB与InnoDB的一点区别是,对于更新场景,RocksDB仍然是快照读,而InnoDB是当前读,导致行为上的差异。比如在RC隔离级别下的范围更新场景,比如一个事务要更新1000条记录,由于是边扫描边加锁,可能在扫描到第999条记录时,发现这个key的Sequence大于扫描的快照(这个key被其它事务更新了),这个时候会触发重新获取快照,然后基于这个快照拿到最新的key值。InnoDB则没有这个问题,通过当前读,扫描过程中,如果第999条记录被更新了,InnoDB可以直接看到最新的记录。这种情况下,RocksDB和InnoDB看到的结果是一样的。在另外一种情况下,假设也是扫描的范围中,新插入了key,这key的Sequence毫无疑问会比扫描的Snapshot要大,因此在Scan过程中这个key会被过滤掉,也就不存在所谓的冲突检测了,这个key不会被找到。更新过程中,插入了id为1和900的两条记录,最后第900条记录由于不可见,所以更新不到。而对于InnoDB而言,由于是当前读,新插入的id为900的记录可以被看到并更新,所以这里是与InnoDB有区别的地方。

    除了更新基于快照这个区别以外,RocksDB在加锁上也更简洁,所有加锁只涉及唯一索引,具体而言,在更新过程中,只对主键加锁;更新列涉及唯一约束时,需要加锁;而普通二级索引,则不用加锁,这个目的是为了避免唯一约束冲突。这里面,如果更新了唯一约束(主键,或者唯一索引),都需要加锁。而InnoDB则是需要对每个索引加锁,比如基于二级索引定位更新,则二级索引也需要加锁。之所以有这个区别是,是因为InnoDB为了实现RR隔离级别。这里稍微讲下隔离级别,实际上MySQL中定义的RR隔离级别与SQL标准定义的隔离级别有点不一样。SQL标准定义RR隔离级别解决不可重复读的问题,Serializable隔离级别解决幻读问题。不可重复读侧重讲同一条记录值不会修改;而幻读则侧重讲两次读返回的记录条数是固定的,不会增加或减少记录数目。MySQL定义RR隔离级别同时解决了不可重复读和幻读问题,而InnoDB中RR隔离级别的实现就是依赖于GAP锁。而RocksDB不支持GAP锁(仅仅支持唯一约束检查,对不存在的key加锁),因为基于快照的机制可以有效过滤掉新插入的记录,而InnoDB由于当前读,导致需要通过间隙锁禁止其它插入,所以二级索引也需要加锁,主要是为了锁间隙,否则两次当前读的结果可能不一样。当然,对RC割裂级别,InnoDB普通二级索引也是没有必要加锁的。

    4.死锁检测算法

    死锁检测采用BFS((Breadth First Search,宽度优先算法),基本思路根据加入等待关系,继续查找被等待者的等待关系,如果发现成环,则认为发生了死锁。需要说明的是InnoDB的死锁检测采用的DFS(Deepth First Search,深度优先算法),逻辑都是类似的,目的是为了找环。本身BFS和DFS两种图搜索算法时间复杂度也相同O(V+E),V和E分别表示节点数目和边的数目。主要实现区别在于,BFS一般与队列配合使用,先进先出;DFS一般与栈配合使用,先进后出。当然在大并发系统下,锁等待关系非常复杂,为了将死锁检测带来的资源消耗控制在一定范围,可以通过设置deadlock_detect_depth来控制死锁检测搜索的深度,或者在特定业务场景下,认为一定不会发生死锁,则关闭死锁检测,这样在一定程度上有利于系统并发的提升。需要说明的是,如果关闭死锁,最好配套将锁等待超时时间设置较小,避免系统真发生死锁时,事务长时间hang住。死锁检测基本流程如下:

    1.定位到具体某个分片,获取mutex

    2.调用AcquireLocked尝试加锁

    3.若上锁失败,则触发进行死锁检测

    4.调用IncrementWaiters增加一个等待者

    5.如果等待者不在被等待者map里面,则肯定不会存在死锁,返回

    6.对于被等待者,沿着wait_txn_map_向下检查等待关系(一次加入所有等待的事务列表,然后逐个分析),看看是否成环

    7.若发现成环,则将调用DecrementWaitersImpl将新加入的等待关系解除,并报死锁错误。

    相关的数据结构:

    class TransactionLockMgr {

    // Must be held when modifying wait_txn_map_ and rev_wait_txn_map_.

    std::mutex wait_txn_map_mutex_;

    // Maps from waitee -> number of waiters.

    HashMap rev_wait_txn_map_;

    // Maps from waiter -> waitee.

    HashMap> wait_txn_map_;

    DecrementWaiters //

    IncrementWaiters //

    }

    struct TransactionOptions {

    bool deadlock_detect = false; //是否检测死锁

    int64_t deadlock_detect_depth = 50; //死锁检测的深度

    int64_t lock_timeout = -1; //等待锁时间,线上一般设置为5s

    int64_t expiration = -1; //持有锁时间,

    }

    展开全文
  • 写在开篇想学习RocksDB的原因是,公司一个分布书KV存储框架的一次分享,发现他们的底层是用的RocksDB。这样就引起了我的好奇。后来发现公司的好几个框架底层都是使用的RocksDB,包括MySQL 也可以选择RocksDB,所以...

    写在开篇

    想学习RocksDB的原因是,公司一个分布书KV存储框架的一次分享,发现他们的底层是用的RocksDB。这样就引起了我的好奇。后来发现公司的好几个框架底层都是使用的RocksDB,包括MySQL 也可以选择RocksDB,所以促使我去了解,为什么要用这个框架。

    由于篇幅问题,这个topic会拆成几篇文章完成,本人也是零基础开始学习的,如果有想法可以一起讨论和研究哈。

    Design

    0c6238624f9f32af8399c2202c93b7d2.png
    图1 RocksDB的设计

    RocksDB的设计思想是数据冷热分离,怎样理解这个“冷热数据”分离呢?新写入的“热数据”会保存在内存中,如果一段时间没有更新,冷数据会“下沉”到磁盘底层的“表层文件”,如果继续没有更新过这个问题,冷数据继续“下沉”到更底层的文件中。如果磁盘底层的冷数据被修改了,它又会再次进入内存,一段时间后又会被持久化刷回到磁盘文件的浅层,然后再慢慢往下移动到底层。

    RocksDB是基于LevelDB的思想开发的,想了解其中区别的,可以点击下方链接了解哈

    Features not in LevelDBgithub.com

    Performance

    摘自 RocksDB wiki :

        1. it should be performant for fast storage and for server workloads
        2. It should support efficient point lookups as well as range scans.
        3. It should be configurable to support high random-read workloads, high update workloads or a combination of both.
        4. Its architecture should support easy tuning of trade-offs for different workloads and hardware.

    B Tree & LSM Tree

    谈RocksDB就不得不说这两个数据结构了,B tree是我们常见的数据结构,尤其是B+ tree, 学习MySQL 索引的同学应该都会或多或少了解过

    B Tree

    27f6ecfbd3f924a157c120536aac9867.png
    图2 B+tree (source:http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/3-index/B-tree=delete3.html)

    简单过一下B+tree 好处:

    1. 非叶子节点不存储数据,只记录索引,在相同内存下,可以存储更多的索引。
    2. 叶子节点存储实际记录行,磁盘上的位置相对紧密。
    3. 叶子之间增加链表,范围查询更加快,不用再进行中序遍历。

    由于“数据预读”和“局部性原理”,每次磁盘读写都是一页一页来读,这种设计很适合范围查询,也就是“顺序读”。

    如果遇到了“随机写”,会发生什么呢?

    ceebbd6c8da933e58bad0abc41acc11f.png
    图3 B tree 随机写

    如上图所示,如果我们同时插入10和1000,这两个值在叶子节点的距离很远,这样会导致,我们存在磁盘的距离很远,也就是有可能不在同一页,那么我们就需要多次写IO,才能完成数据的插入,而写占用了大量的磁盘IO,那么读的性能也会受到影响。

    为了解决这个问题LSM tree 应运而生,具体可参考

    https://www.cs.umb.edu/~poneil/lsmtree.pdfwww.cs.umb.edu

    LSM

    1. 适合于高频写入的同时,提供快速地查找,通过牺牲了部分读性能,用来大幅提高写性能。这个设计思想的依据是:
      1. 内存的速度超磁盘1000倍以上。而读取的性能提升,主要还是依靠内存命中率而非磁盘读的次数
      2. 写入不占用磁盘的IO,读取就能获取更长时间的磁盘IO使用权,从而也可以提升读取效率。
    2. 数据随机写操作(包括插入、修改、删除也是写)都在内存中进行,这样也一定程度地提成了写的性能。

    345ea67a418d0c8c4bc1c0c4f56c8627.png
    图4 How LSM Trees Work (Source: Comparative LSM Study, SIGMOD 2018)

    7b4e6e6241793a37fa3292f01cc01b1d.png
    图5 LSM 存储

    可以看到,LSM tree的“写”,并不强调一开始,就要将新数据放置进,全量active data中,合适的“位置”,假设图5中C2为全量active data。新写入的数据,会先存放在内存中,再一次性flush进disk 的C1处,再从C1 merge 到C2。
    下面,我们将详细地介绍RocksDB 内部是如何来处理这个“flush"和"merge"的过程。

    展开全文
  • <div><p>Stuck in rocksdb installing when <code>make install-rocksdb-ull,anyone help? <pre><code> gzip: stdin: unexpected end of file rocksdb-5.13.4/monitoring/instrumented_mutex.cc rocksdb-5.13.4/...
  • m following the instructions on testing rocksdb and got the following error: <h2>Current Behavior <p>04:03:23 # make db_bench -j72 DEBUG_LEVEL=0 SPDK_DIR=/home/yli/spdk Makefile:1007: warning:...
  • <div><p>1.Collecting python-rocksdb Requirement already satisfied: setuptools in /home/guosheng/.local/lib/python2.7/site-packages (from python-rocksdb) Installing collected packages: python-rocksdb ...
  • RocksDB integration

    2020-12-26 06:05:18
    <div><p>PR #151 added <a href="https://github.com/facebook/rocksdb">RocksDB</a> support. <p>The implementation is using this rocksdb Python package: https://github.com/twmht/python-rocksdb ...
  • Upgrade RocksDB

    2020-12-26 18:53:21
    <div><ul><li>Upgrade RocksDB from an ancient version to v4.11.2.</li><li>Switch RocksDB submodule from <a href="https://github.com/ekg/rocksdb">ekg/rocksdb</a> back to upstream ... I think Erik'...
  • Update rocksdb

    2020-11-22 10:08:39
    1) update version of RocksDB 2) use inner property of RocksDB for count keys (see https://github.com/facebook/rocksdb/wiki/RocksDB-FAQ)</p><p>该提问来源于开源项目:infinispan/infinispan</p></div...
  • Rocksdb update

    2021-01-09 11:31:37
    <div><p>This updates the RocksDB that rippled actually uses to the latest version. It also includes updates to the unity build to handle changes to the RocksDB source structure. <p>Also included is ...
  • RocksDB系列一:RocksDB基础和入门

    万次阅读 2018-07-14 19:19:00
    1、简介 RocksDB是FaceBook起初作为实验性质开发的一个高效数据库软件,旨在充分实现快存上存储数据的服务能力。RocksDB是一个c++库,可以用来存储keys和values,且keys和values可以是任意的字节流,支持原子的读和...
  • <div><p>This adds support for RocksDB via RocksJava binding.</p><p>该提问来源于开源项目:brianfrankcooper/YCSB</p></div>
  • <div><ul><li>rocksdb 6.11.4 (compared to 6.7.4) contains some fixes that might have caused corruption issues like #6797.</li><li>updated default rocksdb format_version to 5 ...paritytech/substrate</p></...
  • RocksDB介绍 RocksDB简介 RocksDB是基于C++语言编写的嵌入式KV存储引擎,它不是一个分布式的DB,而是一个高效、高性能、单点的数据库引擎。它是由Facebook基于Google开源的kv存储LevelDB开发开发。RocksDB使用LSM...
  • Upgrade rocksdb

    2020-11-27 15:54:17
    We need to upgrade RocksDB in the segment store to a later version. <p><strong>Problem location Segment store, cache, RocksDB. <p><strong>Suggestions for an improvement Upgrade RocksDB.</p><p>该提问...
  • RocksDB的磁盘数据组织层次1 磁盘文件的组织方式rocksdb在磁盘上的文件是分为多层的,分别叫做level-0, level-1等等level0上包含的文件,是由内存中的memtable dump到磁盘上生成的,单个文件内部按key有序,文件...
  • RocksDB是啥LSM 类存储引擎、数据库之一。所谓LSM,一般的名字叫 Log Structured-Merge Tree(日志结构合并树),来源于分布式数据库领域,也是BigTable 的论文中所使用的文件组织方式。它的特点在于写入的时候是...
  • RocksDB Wiki

    2020-09-21 15:16:28
    留个RocksDB的脚印,也好有时间去踩踩。。。 本文源自:https://www.bookstack.cn/read/rocksdb-en/573fb63dc4216007.md Welcome to RocksDB RocksDB is a storage engine with key/value interface, where keys ...
  • RocksDB系列二:RocksDB Option

    千次阅读 2018-07-16 10:55:45
    RocksDB用户可以通过Options类将配置信息传入引擎,除此之外,还可以以下其他方法设置,分别为:通过option file生成一个option class从option string中获取option 信息从string map中获取option信息option string...
  • RocksDB译文之一 -- RocksDB简介

    千次阅读 2017-01-09 09:30:19
    这篇文章是RocksDB官方文档的译文,原文地址:https://github.com/facebook/rocksdb/wiki/RocksDB-Basics1.简介RocksDB项目起源于Facebook的一个实验项目,该项目旨在开发一个与快速存储器(尤其是闪存)存储数据性能...

空空如也

1 2 3 4 5 ... 20
收藏数 4,504
精华内容 1,801
关键字:

rocksdb