使用 LevelDB 作为后端时更新 Riak 中的对象的性能影响不应受到每个索引中有多少条目的影响。但是,它可能会受到每个分区中存储的总数据量、自上次更新键以来已写入的数据量以及为该单个对象指定了多少不同的索引条目的影响。
LevelDB如何存储数据
当一个值被写入 LevelDB 时,它被添加到.log
顶层的一个文件中。当.log
文件达到一定大小(我认为是 1Mb,不记得该大小是否可配置)时,文件被切断并开始一个新文件。这些文件没有排序。当顶层(0 级)有多个文件时,会触发压缩。压缩将组合一个或多个顶级文件,对它们包含的键进行排序,并将这些排序列表与.sst
级别 1 中的适当文件合并。为每个排序级别创建一个清单文件,指示存储的键范围在每个.sst
文件中。对于较低级别,此过程也会根据需要重复,每个级别能够存储大约 10 倍于前一个级别的数据量。
当为已存在的键写入新值时,只需将其写入顶层,屏蔽任何先前写入较低级别的值。以前的值将被替换,因为正常的压缩将新值向下移动到较低的级别。
这如何影响阅读
当请求密钥时,LevelDB 从级别 0 开始并检查那里的每个文件以查看它是否包含密钥。如果不是,它会向下移动到级别 1 并检查清单指示的文件是否包含密钥。对于连续的较低级别重复此操作,直到找到密钥或已达到最低级别。因此,返回最近写入的键值。随着每个 LevelDB 后端存储的数据总量增加,使用的级别数、必须搜索的文件数以及读取最旧数据的时间也会增加。
LevelDB 后端如何实现索引
在 LevelDB 后端存储值时,后端使用的原始键是 .sext 编码{o,Bucket,Key}
。如果在对象中指定了任何索引条目,则对于每个索引,都会存储一个附加键,即 .xt 编码{i,Bucket,IndexName,IndexValue,Key}
。
为了在更新值时删除任何过时的索引条目,必须在每次 PUT 或 DELETE 之前执行 GET,将前一个对象的索引规范与正在存储的对象的索引规范进行比较,并{i,...}
删除任何过时的键和任何添加了新的。
索引查询
由于 LevelDB 以排序方式存储数据,因此索引查询被实现为从键开始的折叠{i,Bucket,IndexName,FirstValue,<<>>}
({i,Bucket,IndexName,LastValue,<<255,255,255,255>>}
二进制<<255,...
是一个理论值,指示排序顺序中最后一个可能的键)。查询每个排序级别中的清单,因此只需要打开包含被折叠范围的一部分的数据文件。
概括
example_bin 的几乎每个值都是不同的值。对索引的一个特定值的查询只返回一个或几个对象。这样的索引可以是电子邮件地址或注册时间(作为 unix 时间戳)。
查询所需的索引条目和单个值可能完全位于每个排序级别的单个文件中,因此此查询将需要打开并搜索级别 0 中的所有文件,以及每个较低级别中的 1 个文件。存在的级别数量取决于存储的数据量,将是最大的决定因素。
example_int 索引只有几个可能的值。因此,对特定索引值的查询会返回大量对象。这样的索引可以代表一个用户类别,例如“管理员”或“客户”。
查询单个值的更大范围的索引条目使得每个排序级别需要查询多个文件的可能性更大。此查询将比前一个查询花费更长的时间,因为折叠中包含的索引条目数量较多,并且可能需要打开的文件数量较多。
更新这些对象时对性能有何影响?我知道每次更新对象时都需要检查索引。上述任何一个示例都可以构成 Riak 的耗时或耗费资源的任务吗?
这里所需的时间取决于检索任何旧对象所需的时间,以及对索引条目的更改次数。在此过程中,从不将整个索引视为一个整体,仅将前一个对象上的条目和新对象上的条目视为一个整体。因此,性能不会受到任何索引中的条目数量的影响,而是受该对象具有或具有条目的索引数量的影响。