Gremlin JS操作Amazon Neptune批量插入顶点报ConcurrentModificationException
异常原因
Promise.all会并行触发所有分块的写查询,支持事务的图数据库(如JanusGraph、Neo4j等)默认不支持无锁的并发写入操作,多个写请求同时操作索引/顶点分区时就会抛出ConcurrentModificationException- 你在全局复用了
allVertices遍历实例,多个并发查询会同时修改这个遍历的内部状态,也会引发执行异常
优化修复方案
核心逻辑是把并行执行改成串行执行,同时调整遍历实例的生成逻辑,避免状态复用:
- 移除
Promise.all并行逻辑,改为逐个await执行分块查询 - 把
addV遍历的生成放到每个分块的循环内部,每次查询生成独立的遍历实例 - 可根据实际数据库负载调整分块大小,推荐调整到10~50的区间,减少请求次数同时避免单次查询过大
修复后代码如下:
public async createManyByKey(label: string, key: string, properties: object[]): Promise<T[]> { const result: T[] = []; const propKeys: Array<string> = Object.keys(properties[0]); // 可根据实际性能调整分块大小 const chunkedProperties = chunk(properties, 20); for(const chunk of chunkedProperties){ // 每个分块生成独立的addV遍历,避免状态复用 const addVTraversal = __.addV(label); for(const propKey of propKeys){ addVTraversal.property(propKey, __.select(propKey)); } // 串行执行每个分块的查询 const chunkResult = await this.g.inject(chunk) .unfold().as('data') .coalesce( __.V().hasLabel(label).where(eq('data')).by(key).by(__.select(key)), addVTraversal ) .toList(); result.push(...chunkResult); } return result; }
额外优化建议
- 如果数据库支持配置写重试机制,可以给查询添加自动重试逻辑,遇到并发异常时自动重试失败的分块
- 插入前可以先批量查询已存在的顶点,过滤掉不需要插入的记录,减少写操作的数量
内容的提问来源于stack exchange,提问作者codegutsy
相关产品推荐
相关产品推荐

