You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ArangoDB生产环境查询性能远慢于本地的问题排查求助

问题:ArangoDB生产环境查询性能异常排查

环境信息

  • 生产环境:裸金属服务器,Intel Xeon E-2386G 6c/12t @3.50GHz、32GB ECC 3200MHz内存、2x512GB NVME SSD RAID1,Rocky Linux 9 + ArangoDB 3.9.3
  • 本地环境:Fedora 36 + ArangoDB 3.8.7
  • 数据规模:生产环境searches集合41k文档、results_linkers集合350k文档;本地对应1k、27k文档

现象

同一查询本地毫秒级完成,生产环境耗时数秒,且生产环境数据量较小时也存在慢查询。

生产环境查询执行计划

Query String (415 chars, cacheable: false):
 FOR entry in searches
 FILTER entry.session_key == '21150583'
 LET counters = (
     FOR result in results_linkers
     FILTER result.search_key == entry._key AND result.validation_automatic.status == 'completed'
     COLLECT action = result.validation_automatic.action WITH COUNT INTO action_counter
     RETURN { 'action': action, 'count': action_counter }
 )
 LIMIT 20
 RETURN { 'search': entry, 'counters': counters }
 

Execution plan:
 Id   NodeType                  Calls     Items   Runtime [s]   Comment
  1   SingletonNode                 1         1       0.00000   * ROOT
  2   EnumerateCollectionNode       1        20       0.00737     - FOR entry IN searches   /* full collection scan  */   FILTER (entry.`session_key` == "21150583")   /* early pruning */
 14   LimitNode                     1        20       0.00001       - LIMIT 0, 20
 18   SubqueryStartNode             1        40       0.00001       - LET counters = ( /* subquery begin */
  6   EnumerateCollectionNode    7136   7135580       3.33341         - FOR result IN results_linkers   /* full collection scan, projections: `search_key`, `validation_automatic`  */
  7   CalculationNode            7136   7135580       1.36629           - LET #9 = ((result.`search_key` == entry.`_key`) && (result.`validation_automatic`.`status` == "completed"))   /* simple expression */   /* collections used: result : results_linkers, entry : searches */
  8   FilterNode                    2      1854       0.12298           - FILTER #9
  9   CalculationNode               2      1854       0.00024           - LET #11 = result.`validation_automatic`.`action`   /* attribute expression */   /* collections used: result : results_linkers */
 10   CollectNode                   1        56       0.00013           - COLLECT action = #11 AGGREGATE action_counter = LENGTH()   /* hash */
 17   SortNode                      1        56       0.00001           - SORT action ASC   /* sorting strategy: standard */
 11   CalculationNode               1        56       0.00002           - LET #13 = { "action" : action, "count" : action_counter }   /* simple expression */
 19   SubqueryEndNode               1        20       0.00001           - RETURN  #13 ) /* subquery end */
 15   CalculationNode               1        20       0.00001       - LET #15 = { "search" : entry, "counters" : counters }   /* simple expression */   /* collections used: entry : searches */
 16   ReturnNode                    1        20       0.00000       - RETURN #15

Indexes used:
 none

Optimization rules applied:
 Id   RuleName
  1   move-calculations-up
  2   move-filters-up
  3   move-calculations-up-2
  4   move-filters-up-2
  5   move-calculations-down
  6   reduce-extraction-to-projection
  7   move-filters-into-enumerate
  8   splice-subqueries

Query Statistics:
 Writes Exec   Writes Ign   Scan Full   Scan Index   Filtered   Peak Mem [b]   Exec Time [s]
           0            0     7176820            0    7174966         655360         4.83091

Query Profile:
 Query Stage           Duration [s]
 initializing               0.00000
 parsing                    0.00004
 optimizing ast             0.00001
 loading collections        0.00000
 instantiating plan         0.00003
 optimizing plan            0.00031
 executing                  4.83051
 finalizing                 0.00002

本地环境查询执行计划

Query String (414 chars, cacheable: false):
 FOR entry in searches
 FILTER entry.session_key == '3307542'
 LET counters = (
     FOR result in results_linkers
     FILTER result.search_key == entry._key AND result.validation_automatic.status == 'completed'
     COLLECT action = result.validation_automatic.action WITH COUNT INTO action_counter
     RETURN { 'action': action, 'count': action_counter }
 )
 LIMIT 20
 RETURN { 'search': entry, 'counters': counters }
 

Execution plan:
 Id   NodeType                  Calls   Items   Runtime [s]   Comment
  1   SingletonNode                 1       1       0.00001   * ROOT
  2   EnumerateCollectionNode       1       1       0.00097     - FOR entry IN searches   /* full collection scan  */   FILTER (entry.`session_key` == "3307542")   /* early pruning */
 14   LimitNode                     1       1       0.00001       - LIMIT 0, 20
 18   SubqueryStartNode             1       2       0.00001       - LET counters = ( /* subquery begin */
  6   EnumerateCollectionNode      28   27775       0.02148         - FOR result IN results_linkers   /* full collection scan, projections: `search_key`, `validation_automatic`  */
  7   CalculationNode              28   27775       0.00866           - LET #9 = ((result.`search_key` == entry.`_key`) && (result.`validation_automatic`.`status` == "completed"))   /* simple expression */   /* collections used: result : results_linkers, entry : searches */
  8   FilterNode                    1      97       0.00169           - FILTER #9
  9   CalculationNode               1      97       0.00002           - LET #11 = result.`validation_automatic`.`action`   /* attribute expression */   /* collections used: result : results_linkers */
 10   CollectNode                   1       3       0.00002           - COLLECT action = #11 AGGREGATE action_counter = LENGTH()   /* hash */
 17   SortNode                      1       3       0.00001           - SORT action ASC   /* sorting strategy: standard */
 11   CalculationNode               1       3       0.00001           - LET #13 = { "action" : action, "count" : action_counter }   /* simple expression */
 19   SubqueryEndNode               1       1       0.00001           - RETURN  #13 ) /* subquery end */
 15   CalculationNode               1       1       0.00001       - LET #15 = { "search" : entry, "counters" : counters }   /* simple expression */   /* collections used: entry : searches */
 16   ReturnNode                    1       1       0.00001       - RETURN #15

Indexes used:
 none

Optimization rules applied:
 Id   RuleName
  1   move-calculations-up
  2   move-filters-up
  3   move-calculations-up-2
  4   move-filters-up-2
  5   move-calculations-down
  6   reduce-extraction-to-projection
  7   move-filters-into-enumerate
  8   splice-subqueries

Query Statistics:
 Writes Exec   Writes Ign   Scan Full   Scan Index   Filtered   Peak Mem [b]   Exec Time [s]
           0            0       28901            0      28804         360448         0.03434

Query Profile:
 Query Stage           Duration [s]
 initializing               0.00002
 parsing                    0.00012
 optimizing ast             0.00002
 loading collections        0.00001
 instantiating plan         0.00007
 optimizing plan            0.00117
 executing                  0.03293
 finalizing                 0.00003

额外疑问

采用类似传统关系型数据库的使用方式:单一大集合,通过key查询数据是否合理?数据持续增长时性能是否会持续下降?


解决方案

1. 索引优化(核心)

从执行计划看,两个集合均在执行全表扫描,这是性能瓶颈的核心原因:

  • 给searches集合的session_key字段创建哈希索引:
    CREATE INDEX idx_searches_session_key ON searches(session_key)
    
    作用:直接定位符合session_key的文档,避免全表扫描,减少后续子查询的执行次数。
  • 给results_linkers集合创建复合哈希索引:
    CREATE INDEX idx_results_linkers_search_status ON results_linkers(search_key, `validation_automatic.status`)
    
    作用:同时过滤search_key和validation_automatic.status条件,直接获取符合要求的文档,无需全表扫描后再过滤。

2. 查询改写优化

原查询中,子查询会对每个符合条件的entry执行一次全表扫描,可通过调整逻辑减少重复扫描:

FOR entry IN searches
FILTER entry.session_key == '21150583'
LIMIT 20
LET counters = (
    FOR result IN results_linkers
    FILTER result.search_key == entry._key AND result.validation_automatic.status == 'completed'
    COLLECT action = result.validation_automatic.action WITH COUNT INTO action_counter
    RETURN { 'action': action, 'count': action_counter }
)
RETURN { 'search': entry, 'counters': counters }

注:将LIMIT 20提前到子查询之前,减少需要执行子查询的entry数量(原计划中先扫描全表再过滤session_key,加索引后LIMIT提前能进一步降低子查询执行次数)。

3. 集合使用合理性说明

  • 单一大集合通过key查询的方式在ArangoDB中是合理的,但需注意:
    • 若集合数据量持续增长(如千万级以上),必须确保查询依赖的字段都有合适的索引,否则全表扫描性能会急剧下降。
    • 若数据结构差异较大,拆分多个集合更利于维护和索引优化;若数据结构统一,单集合方案可行。
  • 长期性能保障:
    • 定期清理过期数据,避免集合无限膨胀。
    • 监控索引使用率,及时删除无效索引。
    • 针对高频查询优化执行计划,必要时使用视图(View)预聚合数据。

内容的提问来源于stack exchange,提问作者Kei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:25:40