Java 在大内存B+;指数

Java 在大内存B+;指数,java,multithreading,concurrency,b-tree,concurrentmodification,Java,Multithreading,Concurrency,B Tree,Concurrentmodification,我目前正在设计一个大内存索引结构(几GB)。索引实际上是一个RTree,其中叶子是Btree(不问)。它支持特殊查询并将其推到逻辑极限 由于这些节点是单独的搜索节点,我问自己如何最好地使其并行 到目前为止,我知道六种解决方案: 块在计划写入时读取。树被完全阻塞,直到最后一次读取完成,然后执行写入,写入后,树可以再次用于多次读取。(读取不需要锁定) 克隆节点以更改和重用现有节点(包括leaf),并通过再次停止读取切换和完成来在两者之间切换。由于必须更改叶指针,因此叶指针可能会成为它们自己的集合,从

我目前正在设计一个大内存索引结构(几GB)。索引实际上是一个RTree,其中叶子是Btree(不问)。它支持特殊查询并将其推到逻辑极限

由于这些节点是单独的搜索节点,我问自己如何最好地使其并行

到目前为止,我知道六种解决方案:

  • 块在计划写入时读取。树被完全阻塞,直到最后一次读取完成,然后执行写入,写入后,树可以再次用于多次读取。(读取不需要锁定)

  • 克隆节点以更改和重用现有节点(包括leaf),并通过再次停止读取切换和完成来在两者之间切换。由于必须更改叶指针,因此叶指针可能会成为它们自己的集合,从而可以切换对atomar的修改,并且可以将更改重做到第二个版本,以避免在每次插入时复制指针

  • 使用索引的独立副本,如双缓冲。更新索引的一个副本,然后切换它。一旦没有人读取旧索引,就用同样的方法修改这个索引。这样就可以在不阻塞现有读取的情况下完成更改。如果另一个插入在合理的时间内到达树,这些更改也可以完成

  • 使用串行无共享架构,以便每个搜索线程都有自己的副本。由于线程只能在执行一次读取后更改其树,因此这也是无锁且简单的。由于每个工作线程(绑定到某个核心)的读取是均匀分布的,因此吞吐量不会受到影响

  • 对每个即将写入的节点使用写/读锁,并且在写入过程中只阻塞子树。这将涉及对树的额外操作,因为拆分和合并将向上传播,因此需要重新访问insert(因为向上扩展锁(parentwise)将引入死锁的机会)。由于如果页面大小较大,拆分和合并就不会那么频繁,因此这也是一种好方法。实际上,目前我的BTree实现使用了一种类似的机制,即拆分节点并重新插入值,除非不需要拆分(这不是最优的,但更简单)

  • 对每个节点使用双缓冲区,就像对每个页面在两个版本之间切换的数据库使用阴影缓存一样。因此,每次修改节点时,都会修改一个副本,一旦发出读取,就会使用旧版本或新版本。每个节点都会获得一个版本号,并选择更接近活动版本(最新更改)的版本。要切换到版本,只需对根信息进行atomar更改。这样,树就可以被改变和使用。每次都可以执行此swith,但必须确保在重写新版本时没有读取使用旧版本。这种方法可以不干扰缓存位置,以便链接leaf等。但它也需要两倍的内存量,因为必须存在一个后缓冲区,但可以节省分配时间,并且可能适合高频率的更改

  • 有了这些想法,什么才是最好的?我知道这要看情况而定,但在野外做什么?如果有10个读线程(甚至更多)并且被一个写操作阻塞,我想这不是我真正想要的

    另外,三级、二级和一级缓存以及具有多个CPU的场景如何?有什么问题吗?双缓冲的美妙之处在于,那些命中旧版本的读取仍在使用正确的缓存版本

    创建节点的新副本的版本并不吸引人。那么,在今天的数据库环境中,我们将遇到什么呢

    [更新]

    通过重读这篇文章,我想知道对于拆分和合并使用写锁是否更适合创建替换节点,因为对于拆分和合并,我需要复制大约一半的元素,这些操作非常罕见,因此实际上完全复制节点可以通过在父节点中替换此节点来实现,这是一个简单而快速的操作。这样,读取的实际块将非常有限,因为我们无论如何都要创建副本,所以只有在替换新节点时才会发生阻塞。因为在这些访问过程中,叶子可能不会被改变,所以它并不重要,因为信息密度没有改变。但是,对于节点的每次访问,这同样需要读锁的增量和减量,并检查预期的写锁。这一切都是开销,这一切都阻止了进一步的读取

    [更新2]

    解决方案7。(目前受欢迎)

    目前,我们倾向于为内部(非叶)节点使用双缓冲区,并使用类似于行锁定的方法

    我们试图使用这些索引结构(这是索引所做的一切)分解的逻辑表导致在这些信息上使用集合代数。我注意到这个集合代数是线性的(O(m+n)表示交集和并集),并给我们机会锁定作为此类操作一部分的每个条目


    通过对内部节点进行双缓冲(这不难实现,成本也不高)(noone回答说大约),我想总结一下我们(I)终于有了。结构现在被分离了。我们有一个RTree,它的叶子实际上是表。这些表甚至可以是远程的,所以我们有一种分发方式,由于RMI和代理,这种分发方式基本上是透明的

    剩下的很简单。RTree可以建议一个表拆分,而这个拆分又是一个表。这个拆分是在一个单独的主机上完成的,如果它必须是远程的,则会转移到另一个主机上。Merge几乎是类似的

    对于绑定到不同CPU的线程,此远程也适用,以避免缓存问题

    关于内存中的修改,正如我已经建议的,我们复制内部节点和