关于ChronicleMap中的Multimaps,ChronicleMap's GitHub肯定有免责声明:
Chronicle Map不是......
......没有二级索引。
多图。使用
ChronicleMap<K, Collection<V>>
作为多图在技术上是可行的,但通常会导致问题......
不幸的是,这是我的一个使用案例,并且使用堆外存储(使用ChronicleMap)肯定是最简单的方法。
让我试着用披萨来解释我的问题。我有10万种不同的比萨饼。每个披萨都有一个ID和许多不同的浇头和面包皮。我有三种访问模式:
我可以使用ChronicleMap<UUID,Pizza>
轻松存储比萨饼。但这只是一种访问模式。我不想遍历每一个披萨,找到具有匹配的顶部或外壳的披萨。所以,我想存储ChronicleMap<Topping,Collection<UUID>>
和ChronicleMap<Crust,Collection<UUID>>
之类的内容。
然后,如果有人问我所有的意大利辣香肠比萨饼,我会在顶部的ChronicleMap中查找匹配的比萨饼的UUID,然后在主披萨地图中。
但上面引用的文件让我感到害怕。有谁知道这些事情经常导致的“问题”是什么?我为什么不这样做,即使它似乎对我有用?它是否与ChronicleMap如何存储序列化对象有关,特别是集合?
针对潜在问题的一些补充说明:
答案 0 :(得分:18)
如果实际数据确实类似于比萨饼,浇头和面包皮,我。即只有少数不同的浇头/外壳,而且成千上万的比萨饼都含有它们,我会说拥有一个合适的多图谱对于这种情况来说是过度的,你最好有pepperoni_pizzas.dat
,onions_pizzas.dat
, ...具有UUID的不同可附加共享列表,您可以使用Chronicle Queue进行访问,并方便地从多个进程更新它们。
如果有成千上万的浇头/面包10s-100s,平均只有10s-100s的比萨饼具有特定的浇头,你应该使用multimap。
基本上,Chronicle-Maps-as-multimaps存在3种“问题”:
如果使用List<UUID>
或Set<UUID>
类型的值创建Chronicle Map而不指定自定义值序列化程序,它将起作用,但它将完全没有效率,因为它将默认为内置Java序列化,用于在每个请求上序列化和反序列化整个值集合,而不重用任何集合堆对象,也不重用元素的单个UUID
堆对象。因此,每次对ChronicleMap的请求都会产生大量垃圾。
<强>解决方案强>
但是,如果您将值序列化程序指定为ListMarshaller
或SetMarshaller
(或您的自定义集合编组程序,您可以根据ListMarshaller
和SetMarshaller
实现编写)以及可重用的UUID堆对象,它将解决这个垃圾问题:
ListMarshaller<ReusableUuid> valueMarshaller = ListMarshaller.of(
ReusableUuidReader.INSTANCE, ReusableUuidWriter.INSTANCE);
List<ReusableUuid> averageValue = Stream
.generate(() -> ReusableUuid.random())
.limit(averagePizzasForTopping)
.collect(Collectors.toList());
ChronicleMap<Topping, List<ReusableUuid>> map = ChronicleMap
.of(Topping.class, (Class<List<ReusableUuid>>) (Class) List.class)
.averageKey(pepperoni)
.valueMarshaller(valueMarshaller)
.averageValue(averageValue)
.entries(numberOfToppings)
.createPersistedTo(new File("toppings_to_pizza_ids.dat"));
当您将另一个披萨UUID附加到100个UUID的列表中,并将新值插回到Chronicle Map时,Chronicle Map将重新写入整个列表,而不是将一个UUID附加到堆外记忆的末尾块。如果您使用复制,它会将100个UUID的完整列表作为更新值发送到其他节点,而不是仅发送一个添加的UUID。
两者(价值更新和复制)都可以通过可怕的黑客进行优化,但它需要非常深入的Chronicle Map实施知识,并且非常脆弱。
如果您计划在数据存储生命周期中添加新的比萨饼,最初为entires分配的内存区域将变得太小而无法容纳具有更多UUID的新值,因此将重新分配内存区域(对于每个UUID列表可能会多次) 。 Chronicle Map的数据结构设计意味着简化的内存分配方案,如果条目被多次重新分配,则会因碎片而受到严重影响。
如果列表中有很多UUID,并且您在Linux上运行应用程序,则可以通过为每个条目预先分配大量内存(任何列表实际需要的内容)来缓解此问题(通过在.actualChunkSize()
配置中指定ChronicleMapBuilder
并依赖Linux的延迟映射内存分配功能(根据需要逐页)。因此,对于每个UUID列表,您将丢失最多4KB的内存,如果列表的大小为KB,则可能没问题。
另一方面,如果您的列表太长(并且它们是UUID列表,即小结构),并且您总共只有10万个比萨饼,那么您首先不需要多重映射,请参阅开头这个答案。
内存过量使用并依赖于Linux中的延迟映射内存分配的技巧也适用于值的短列表(集合),但仅限于元素本身很大,因此平均总值大小为多KB。
当您可以以任何其他方式避免进入内存重新分配时,碎片也不是问题,i。即新的比萨UUID会及时添加,但也会被删除,因此顶级到美元的列表大小会在一些平均值附近浮动,重新分配很少会被删除。
如果在将条目插入Chronicle Map之后永远不会更新(或永远不会改变大小),则内存碎片永远不会成为问题。
在某些用例中,如果配置正确,Chronicle Map可以充当多图。在其他情况下,Chronicle Map as multimap本质上是低效的。
重要因素:
List<Value>
条目