【问题标题】:ArangoDB sharding cluster performance issueArangoDB 分片集群性能问题
【发布时间】:2020-12-15 16:28:50
【问题描述】:

我有一个在单实例设置中运行良好的查询。但是,当我尝试在分片集群上运行它时,性能下降了(执行时间延长了 4 倍)。

查询计划显示实际上所有处理都在协调器节点上完成,而不是在 DbServer 上。 如何推送查询在 DbServer 上执行?

提供一些背景信息:我收集了大约 120k(将增长到数百万)带有嵌套数组的多级 JSON 文档。并且查询需要在到达正确的节点之前取消嵌套这些数组。

AQL 查询:

for doc IN doccollection
for arrayLevel1Elem in doc.report.container.children.container
for arrayLevel2Elem in arrayLevel1Elem.children.container.children.num
for arrayLevel3Elem in arrayLevel2Elem.children.code

filter doc.report.container.concept.simpleCodedValue == 'A' 
filter arrayLevel1Elem.concept.codedValue == "B"
filter arrayLevel2Elem.concept.simpleCodedValue == "C"   
filter arrayLevel3Elem.concept.simpleCodedValue == 'X'
filter arrayLevel3Elem.value.simpleCodedValue == 'Y'     

collect studyUid = doc.report.study.uid, personId = doc.report.person.id, metricName = arrayLevel2Elem.concept.meaning, value = to_number(arrayLevel2Elem.value)

return {studyUid, personId, metricName, value}

查询计划:

 Id   NodeType                  Site          Est.   Comment
  1   SingletonNode             DBS              1   * ROOT
  2   EnumerateCollectionNode   DBS         121027     - FOR doc IN doccollection   /* full collection scan, projections: `report`, 2 shard(s) */   FILTER (doc.`report`.`container`.`concept`.`simpleCodedValue` == "A")   /* early pruning */
  3   CalculationNode           DBS         121027       - LET #8 = doc.`report`.`container`.`children`.`container`   /* attribute expression */   /* collections used: doc : doccollection */
 19   CalculationNode           DBS         121027       - LET #24 = doc.`report`.`study`.`uid`   /* attribute expression */   /* collections used: doc : doccollection */
 20   CalculationNode           DBS         121027       - LET #26 = doc.`report`.`person`.`id`   /* attribute expression */   /* collections used: doc : doccollection  */
 29   RemoteNode                COOR        121027       - REMOTE
 30   GatherNode                COOR        121027       - GATHER   /* parallel, unsorted */
  4   EnumerateListNode         COOR      12102700       - FOR arrayLevel1Elem IN #8   /* list iteration */
 11   CalculationNode           COOR      12102700         - LET #16 = (arrayLevel1Elem.`concept`.`codedValue` == "B")   /* simple expression */
 12   FilterNode                COOR      12102700         - FILTER #16
  5   CalculationNode           COOR      12102700         - LET #10 = arrayLevel1Elem.`children`.`container`.`children`.`num`   /* attribute expression */
  6   EnumerateListNode         COOR    1210270000         - FOR arrayLevel2Elem IN #10   /* list iteration */
 13   CalculationNode           COOR    1210270000           - LET #18 = (arrayLevel2Elem.`concept`.`simpleCodedValue` == "C")   /* simple expression */
 14   FilterNode                COOR    1210270000           - FILTER #18
  7   CalculationNode           COOR    1210270000           - LET #12 = arrayLevel2Elem.`children`.`code`   /* attribute expression */
 21   CalculationNode           COOR    1210270000           - LET #28 = arrayLevel2Elem.`concept`.`meaning`   /* attribute expression */
 22   CalculationNode           COOR    1210270000           - LET #30 = TO_NUMBER(arrayLevel2Elem.`value`)   /* simple expression */
  8   EnumerateListNode         COOR  121027000000           - FOR arrayLevel3Elem IN #12   /* list iteration */
 15   CalculationNode           COOR  121027000000             - LET #20 = ((arrayLevel3Elem.`concept`.`simpleCodedValue` == "X") && (arrayLevel3Elem.`value`.`simpleCodedValue` == "Y"))   /* simple expression */
 16   FilterNode                COOR  121027000000             - FILTER #20
 23   CollectNode               COOR   96821600000             - COLLECT studyUid = #24, personId = #26, metricName = #28, value = #30   /* hash */
 26   SortNode                  COOR   96821600000             - SORT studyUid ASC, personId ASC, metricName ASC, value ASC   /* sorting strategy: standard */
 24   CalculationNode           COOR   96821600000             - LET #32 = { "studyUid" : studyUid, "personId" : personId, "metricName" : metricName, "value" : value }   /* simple expression */
 25   ReturnNode                COOR   96821600000             - RETURN #32

