如何从缓存中有效地存储和读回层次结构

时间:2011-11-15 23:37:36

标签: c# performance caching redis hierarchical-data

我的情况是,我目前正在SQL数据库中存储层次结构,该数据库快速接近15000个节点(5000个边缘)。此层次结构根据树中的用户位置定义我的安全模型,授予对下面项目的访问权限。因此,当用户请求所有安全项目的列表时,我正在使用CTE将其递归到数据库中(并展平所有项目),这会开始显示其年龄(慢)。

层次结构不经常更改,所以我试图将其移动到RAM(redis)中。请记住,我有许多需要这个安全调用的子系统,以及用于为CRUD操作构建树的UI。

首次尝试

我的第一次尝试是将关系存储为键值对 (这是它如何存储在数据库中)

       E
     /   \
    F     G
   / \   /  \
  H  I  J    K

mapped to:
    E - [F, G]
    F - [H, I]
    G - [J, K]

因此,当我想要E及其所有的死者时,我递归地让他的孩子和他们的孩子使用钥匙,它允许我从任何节点开始向下移动。这个解决方案提供了很好的速度提升但是有15,000个节点,在代码中重建我的树大约有5000个缓存命中(更糟糕的情况......从E开始。性能基于起始节点位置,导致超级用户看到最糟糕的表现)。这仍然很快,但似乎很健谈。我喜欢这样一个事实,即我可以通过将其从键列表中弹出而无需重建我的整个缓存来随时删除节点。这也可以快速点亮,以便在UI上直观地构建树。​​

第二次尝试

我的另一个想法是从数据库中获取层次结构,构建树并将其存储在RAM(redis)中,然后将整个内存拉出内存(大小约为2 MB,序列化)。这给了我一个单独的调用(不是很健谈)到redis中拉出整个树,找到用户父节点,然后下降以获取所有子项。这些调用很频繁,在网络层传递2 MB似乎很大。这也意味着我无法轻松添加/删除和项目,而无需拉下树并编辑并将其全部推回。同时根据需要通过HTTP建立树木意味着每个请求必须下拉2MB才能获得直接子项(使用第一个解决方案非常小)。


您认为哪种解决方案是更好的方法(长期持续增长)。两者都更加快速,并从数据库中减轻一些负担。或者他们是一个更好的方法来实现这个我没有想过的?

由于

3 个答案:

答案 0 :(得分:3)

让我提出一个想法...

使用分层版本控制。修改图中的节点时,增加其版本(数据库中的简单int字段),但增加其所有祖先的版本。

  • 首次从数据库获取子树时,将其缓存到RAM。 (您可以通过递归CTE对其进行优化,并在单个数据库往返中进行此操作。)
  • 但是,下次需要检索同一子树时,只检索根。然后将缓存版本与刚刚从数据库中获取的版本进行比较。
    • 如果匹配,那么很好,您可以停止提取并只重复使用缓存。
    • 如果他们不这样做,请抓取孩子并重复此过程,随时刷新缓存。

最终结果是,通常只在一个节点之后,你会很早就剔除提取,你甚至不需要缓存整个图形。修改很昂贵,但这不应该是一个问题,因为它们很少见。

顺便说一句,类似的原则可以在相反的方向工作 - 即当你从叶子开始并需要找到根的路径时。您需要以相反的方向更新版本控制层次结构,但其余的应该以非常类似的方式工作。你甚至可以组合两个方向。

---编辑---

如果您的数据库和ADO.NET驱动程序支持它,则可能需要查看服务器通知,例如MS SQL Server的SqlDependencyOracleDependency

基本上,您指示DBMS监视更改并在发生更改时通知您。这非常适合以有效的方式使客户端缓存保持最新状态。

答案 1 :(得分:1)

如果经常不更改层次结构,则可以为每个节点(而不仅仅是直接子节点)计算以下项目的完整列表。 通过这种方式,您将需要更多的RAM,但它对任何用户都可以快速工作,因为您将能够在单次读取中读取整个后代节点列表。

对于您的示例(我将使用JSON格式):

E - {"direct" : [F, G], "all" : [F, G, H, I, J, K]}
F - {"direct" : [H, I], "all" : [H, I]}
G - {"direct" : [J, K], "all" : [J, K]}

嗯,对于超级用户,你仍然需要为每个请求传输大量数据,但我认为没有办法让它变得更少。

答案 2 :(得分:0)

我们做这样的事情。我们将树读入内存,将其存储在应用程序缓存中,然后从内存中访问它。由于我们几乎从未进行过更改,并且不必立即将更改反映在网络应用中,因此我们甚至无需检测它们,只需让缓存老化并获得更新即可。它对我们来说非常好。