【问题标题】:Data Structure Behind Amazon S3s Keys (Filtering Data Structure)Amazon S3s 键背后的数据结构(过滤数据结构)
【发布时间】:2011-01-26 17:18:34
【问题描述】:

我想实现一个类似于 Amazon S3 查找功能的数据结构。就上下文而言,Amazon S3 将所有文件存储在一个平面命名空间中,但允许您通过名称中的公共前缀来查找文件组,因此复制了目录树的强大功能而没有它的复杂性。

问题是,查找和过滤操作都是 O(1)(或者足够接近,即使在非常大的存储桶上 - S3 的磁盘等价物 - 这两个操作也可能是 O(1))。

简而言之,我正在寻找一种功能类似于哈希映射的数据结构,并具有高效(至少不是 O(n))过滤的额外好处。我能想到的最好的方法是扩展 HashMap 以便它还包含一个(排序的)内容列表,并对匹配前缀的范围进行二进制搜索,然后返回该集合。这对我来说似乎很慢,但我想不出任何其他方法。

有谁知道亚马逊是如何做到的,或者有更好的方法来实现这种数据结构?

【问题讨论】:

  • “查找和过滤操作都是 O(1)”。有什么参考资料吗?
  • 不,这就是为什么我注意到它们足够接近。如果它们是 O(log n),它们是相当快的 O(log n),S3 的列表命令的响应性感觉就像是恒定的时间。
  • 二分查找(或 B 树索引查找)会相当快。
  • 其实我怀疑很多桶是否能达到相当多的条目。即使有一百万个条目,也绝对没有什么能阻止您使用常规 TreeMap。

标签: java data-structures amazon-s3 filtering hashmap


【解决方案1】:

只是为了验证我的说法,即常规 TreeMap 应该足以满足任何包含多达 1,000,000 个条目的存储桶,这里有一个非常简单的测试用例,它给出了一些数字(注意:这并不是作为一个微基准,它只是为了获得一个感觉这个问题的严重性)。

我使用随机生成的 UUID 来模拟键(如果你用斜杠替换破折号,你甚至会得到一种目录结构)。之后,我把它们放到了一个普通的java.util.TreeMap中,最后用map.subMap(fromKey, toKey)查询它们。

public static void main(String[] args) {

    TreeMap<String, Object> map = new TreeMap<String, Object>();

    int count = 1000000;
    ArrayList<String> uuids;

    {
        System.out.print("generating ... ");
        long start = System.currentTimeMillis();
        uuids = new ArrayList<String>(count);
        for (int i = 0; i < count; i++) {
            uuids.add(UUID.randomUUID().toString());
        }
        System.out.println((System.currentTimeMillis() - start) + "ms");
    }

    {
        System.out.print("inserting .... ");
        long start = System.currentTimeMillis();

        Object o = new Object();
        for (int i = 0; i < count; i++) {
            map.put(uuids.get(i), o);
        }

        System.out.println((System.currentTimeMillis() - start) + "ms");
    }

    {
        System.out.print("querying ..... ");

        String from = "be400000-0000-0000-0000-000000000000";
        String to =   "be4fffff-ffff-ffff-ffff-ffffffffffff";

        long start = System.currentTimeMillis();

        long matches = 0;

        for (int i = 0; i < count; i++) {
            Map<String, Object> result = map.subMap(from, to);
            matches += result.size();
        }

        System.out.println((System.currentTimeMillis() - start) + "ms (" + matches/count
                + " matches)");

    }
}

这是我机器的一些示例输出(1,000,000 个键,1,000,000 个范围查询):

generating ... 6562ms
inserting .... 2933ms
querying ..... 5344ms (229 matches)

插入 1 个键平均需要 0.003 毫秒(当然,最后肯定会更多),而查询具有 229 个匹配项的子范围每次查询需要 0.005 毫秒。这是一些相当理智的表现,不是吗?

将数量增加到10,000,000个key和query后,数量如下:

generating ...  59562ms
inserting ....  47099ms
querying ..... 444119ms (2430 matches)

插入 1 个键平均需要 0.005 毫秒,而查询具有 2430 个匹配项的子范围每次查询需要 0.044 毫秒。即使查询速度慢了 10 倍(最后,它会遍历所有始终为 O(n) 的匹配项),但性能仍然不算太差。

由于 S3 是一项云服务,我认为无论如何它都会受到网络的限制。因此,并不迫切需要极其花哨的数据结构来获得所需的性能。尽管如此,我的测试用例还是缺少一些特性,最显着的是并发性和持久性。尽管如此,我认为我已经证明,对于这个用例来说,一个常规的树结构就足够了。如果您想做一些花哨的事情,请尝试使用子树读写锁定,也许可以替代 .subMap(fromKey, toKey);

【讨论】:

  • 非常感谢。我想我只是丢弃了红黑树而没有真正考虑它们,但你是对的,它们对于大多数用途来说都足够快。
【解决方案2】:

只是附加到 sfussinigger 的答案;并发使用 ConcurrentSkipListMap 非常容易,它具有类似于 TreeMap 的属性。它不是太“花哨”的数据结构(无论如何,它已经为您实现了)。这肯定比子树读写锁定要容易。

【讨论】:

  • +1 我什至会说跳过列表是一种非常简单的数据结构。谢谢你提醒我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 2018-06-30
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
  • 1970-01-01
相关资源
最近更新 更多