非常感谢任何提示。

【问题讨论】:

    标签: arangodb


    【解决方案1】:

    查询实际上并没有在数据库服务器上执行 - 协调器处理查询编译和执行,只是真正向数据库服务器询问数据。

    这意味着查询执行的内存负载发生在协调器上(很好!)但是协调器必须通过网络传输(有时是大量的)数据。这是迁移到集群的最大缺点 - 而不是一个容易解决的问题。

    我一开始也走这条路,并找到了优化我的查询一些的方法,但最后,使用“@987654321”更容易@" 集群或 "active-failover" 设置。

    提出架构建议很棘手,因为每个用例都可能如此不同,但我遵循了一些通用的 AQL 准则:

    1. 不推荐收集FORFILTER 语句(参见#2)。试试这个版本,看看它是否运行得更快(并尝试索引report.container.concept.simpleCodedValue):
    FOR doc IN doccollection
        FILTER doc.report.container.concept.simpleCodedValue == 'A'
        FOR arrayLevel1Elem in doc.report.container.children.container
            FILTER arrayLevel1Elem.concept.codedValue == 'B'
            FOR arrayLevel2Elem in arrayLevel1Elem.children.container.children.num
                FILTER arrayLevel2Elem.concept.simpleCodedValue == 'C'
                FOR arrayLevel3Elem in arrayLevel2Elem.children.code
                    FILTER arrayLevel3Elem.concept.simpleCodedValue == 'X'
                    FILTER arrayLevel3Elem.value.simpleCodedValue == 'Y'
                    COLLECT
                        studyUid = doc.report.study.uid,
                        personId = doc.report.person.id,
                        metricName = arrayLevel2Elem.concept.meaning,
                        value = to_number(arrayLevel2Elem.value)
                    RETURN { studyUid, personId, metricName, value }
    
    1. FOR doc IN doccollection 模式将从数据库服务器中为doccollection 中的每个项目调用整个文档。最佳做法是限制您要检索的文档数量(最好使用索引支持的搜索)和/或只返回几个属性。不要害怕使用LET - 协调器上的内存比数据库上的内存更快。此示例同时进行 - 过滤器 返回一组较小的数据:
    LET filteredDocs = (
        FOR doc IN doccollection
            FILTER doc.report.container.concept.simpleCodedValue == 'A'
            RETURN {
                study_id: doc.report.study.uid,
                person_id: doc.report.person.id,
                arrayLevel1: doc.report.container.children.container
            }
    )
    FOR doc IN filteredDocs
        FOR arrayLevel1Elem in doc.arrayLevel1
            FILTER arrayLevel1Elem.concept.codedValue == 'B'
            ...
    

    【讨论】:

    • 嗨,Kerry,非常感谢您迅速而详尽的推荐。我遵循了您的指导并设法提高了性能: 1. 最大的影响(20%:~100s -> ~80s)来自在第一个过滤字段(report.container.concept.simpleCodedValue)上添加索引。我最初跳过了它,因为它的基数很低,并且在单实例架构的情况下它不会影响性能。 2. 上移过滤器语句并没有改变性能。在上面发布的执行计划中,您可以看到优化器已经应用了 move-filters-up 规则。
    • 续 3. 使用 LET 语句再次有所帮助(~80s -> ~75s)但是,与单实例的 20s 执行时间相比,它看起来并不好。所以,我需要同意你的观点,在我的海量数据提取情况下,分片集群并不是最好的选择。至少只要查询不能在 DbServer 节点上完全处理。事实上,ArangoDB 的“One Shard”功能将查询发送到 DbServer。但随后我们失去了我期望实现的水平可扩展性。无论如何,再次感谢您基于经验的反馈 - 对理解背后的逻辑很有帮助。
    • @kgr - 有时就是这样。您只能对某些查询做这么多,集群并不总是最好的方法。我也希望水平可扩展性,但发现我的查询并不适合。也就是说,有一些方法可以让它更高效地运行。
    • @kgr - 关键是确保您了解底层架构以及瓶颈所在。例如,您遇到的问题似乎在于集群上的大查询 - 大量数据在 DB 和 Coord 节点之间移动。为了最大限度地减少这种情况,请尝试创建一个 Foxx 服务,该服务进行大量较小的查询,在 JavaScript 中执行一些连接或过滤,最大限度地减少来自 DB 的数据量或 DB 节点之间的消息数量。此外,这也是“SmartGraph”旨在帮助解决的问题。 arangodb.com/enterprise-server/smartgraphs
    • SmartGraph 功能您可能是对的。就我而言,我什至有 Disjoint SmartGraphs。而且,根据文档,“这确保了图形遍历、最短路径和 k-shortest-paths 查询可以在 DB-Server 上本地执行”。绝对值得一试。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多