密钥值数据库中的商店列表

时间:2013-08-29 14:15:25

标签: nosql key-value berkeley-db leveldb

我搜索存储与键值数据库中的键相关联的列表的最佳方式(如berkleydbleveldb

例如: 我有用户和用户的订单 我想存储每个用户的订单ID列表,以便使用范围选择(用于分页)快速访问

如何存储此结构?

我不想为每个用户以序列化格式存储它:

user_1_orders = serialize(1,2,3..)
user_2_orders = serialize(1,2,3..)

beacuse list可以很长

我认为每个用户的单独db文件都有商店订单ID作为其中的键,但是这不能解决范围选择问题..如果我想获得范围为[5000:5050]的用户ID怎么办?

我了解redis,但对berkleydbleveldb等关键值实施感兴趣。

4 个答案:

答案 0 :(得分:0)

您可以使用Redis在zset(排序集)中存储列表,如下所示:

// this line is called whenever a user place an order
$redis->zadd($user_1_orders, time(), $order_id);
// list orders of the user
$redis->zrange($user_1_orders, 0, -1);

Redis足够快。但是你应该知道的关于Redis的一件事是它将所有数据存储在内存中,所以如果数据最终超过物理内存,你必须自己对数据进行分片。

此外,您可以使用SSDBhttps://github.com/ideawu/ssdb),它是leveldb的包装,具有与Redis类似的API,但将大多数数据存储在磁盘中,内存仅用于缓存。这意味着SSDB的容量是Redis的100倍 - 高达TBs。

答案 1 :(得分:0)

您可以在支持扫描的键值存储(如leveldb)中对此进行建模的一种方法是将订单ID添加到每个用户的键中。因此,每个订单的新密钥都是userId_orderId。现在要获取特定用户的订单,您可以执行简单的前缀扫描 - 扫描(userId *)。现在这使得userId范围查询变慢,在这种情况下,您可以仅为userIds维护另一个表或使用另一个键约定:Id_userId用于获取[5000-5050]之间的userIds

最近我看到hyperdex在leveldb:ex:http://hyperdex.org/doc/04.datatypes/#lists之上添加数据类型支持,所以你也可以尝试一下。

答案 2 :(得分:0)

在BerkeleyDB中,您可以按排序或未排序的顺序存储每个键的多个值。这将是最自然的解决方案。 LevelDB没有这样的功能。您应该查看LMDBhttp://symas.com/mdb/),但它也支持排序的多值键,并且比其他任何键都更小,更快,更可靠。

答案 3 :(得分:0)

让我们从一个列表开始。您可以使用单个hashmap:

  1. 在行0中存储用户订单的数量
  2. 为每个新订单存储一个计数递增的新行
  3. 所以yoru hashmap如下所示:

    key | value
    -------------
     0  |   5
     1  | tomato
     2  | celery
     3  | apple
     4  | pie
     5  | meat
    

    密钥的稳定增量可确保每个密钥都是唯一的。鉴于db是按键排序的,并且pack函数将整数转换为一组正确排序的字节数组,您可以获取列表的切片。要获取5000到5050之间的订单,您可以使用bsddb Cursor.set_range或leveldb' s createReadStream (js api)

    现在让我们扩展到多个用户订单。如果你可以打开几个hashmap,你可以使用上面的几个hashmap。也许你会遇到一些系统问题(每个目录最多nb个开放fds或最大文件数)。因此,您可以使用单个并为多个用户共享相同的哈希映射。

    我在以下的leveldb和bsddb工作中解释了这一点,因为你pack正确使用了词典顺序(byteorder)。所以我假设你有一个pack函数。在bsddb中,您必须自己构建pack函数。请查看wiredtiger.packingbytekey获取灵感。

    原则是使用用户的id命名密钥。它也被称为关键组成。

    假设您的数据库如下所示:

       key   |  value
    -------------------
      1  | 0 |    2       <--- count column for user 1
      1  | 1 |  tomato
      1  | 2 |  orange 
        ...      ...
      32 | 0 |    1       <--- count column for user 32
      32 | 1 |  banna
        ...  |   ...
    

    使用以下(伪)代码创建此数据库:

    db.put(pack(1, make_uid(1)), 'tomato')
    db.put(pack(1, make_uid(1)), 'orange')
    ...
    db.put(pack(32, make_uid(32)), 'bannana')
    

    make_uid实现如下:

    def make_uid(user_uid):
        # retrieve the current count
        counter_key = pack(user_uid, 0)
        value = db.get(counter_key)
        value += 1  # increment
        # save new count
        db.put(counter_key, value)
        return value
    

    然后你必须进行正确的范围查找,它与单个复合键类似。使用bsddb api cursor.set_range(key)我们检索所有项目 用户5000的{​​{1}}和5050之间:

    42

    未完成错误检查。除其他事项外,如果从列表中删除项目,则不能保证切片def user_orders_slice(user_id, start, end): key, value = cursor.set_range(pack(user_id, start)) while True: user_id, order_id = unpack(key) if order_id > end: break else: # the value is probably packed somehow... yield value key, value = cursor.next() 可以撕掉51个项目。查询说user_orders_slice(42, 5000, 5050)项的正确方法是实现user_orders_query(user_id,start,limit)`。

    我希望你明白这个主意。