【问题标题】:How can I use a gremlin query to filter based on a users permissions?如何使用 gremlin 查询根据用户权限进行过滤?
【发布时间】:2017-10-04 00:37:16
【问题描述】:

我对图形数据库相当陌生,但是我已经广泛使用 SQL Server 和文档数据库(Lucene、DocumentDb 等)。我完全有可能以错误的方式处理此查询,因为我是图形数据库的新手。我正在尝试将一些逻辑转换为我们目前正在使用 SQL Server 的图形数据库(具体来说是通过 Gremlins 的 CosmosDB Graph)。更改的原因是这个问题集并不是 SQL Server 真正擅长的,因此我们的 SQL 查询(我们已经尽我们所能优化)真正开始成为我们应用程序的热点。

为了非常简要地概述我们的逻辑,我们运营了一个网上商店,允许管理员配置产品和用户具有多个级别的细粒度权限(如下所述)。根据这些权限,我们只向用户显示他们被允许查看的产品。

实体:

  • 地区:一个地区由多个国家/地区组成
  • 国家:一个国家有很多市场和很多地区
  • 市场:市场是一个国家/地区的一组商店
  • 商店:商店属于单一市场

用户拥有以下一组权限,每组可以包含多个值:

  • 可以查看区域
  • 可以查看国家
  • 可以查看市场
  • 可以查看商店

产品具有以下一组权限,每组可以包含多个值:

  • 对区域可见
  • 对国家可见
  • 市场可见
  • 对商店可见

尝试了几天后,这是我想出的查询。此查询确实有效并为给定用户返回了正确的产品,但是执行大约需要 25 秒。

g.V().has('user','username', 'john.doe').union(
    __.out('can-view-region').out('contains-country').in('in-market').hasLabel('store'),
    __.out('can-view-country').in('in-market').hasLabel('store'),
    __.out('can-view-market').in('in-market').hasLabel('store'),
    __.out('can-view-store')
).dedup().union(
    __.out('in-market').in('contains-country').in('visible-to-region').hasLabel('product'),
    __.out('in-market').in('visible-to-country').hasLabel('product'),
    __.out('in-market').in('visible-to-market').hasLabel('product'),
    __.in('visible-to-store').hasLabel('product')
).dedup()

有没有更好的方法来做到这一点?这个问题可能不是最适合图形数据库吗?

任何帮助将不胜感激!

谢谢, 克里斯

【问题讨论】:

  • 遍历的哪一部分占用的时间最多?更具体地说,第一个 dedup() 之前的遍历部分占用了 25 秒的多少?
  • @stephenmallette 在第一次重复数据删除之前大约需要 30 毫秒,因此速度非常快。该重复数据删除的输出约为 600 个顶点。其余时间都在第二工会。第二个 union+dedup 的输出大约是 5000 个顶点。
  • 1.这是针对分区图集合运行的吗?如果是,您将哪个属性用作分区键。 2. 您是在使用 Microsoft.Azure.Graphs .net SDK(如果是,是哪个版本)还是通过 gremlin 客户端使用 CosmosDB Graph 服务? 3. 你可以尝试删除联合并将其分成两个遍历并测试它们各自的持续时间吗?这将判断union 步骤是否存在问题。
  • @OliverTowers 1) 不,它没有分区。 2) 是的,我正在使用 Microsoft.Azure.Graphs SDK 的 v0.3.0-preview。 3)我针对单个商店运行了第二个联合,执行大约需要 350 毫秒。似乎它没有并行运行第二个联合(不确定是否应该这样做)。它运行第一个联合(30 毫秒),获取第一个联合的 600 个结果,并为每个结果运行第二个联合(350 毫秒)。所以 (30ms + (600 个结果 * 350ms)) = 21 秒。不过,我想不出更好的方法来编写此查询。

标签: gremlin azure-cosmosdb tinkerpop tinkerpop3


【解决方案1】:

我认为这不会有太大帮助,但这是您查询的改进版本:

g.V().has('user','username', 'john.doe').union(
    __.out('can-view-region').out('contains-country').in('in-market').hasLabel('store'),
    __.out('can-view-country','can-view-market').in('in-market').hasLabel('store'),
    __.out('can-view-store')
).dedup().union(
    __.out('in-market').union(
      __.in('contains-country').in('visible-to-region'),
      __.in('visible-to-country','visible-to-market')).hasLabel('product'),
    __.in('visible-to-store').hasLabel('product')
).dedup()

