在Redis中,如何高效地查询大表?

3

我有一个包含9列和1200万行的大表格,如下:

col1  col2  col3  col4  col5  col6  col7  col8  col9
12.3  37.4  7771  -675  -23   23.8  78.8  -892  67.5
79.3  -6.3  6061  -555  -24   28.1  77.1  -889  32.6
55.6  -7.3  8888  -921  -56   78.3  22.3  -443  22.9
....  ....  ....  ....  ....  ....  ....  ....  ....

目前,这个表格以TSV(制表符分隔向量)格式保存在我的硬盘上,大小为432MB。我想将表格填充到Redis中,以便最有效地完成这种查询:对于每个列给定一个最小值和最大值,计算在给定范围内的行数,即

(min_col1 <= col1 <= max_col1) &&
(min_col2 <= col2 <= max_col2) &&
(min_col3 <= col3 <= max_col3) &&
(min_col4 <= col4 <= max_col4) &&
(min_col5 <= col5 <= max_col5) &&
(min_col6 <= col6 <= max_col6) &&
(min_col7 <= col7 <= max_col7) &&
(min_col8 <= col8 <= max_col8) &&
(min_col9 <= col9 <= max_col9)

我的问题如下:
1) 如何将表格填充到Redis中?应该使用什么类型的键/值数据结构?哈希、列表、集合、排序集,还是其他什么?
2) 在填充了表格之后,给定9个列的最小和最大值,如何编写查询以获取计数,即在9个范围内落入行的数量?我能想到的一种方法是,首先找出满足每个X(1到9)的(min_colX <= colX <= max_colX)的行,然后计算它们的交集。但是我猜这不是最有效的方法。我只想尽快检索到计数。
顺便说一句,我尝试过MongoDB。使用mongoimport来填充表格很简单,但是我的查询需要10秒钟才能完成,这对于我的实时应用来说太慢并且无法接受。相比之下,Redis将数据保存在内存中,因此我希望Redis可以将查询时间缩短到1秒钟以内。
供您参考,这是我在MongoDB中的操作。
mongoimport -u my_username -p my_password -d my_db -c my_coll --type tsv --file my_table.tsv --headerline
use my_db
db.my_coll.ensureIndex({col1:1, col2:1, col3:1, col4:1, col5:1, col6:1, col7:1, col8:1, col9:1 }).
db.my_coll.count({ col1: {$gte: min_col1, $lte: max_col1), col2: {$gte: min_col2, $lte: max_col2}, col3: {$gte: min_col3, $lte: max_col3}, col4: {$gte: min_col4, $lte: max_col4}, col5: {$gte: min_col5, $lte: max_col5}, col6: {$gte: min_col6, $lte: max_col6}, col7: {$gte: min_col7, $lte: max_col7}, col8: {$gte: min_col8, $lte: max_col8}, col9: {$gte: min_col9, $lte: max_col9} }).

我使用了explain()函数来确保Btree索引被实际使用,而不是被当成表扫描。

我还尝试创建一个ram磁盘,并将我的MongoDB数据库保存到其中,但由于对于我的实时应用程序来说,将查询时间从10秒缩短到9秒还远远不能够接受。

mkdir ~/ram
chmod -R 755 ~/ram
mount -t tmpfs none ~/ram -o size=8192m
mongod --dbpath ~/ram --noprealloc --smallfiles

你是如何运行MongoDB查询的?你创建了任何索引吗?这似乎不是太多的数据 - 在Mongo中,它们都可以适应内存。 - Asya Kamsky
1
使用Redis时,您没有表,没有列,也没有查询。在多个维度上索引是不可能的(不是直接的)。要支持n维中的高效查询,您需要一种特定的数据结构,例如kd-tree- http://en.wikipedia.org/wiki/Kd-tree。 - Didier Spezia
在MongoDB中,我所做的是:1)创建索引:db.coll.ensureIndex({col1:1, col2:1, col3:1, col4:1, col5:1, col6:1, col7:1, col8:1, col9:1}). 2)进行查询:db.coll.count({ col1: {$gte: min_col1, $lte: max_col1), col2: {$gte: min_col2, $lte: max_col2}, col3: {$gte: min_col3, $lte: max_col3}, col4: {$gte: min_col4, $lte: max_col4}, col5: {$gte: min_col5, $lte: max_col5}, col6: {$gte: min_col6, $lte: max_col6}, col7: {$gte: min_col7, $lte: max_col7}, col8: {$gte: min_col8, $lte: max_col8}, col9: {$gte: min_col9, $lte: max_col9} }). 3)使用explain()进行检查。Btree索引被使用。 - Jacky Lee
2个回答

3

在NoSQL领域有一个(有点)新的玩家:Tarantool

Tarantool具有内置的二级索引,并对它们的范围查询提供了一定支持。目前,据我所知,只能进行>=查询。

根据用户指南

box.select_range(space_no, index_no, limit, key, ...)

选择一组元组,从由键指定的偏移量开始。关键字可以是多部分的。最多使用limit元组限制选择。如果未指定键,则从索引中的第一个键开始。

看起来这是一个很好的工具。但是需要进行一些额外的工作(编写代码来上限绑定这些查询)。


3
将每个“col”设为一个排序的集合,然后在每个键上使用“ZRANGEBYSCORE”,并在应用程序中进行交集和计数。我使用phpredis,并且经常在内存中使用“array_intersect”执行此操作。
性能问题出现在“ZADD”中,因为您将使用它来创建排序的集合。
一旦您在Redis中的内存中创建了所有排序的集合,其余部分就非常快速了。
创建排序的集合(Redis示例)
ZADD col1 12.3 line1
ZADD col1 79.3 line2
ZADD col1 55.6 line3

ZADD col2 37.4 line1
ZADD col2 -6.3 line2
ZADD col2 -7.3 line3

PHP,查找范围、交集和计数。
$COL1 = $redis->zrangebyscore('col1', -10, 10);
$COL2 = $redis->zrangebyscore('col2', 2010, 2012);
$count = count(array_intersect($COL1, $COL2));

希望有所帮助。

我认为这不是一个好主意。$COL1和$COL2将是非常庞大的数组,您的复杂度将是O(N)。 - gouchaoer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接