Rocker-Erlang的rocksdb驱动程序

引言


在Internet上,关于sql / nosql方法的选择以及一个或另一个KV存储的优缺点,有很多信息和争论。 您现在所读的内容不是rocksdb手册,也不是使用此存储库及其驱动程序的激动。 我想分享为Erlang优化NIF开发过程的中期结果。 本文介绍了在几个晚上开发的适用于rocksdb的驱动程序。


因此,在一个项目中,任务是可靠地处理大量事件。 每个事件占用50到350个字节,每天每个节点生成超过8000万个事件。 只是要注意,未考虑将消息传递到节点的容错性问题。 同样,处理限制之一是事件组的原子性和一致性变化。


因此,对驱动程序的主要要求是:


  1. 可靠度
  2. 性能表现
  3. 安全性(规范意义上的)
  4. 功能性:
    • 所有基本的kv功能
    • 列族
    • 交易次数
    • 资料压缩
    • 灵活的存储配置支持
  5. 最低代码库

现有解决方案概述


  • erocksdb是leofs开发人员的解决方案。 优点包括在实际项目中的认可。 通过缺点-过时的代码库和缺乏交易性。 该驱动程序基于rocksdb 4.13。
  • rocke具有许多限制,例如缺少配置选项,但最重要的是,所有键和值都必须是字符串。 他仅以实现一个或另一个功能而限制另一个功能的许多驱动程序为例参加了此次回顾。
  • erlang-rocksdb是功能齐全的项目,其开发始于2014年。 就像erocksdb在实际项目中一样。 它具有C / C ++的大型代码库和广泛的功能。 该驱动程序适合一般实践,并在大多数项目中使用。

在使用rocksdb的erlang驱动程序对当前状况进行了快速分析之后,很明显,它们都不完全满足项目要求。 尽管可以使用erlang-rocksdb,但是有几个免费的晚上,并且在Rust和好奇心方面成功地开发和实现了Bloom过滤器之后:是否有可能在短时间内实现当前项目的所有要求并在NIF中实现大多数功能?


摇杆


Rocker是Erlang的NIF,使用了rockdb的Rust包装器。 关键功能是安全性,性能和最少的代码库。 密钥和数据以二进制形式存储,这对存储格式没有任何限制。 目前,该项目适合用于第三方解决方案。
源代码位于项目存储库中


API概述


底座开口