我想知道hasLabel() 检查是否真的有必要。例如,如果.in('in-market') 只能引导一个store 顶点,则删除额外的检查。

此外,创建捷径边缘可能是值得的。每当您更改权限时,这都会增加写入时间,但会显着增加给定查询的读取时间。由于读取可能比权限更新更频繁地发生,这可能是一个很好的权衡。

【讨论】:

  • 删除 hasLabel 的查询速度提高了大约 2 秒。我认为将多个参数传递给 out 或 in 会导致 or 逻辑,而不是链接。整洁的。谢谢你的提示。至于捷径边缘,我曾考虑过这样做,但我不知道保持最新状态的好方法。实际上,这就是我们现在所做的。现在我们有一个 lucene 索引,我们在其中爆炸关系以使查询超快(效果很好),但索引速度非常慢(如果产品具有区域关联,有时长达 5 分钟)。它也无法再扩展了。
  • 一些捷径会很简单(就像市场总是在一个国家,所以我们可以添加一个国家边缘)但即使这样,我担心第二个联盟总是会很慢当前设置。即使我可以让第二个查询花费 10 毫秒(这将是一个巨大的加速),600 个顶点 * 10 毫秒仍然是 6 秒,这对于网站来说是不可接受的。不知道有没有不能并行运行的原因?
  • 并行是供应商需要自己实现的东西,它不是由 TinkerPop 提供的。我不知道 Janus 的计划,但并行查询执行在 DSE Graph 的待办事项列表中。无论如何,我能想到的唯一其他解决方案是一个完全不同的模型,它使用多值属性来存储每个产品顶点的权限。
  • 哦,忘了你用的是CosmosDB,还以为是Janus。但同样的事情,我对他们的计划一无所知。
【解决方案2】:

CosmosDB Graph 团队正在研究可以在 union 步骤上进行的改进。

尚未建议的其他选项:

  1. 使用附加谓词减少每跳遍历的边数。例如: g.V('1').outE('market').has('prop', 'value').inV()
  2. 是否可以在您的客户端代码中拆分遍历并执行并行请求?由于您使用的是 .NET,因此您可以在第一个联合中获取每个结果,并为第二个联合中的遍历执行并行请求。像这样的东西(未经测试的代码):

    string firstUnion = @"g.V().has('user','username', 'john.doe').union(
        __.out('can-view-region').out('contains-country').in('in-market').hasLabel('store'),
        __.out('can-view-country').in('in-market').hasLabel('store'),
        __.out('can-view-market').in('in-market').hasLabel('store'),
        __.out('can-view-store')
    ).dedup()"
    
    string[] secondUnionTraversals = new[] {
        "g.V({0}).out('in-market').in('contains-country').in('visible-to-region').hasLabel('product')",
        "g.V({0}).out('in-market').in('visible-to-country').hasLabel('product')",
        "g.V({0}).out('in-market').in('visible-to-market').hasLabel('product')",
        "g.V({0}).in('visible-to-store').hasLabel('product')",
    };
    
    var response = client.CreateGremlinQuery(col, firstUnion);
    while (response.HasMoreResults)
    {
        var results = await response.ExecuteNextAsync<Vertex>();
        foreach (Vertex v in results)
        {
            Parallel.ForEach(secondUnionTraversals, (traversal) =>
            {
                var secondResponse = client.CreateGremlinQuery<Vertex>(col, string.Format(traversal, v.Id));
                while (secondResponse.HasMoreResults)
                {
                    concurrentColl.Add(secondResponse);
                }
            });
        }
    }
    

【讨论】:

  • 我可以试一试。由于线程耗尽,我有点担心在 Web 服务器上执行此操作。有没有办法告诉查询我只想要前 20 个结果?我尝试过使用.range,但它并不像 linq,其中表达式知道在 x 记录后产生返回。无论技术如何,我都对如何提高此查询的性能感到不知所措。我会试一试,让你知道我的结果。
猜你喜欢
  • 2018-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-22
  • 1970-01-01
  • 2012-08-08
  • 2013-09-29
  • 2017-09-24
相关资源
最近更新 更多