方法 | Elasticsearch Jest 批量操作bug 根因定位排查

本文记录了使用Jest进行批量数据导入Elasticsearch过程中遇到的异常问题及解决方法,包括错误提示分析、调试步骤、源码查找尝试及最终定位到的字段问题。

1、背景

使用Jest进行批量插入数据的时候,偶尔会出现如下的bug

One or more of the items in the Bulk request failed, check
BulkResult.getItems() for more information.

起初认为是偶发,就把并发数调小,就再没有关注。

2、出了事了,才找到根因。

当今天调试的时候,把批量数调到最小1的时候,ES中仍然没有导入数据。

这才意识到,不对,是不是单条数据就有问题?

后改到单条数据(不走批量),直接插入,报错如下:

单条导入报错如下:

[INFO]-[com.es.process.ESProcess] err
{“root_cause”:[{“type”:”mapper_parsing_exception”,”reason”:”Field
[_id] is a metadata field and cannot be added inside a document. Use
the index API request
parameters.”}],”type”:”mapper_parsing_exception”,”reason”:”Field [_id]
is a metadata field and cannot be added inside a document. Use the
index API request parameters.”}

比较直观,字段的问题。

逐个字段修复后,问题不在。

批量值改成较大值100后,也能较快导入。

3、走的弯路

3.1 源码附近找答案,无果。

Jest Bulk Github 源码位置:

if (isHttpSuccessful(statusCode)) {
    if(jsonMap.has("errors") && jsonMap.get("errors").getAsBoolean())
    {
        result.setSucceeded(false);
        result.setErrorMessage("One or more of the items in the Bulk request failed, check BulkResult.getItems() for more information.");
        log.debug("Bulk operation failed due to one or more failed actions within the Bulk request");
    } else {
        result.setSucceeded(true);
        log.debug("Bulk operation was successfull");
    }

源码中,有明确告诉:

批量操作失败,会打印Bulk operation failed错误。但是没有明确告诉,错在哪里?

更进一步,有告诉:

BulkResult.getItems() for more information

调试后,打印日志如下:

error = [io.searchbox.core. BulkResult BulkResultItem@48782849,io.searchbox.core.BulkResult B u l k R e s u l t I t e m @ 48782849 , i o . s e a r c h b o x . c o r e . B u l k R e s u l t BulkResultItem@6e2561df,
io.searchbox.core.BulkResult$BulkResultItem@1196e6ad,

貌似,还是没有根本错误的原因。

3.2 Google/Stackoverflow 查询

类似问题,匹配度少,无果。

3.3 想想可能出错的场景?

1、是不是要大量doc没有formerge导致的?

和这个没有本质关联。

2、是不是短时间内大量更新,导致的冲突。

不会的,ES是_version版本控制的。

3、是不是批量操作量太大了,超过队列长度大小。

逐步减少到1,才找到根本原因。

4、小结

1、批量的错误,要缩小范围调试,单条数据看有没有问题?

2、跟踪排查,转化思路,不放弃,直到定位本质原因。

加入知识星球,更短时间更快习得更多干货!
加入知识星球,更短时间更快习得更多干货!

2018-05-25 22:47 思于家中床前

作者:铭毅天下
转载请标明出处,原文地址:
http://blog.youkuaiyun.com/laoyang360/article/details/52244917
如果感觉本文对您有帮助,请点击‘顶’支持一下,您的支持是我坚持写作最大的动力,谢谢!

排一下bug,有问题<script setup> import { dictStageList,dictECUList } from '@/api/architecture/dict'; import { dotDetail } from '@/api/architecture'; import { nextTick, onMounted,watch } from 'vue'; import { debounce } from "lodash-es"; const props=defineProps({ workitemInstId:String }) // 阶段 -------------------------------- const stage_selectValue=ref('') const stage_list=ref([]) const currentNodeKey=ref('') const fetchStageList=debounce(async()=>{ let res=await dictStageList(stage_selectValue.value).catch(()=>{}) if(res.code===200){ stage_list.value=[{ id:0, label:'全选', children:res.data.map(i=>{ return { id:i.stageConfigId, label:i.stageName } }) }]; } // 默认第一个 nodeClick(stage_list.value[0].children[0]) },300) fetchStageList() const nodeClick=(node)=>{ if(node.label==='全选')return currentNodeKey.value=node.id; // console.log('阶段-选中节点',node.id) // console.log('阶段-选中节点-stageList',stageList.value) let findCurrentStateItem=stageList.value.find(i=>i.stageConfigId===node.id); // handle_bindModelRow(findCurrentStateItem) bindModelRow.value=[] bindEcuRow.value={} currentNodeKey2.value='' elTreeModelRef.value.setCheckedKeys(bindModelRow.value) if(!findCurrentStateItem){ elTreeModelRef.value.setCheckedKeys(bindModelRow.value) vxeTableRef.value.setRadioRow(bindEcuRow.value) }else{ bindModelRow.value=findCurrentStateItem.modelList.map(i=>i.modelConfigId) elTreeModelRef.value.setCheckedKeys(bindModelRow.value) // console.log('--------------------',findM) nextTick(()=>{ let findM=model_list.value[0].children.find(it=>bindModelRow.value.includes(it.id)) // nodeClick2(findM) currentNodeKey2.value=findM.id; // console.log('车型-选中节点',node) filterECUlist(findM.label) }) } // console.log('阶段-选中节点数据',findCurrentStateItem) // console.log('------bindStageRow:',bindStageRow.value) 这个应该在初始数据绑定 // console.log('------bindModelRow:',bindModelRow.value) // console.log('------bindEcuRow:',bindEcuRow.value) } // 车型 -------------------------------- const model_selectValue=ref('') let model_list_all=[] //全部model const model_list=ref([]) const fetchECUList=async()=>{ let res=await dictECUList('').catch(()=>{}) if(res.code!==200)return ecu_list_all=res.data; model_list_all=new Set(res.data.map(i=>i.modelName)); model_list.value=[{ id:0, label:'全选', children:[...model_list_all].map((i,index)=>{ return { id:i, label:i } }) }]; } fetchECUList() const filterData=debounce(()=>{ model_list.value=[{ id:0, label:'全选', children:[...model_list_all].filter(item=>item.includes(model_selectValue.value)).map((i,index)=>{ return { id:i, label:i } }) }]; },300) const currentNodeKey2=ref('') const nodeClick2=(node)=>{ if(node.label==='全选')return currentNodeKey2.value=node.id; // console.log('车型-选中节点',node) filterECUlist(node.label) } // ECU -------------------------------- const ecu_selectValue=ref('') const ecu_list=ref([]) let ecu_list_all=[] //全部ecu let two_ecu_list_all=[] const filterECUlist=(sval)=>{ two_ecu_list_all=ecu_list_all.filter(i=>{ return i.modelName===sval; }) ecu_list.value=two_ecu_list_all handle_bindEcuRow(true,{}) } const debounceFilterECUlist=debounce(()=>{ ecu_list.value=two_ecu_list_all.filter(i=>i.modelName.includes(ecu_selectValue.value) || i.chnFullName.includes(ecu_selectValue.value)) },300) // 已选择 -------------------------------- const stageList=ref([]); const bindStageRow=ref([]); // 当前选中的阶段 const bindModelRow=ref([]); // 当前选中的车型 const bindEcuRow=ref({}); // 当前选中的ecu对象 const elTreeStageRef=ref() const elTreeModelRef=ref() const vxeTableRef=ref() // watch(bindStageRow,(nVal)=>{ // console.log('监听[阶段]值',nVal) // },{deep:true}) // 处理bindStageRow const handle_bindStageRow=(check,callF)=>{ // console.log(check) let m_findIndex=bindStageRow.value.findIndex(i=>currentNodeKey.value===i); if(m_findIndex === -1 && check){ bindStageRow.value.push(currentNodeKey.value) }else if(m_findIndex !== -1 && !check){ bindStageRow.value.splice(m_findIndex,1) } callF && callF(); } // 处理bindModelRow const handle_bindModelRow=(check,callF)=>{ console.log(check,currentNodeKey2.value) let m_findIndex=bindModelRow.value.findIndex(i=>currentNodeKey2.value===i); if(m_findIndex === -1 && check){ bindModelRow.value.push(currentNodeKey2.value) }else if(m_findIndex !== -1 && !check){ bindModelRow.value.splice(m_findIndex,1) handle_bindEcuRow(true,{}) } callF && callF(); } // 处理bindEcuRow const handle_bindEcuRow=(bol,data,callF)=>{ if(!bol){ if(JSON.stringify(bindEcuRow.value) === '{}'){ bindEcuRow.value=data; callF && callF(data); } }else{ bindEcuRow.value=data; } } // 选第一个 const checkChange=(data,check)=>{ if(data.label==='全选')return currentNodeKey.value!==data.id ? nodeClick(data) : null; // console.log("****checkChange",data,check) handle_bindStageRow(check) setStageList(1,check) } // 选中间一个,处理前后 const checkChange2=(data,check)=>{ // console.log('******我是中间的',data,check,currentNodeKey2.value!==data.id) if(data.label==='全选')return currentNodeKey2.value!==data.id ? nodeClick2(data) : null; // 反选 nextTick(()=>{ //设置默认值 handle_bindEcuRow(false,check?ecu_list.value[0]:{},(row)=>{ if(vxeTableRef.value){ vxeTableRef.value.setRadioRow(row) } }) handle_bindModelRow(check) console.log(bindModelRow.value.length) handle_bindStageRow(bindModelRow.value.length,()=>{ elTreeStageRef.value.setCheckedKeys(bindStageRow.value) }) setStageList(2,check) }) } // 选最后一个,处理前两个 const handleRadioChange=(e)=>{ handle_bindEcuRow(true,e.row) // 反选 handle_bindModelRow(true,()=>{ elTreeModelRef.value.setCheckedKeys(bindModelRow.value) }) handle_bindStageRow(bindModelRow.value.length,()=>{ elTreeStageRef.value.setCheckedKeys(bindStageRow.value) }) setStageList(3) } // 组装值 stageList const setStageList=(type,check)=>{ let findIndexS=stageList.value.findIndex(i=>i.stageConfigId===currentNodeKey.value); let modelName=model_list.value[0].children.find(item=>item.id===currentNodeKey2.value)?.label let stageItem=stageList.value.slice(findIndexS,findIndexS+1)[0]; if(type===1){ if(!check){ stageList.value.splice(findIndexS,1) }else if(findIndexS === -1 && check){ // 不存在stageItem新增 let newStageItem={ stageConfigId:currentNodeKey.value, stageName:stage_list.value[0].children.find(item=>item.id===currentNodeKey.value)?.label, modelList:modelName?[ { stageConfigId:currentNodeKey.value, modelConfigId:modelName, modelName:modelName, ecuVo:{ stageConfigId: currentNodeKey.value, modelConfigId: modelName, ecuConfigId: bindEcuRow.value.ecuConfigId, ecuAbbr: bindEcuRow.value.ecuAbbr, chnFullName: bindEcuRow.value.chnFullName, } } ]:[] } stageList.value.push(newStageItem) } }else if(type===2){ // 选车型 // console.log(stageItem) let modelFindIndex=stageItem?.modelList?.findIndex(cItem=>cItem.modelConfigId === modelName) if(!modelFindIndex)return // console.log('**',modelFindIndex,check) if(modelFindIndex===-1 && check){ let modelItem={ stageConfigId:currentNodeKey.value, modelConfigId:modelName, modelName:modelName, ecuVo:{ stageConfigId: currentNodeKey.value, modelConfigId: modelName, ecuConfigId: bindEcuRow.value.ecuConfigId, ecuAbbr: bindEcuRow.value.ecuAbbr, chnFullName: bindEcuRow.value.chnFullName, } } stageItem.modelList.push(modelItem) }else if(modelFindIndex!==-1 && !check){ stageItem.modelList.splice(modelFindIndex,1) } }else if(type===3){ // 更新ecuVo let modelItem=stageItem.modelList.find(cItem=>cItem.modelConfigId === modelName); if(modelItem){ modelItem.ecuVo={ stageConfigId: currentNodeKey.value, modelConfigId: modelName, ecuConfigId: bindEcuRow.value.ecuConfigId, ecuAbbr: bindEcuRow.value.ecuAbbr, chnFullName: bindEcuRow.value.chnFullName, } } } // console.log('组合完成--stageList---',stageList.value) } </script> <template> <div class="dot-content"> <div class="select-left"> <!-- 阶段 --> <div class="item"> <el-input v-model="stage_selectValue" placeholder="搜索" prefix-icon="Search" size="small" clearable @input="fetchStageList"/> <el-tree ref="elTreeStageRef" :data="stage_list" show-checkbox node-key="id" default-expand-all :current-node-key="currentNodeKey" highlight-current @node-click="nodeClick" @check-change="checkChange" > <template #default="{ node, data }"> <el-text @click.stop="nodeClick(node.data)" truncated>{{ node.label }}</el-text> </template> </el-tree> </div> <!-- 车型 --> <div class="item"> <el-input v-model="model_selectValue" placeholder="搜索" prefix-icon="Search" size="small" clearable @input="filterData"/> <el-tree ref="elTreeModelRef" :data="model_list" show-checkbox node-key="id" default-expand-all :current-node-key="currentNodeKey2" highlight-current @node-click="nodeClick2" @check-change="checkChange2" > <template #default="{ node, data }"> <el-text @click.stop="nodeClick2(node.data)" truncated>{{ node.label }}</el-text> </template> </el-tree> </div> <!-- ecu --> <div class="item"> <el-input v-model="ecu_selectValue" placeholder="搜索" prefix-icon="Search" size="small" clearable @input="debounceFilterECUlist"/> <vxe-table size="mini" ref="vxeTableRef" border="inner" :cell-config="{height:32}" :header-cell-config="{height:32}" :data="ecu_list" @radio-change="handleRadioChange"> <vxe-column type="radio" title=""></vxe-column> <vxe-column field="ecuAbbr" title="ECU"></vxe-column> <vxe-column field="chnFullName" title="中文全称"></vxe-column> </vxe-table> </div> </div> <!-- 已选择 --> <div class="select-right"> <div class="top-menu"> <div class="select-title">已选择</div> <el-button type="danger" icon="Delete" text @click="delAllWiki" size="small">清除</el-button> </div> <div class="con-list"> <div class="item" v-for="item in stageList" :key="item.stageConfigId"> <!-- 阶段 --> <div class="stage-name"> <el-text truncated>{{ item.stageName }}</el-text> <el-button link type="danger" icon="Close"></el-button> </div> <div class="model-list"> <div class="m-l-item" v-for="sitem in item.modelList" :key="sitem.stageConfigId"> <el-text truncated>{{ sitem.modelName }}</el-text> <el-text truncated>{{ sitem.ecuVo?.ecuAbbr }}</el-text> <el-text truncated>{{ sitem.ecuVo?.chnFullName }}</el-text> <el-button link type="danger" icon="Close"></el-button> </div> </div> </div> </div> </div> </div> </template>
最新发布
09-02
插件的工程化编写 本节内容 本节将介绍如何使用 Node.js 项目编译出海豹可使用的插件,面向有前端经验的开发者。 我们假定你了解如何使用前端工具链,你应当具备诸如命令行、Node.js、npm/pnpm 等工具的使用知识。如果你对这些内容感到陌生,请自行了解或转至 使用单 JS 文件编写,手册不会介绍这些相关背景知识。 如果你打算使用 TypeScript,或者需要编写大型插件,希望更加工程化以方便维护,可以创建项目使用前端工具链来编译出插件。 海豹提供了相应的 模板项目。注册扩展和指令的代码已经写好,可以直接编译出一个可直接装载的 JS 扩展文件。 Clone 或下载模板项目 推荐的流程: 在 模板项目仓库 点击 Use this template 按钮,使用该模板在自己的 GitHub 上创建一个扩展的仓库,并设置为自己的扩展的名字; git clone 到本地,进行开发。 如果不打算使用 GitHub 托管仓库,希望先在本地编写: 在 模板项目仓库 点击 Code 按钮,在出现的浮窗中选择 Download ZIP,这样就会下载一个压缩包; 解压后进行开发。 补全信息 当插件开发完成后(或者开始开发时),你还需要修改几处地方: header.txt:这个文件是你插件的描述信息; tools/build-config.js:最开头一行 var filename = 'sealdce-js-ext.js'; 改成你中意的名字,注意不要与现有的重名。这决定了编译时输出的插件文件名。 (可选)package.json:修改其中 name version description 等项目描述信息,不过不修改也不会影响编译。 使用和编译 在确认你所使用的包管理器后,在命令行使用如下命令安装依赖: npm install 当你写好了代码,需要工程编译为插件的单 js 文件以便上传到海豹骰时,在命令行使用如下命令: npm run build 编译成功的 js 文件在 dist 目录下,默认的名字是 sealdce-js-ext.js。 目录结构 只列出其中主要的一些文件 src index.ts:你的扩展的代码就写在这个文件里。 tools build-config.js:一些编译的配置,影响 index.ts 编译成 js 文件的方式; build.js:在命令 npm run build 执行时所运行的脚本,用于读取 build-config 并按照配置进行编译。 types seal.d.ts:类型文件,海豹核心提供的扩展 API。 header.txt:扩展头信息,会在编译时自动加到目标文件头部; package.json:命令 npm install 时就在安装这个文件里面所指示的依赖包; tsconfig.json:TypeScript 的配置文件。 其他问题 我能在项目中引用 npm 包吗? 当然可以,像正常的前端项目一样,你可以在其中引用其他 npm 包,比如模板项目中就为你引入了常用的 lodash-es。 一般来说纯 JS 编写的包都是可以引用的,一些强 native 相关的包可能存在兼容性问题,你需要自行尝试。 推荐你尽量使用 esm 格式的包,不过 commonjs 格式的包也是可以使用的,如 dayjs。其他格式的支持和更多问题排查,请查阅模板项目所使用的构建工具 esbuild 的文档,tools/build-config.js 中即是 esbuild 的配置项。 我想使用的 API 没有被自动提示,直接使用被提示错误,如何解决? types/seal.d.ts 文件中维护了海豹提供的 API,但目前来说维护的并不完全。如果你发现有一些存在的 API 未被提示,可以手动在 types/seal.d.ts 补上来解决报错。 有时 seal.d.ts 会有更新,可以去模板项目仓库看看有没有最新的,有的话可以替换到你的项目中。也非常欢迎你向模板仓库提 PR 来帮忙完善。 默认输出的插件代码是压缩过的,如何尽量保持产物的可读性? 调整 tools/build-config.js 中的选项,关闭 minify: module.exports = { ... build: { ... minify: false, ... } } 这个是它的js排堆编写方式,不知道能不能更简便地达成上面的要求
07-11
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭毅天下

和你一起,死磕Elastic!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值