有两种模式可以使用数据库:


  1. 总密钥空间。 在此模式下,所有按键都将放置在一组中。 Rocksdb允许您灵活配置当前任务的存储选项。 根据它们的不同,可以通过两种方式打开数据库:


    • 使用一组标准的选项


      rocker:open_default(<<"/project/priv/db_default_path">>) -> {ok, Db}. 

      该操作的结果将是使用该数据库的指针,并且该数据库将因其他任何打开尝试而被阻止。 清除该指针后,数据库将立即自动解锁。


    • 要么配置任务的选项
       {ok, Db} = rocker:open(<<"/project/priv/db_path">>, #{ create_if_missing => true, set_max_open_files => 1000, set_use_fsync => false, set_bytes_per_sync => 8388608, optimize_for_point_lookup => 1024, set_table_cache_num_shard_bits => 6, set_max_write_buffer_number => 32, set_write_buffer_size => 536870912, set_target_file_size_base => 1073741824, set_min_write_buffer_number_to_merge => 4, set_level_zero_stop_writes_trigger => 2000, set_level_zero_slowdown_writes_trigger => 0, set_max_background_compactions => 4, set_max_background_flushes => 4, set_disable_auto_compactions => true, set_compaction_style => universal }). 

  2. 细分为几个空格。 密钥存储在所谓的列族中,每个列族可以有不同的选项。 让我们考虑一个使用所有列族的标准选项打开数据库的示例
     {ok, Db} = case rocker:list_cf(BookDbPath) of {ok, CfList} -> rocker:open_cf_default(BookDbPath, CfList); _Else -> CfList = [], rocker:open_default(BookDbPath) end. 

碱去除


要正确删除数据库,必须调用rocker:destroy(Path). 在这种情况下,不应使用底座。


失败后的数据库恢复


如果系统发生故障,可以使用“ rocker:repair(Path)方法还原数据库,该过程包括4个步骤:


  1. 文件搜索
  2. 通过玩WAL还原表
  3. 提取元数据
  4. 描述符记录

创建列族


 Cf = <<"testcf1">>, rocker:create_cf_default(Db, Cf) -> ok. 

去除柱族


 Cf = <<"testcf1">>, rocker:drop_cf(Db, Cf) -> ok. 

CRUD操作


关键数据输入

 rocker:put(Db, <<"key">>, <<"value">>) -> ok. 

通过键获取数据

 rocker:get(Db, <<"key">>) -> {ok, <<"value">>} | notfound 

关键数据删除

 rocker:delete(Db, <<"key">>) -> ok. 

CF中的关键数据输入

 rocker:put_cf(Db, <<"testcf">>, <<"key">>, <<"value">>) -> ok. 

CF中的关键数据检索

 rocker:get_cf(Db, <<"testcf">>, <<"key">>) -> {ok, <<"value">>} | notfound 

清除CF键

 rocker:delete_cf(Db, <<"testcf">>, <<"key">>) -> ok 

迭代器


如您所知,rocksdb的基本原理之一是密钥的有序存储。 此功能在实际任务中非常有用。 要使用它,我们需要数据迭代器。 Rocksdb有几种传递数据的模式(可以在测试中找到详细的代码示例):


  • 从表的开始。 摇杆在迭代器{'start'}对此负责
  • 在表格{'end'}{'end'}
  • 从特定的键向前{'from', Key, forward}
  • 从特定键返回{'from', Key, reverse}

值得注意的是,这些模式也适用于传递存储在列族中的数据。


创建一个迭代器

 rocker:iterator(Db, {'start'}) -> {ok, Iter}. 

迭代器检查

 rocker:iterator_valid(Iter) -> {ok, true} | {ok, false}. 

为CF创建迭代器

 rocker:iterator_cf(Db, Cf, {'start'}) -> {ok, Iter}. 

创建一个前缀迭代器

创建数据库时,前缀迭代器需要明确指定前缀的长度。


 {ok, Db} = rocker:open(Path, #{ prefix_length => 3 }). 

使用“ aaa”前缀创建迭代器的示例:


 {ok, Iter} = rocker:prefix_iterator(Db, <<"aaa">>). 

为CF创建前缀迭代器

与前面的前缀迭代器类似,它需要为列族提供显式的prefix_length


 {ok, Iter} = rocker:prefix_iterator_cf(Db, Cf, <<"aaa">>). 

获取下一项

该方法返回下一个键/值,如果迭代器完成,则返回OK。


 rocker:next(Iter) -> {ok, <<"key">>, <<"value">>} | ok 

交易次数


一个相当常见的情况是需要同时记录对关键组的更改。 使用Rocker,您可以在通用集和CF中组合CRUD操作。
此示例说明了如何处理事务:


 {ok, 6} = rocker:tx(Db, [ {put, <<"k1">>, <<"v1">>}, {put, <<"k2">>, <<"v2">>}, {delete, <<"k0">>, <<"v0">>}, {put_cf, Cf, <<"k1">>, <<"v1">>}, {put_cf, Cf, <<"k2">>, <<"v2">>}, {delete_cf, Cf, <<"k0">>, <<"v0">>} ]). 

性能表现


您可以在测试套件中找到性能测试。 在我的机器上,它显示约30k RPS用于写入,而200k RPS用于读取。 在实际情况下,您可以预期写入的15-20k RPS和读取的120k RPS,每个键的平均数据大小约为1 Kb,键的总数超过10亿


结论


Rocker在我们项目中的开发和应用使我们能够减少系统的响应时间,提高可靠性并减少重新启动时间。 以最小的开发和实施成本获得了这些优势。


对于我自己,我得出结论,对于需要优化的Erlang项目,Rust的使用是最佳的。 Erlang设法快速而有效地实现了95%的代码,而Rust重写/添加了5%的抑制性而又不损害整个系统的可靠性。


PS在Erlang中开发用于任意精度算术的NIF有积极的经验,可以在另一篇文章中进行写作。 我想澄清一下Rust上的NIF主题是否对社区很有趣?

Source: https://habr.com/ru/post/zh-CN413353/


All Articles