循环逻辑异常:Slack API数据仅部分写入数据库问题排查
问题排查:Slack API数据仅部分写入数据库
背景说明
应用调用Slack API获取指定日期的gzip格式响应,解压为JSON文件(单日期约1500条记录),计算各字段累计值后写入数据库,最终写入的数据格式如下:
{ "date" : "2022-09-01", "number_of_users" : 1503, "is_billable_seat" : 1202, "is_active_ios" : 600, "is_active_android" : 400, "is_active_desktop" :503 }
当前传入包含4个日期的数组,通过循环调用API并写入数据库,但仅2天数据成功写入数据库,核心代码如下:
listDate = ['2022-09-01', '2022-09-02', '2022-09-03', '2022-09-04'] for(let i=0; i<listDate.length; i++){ let arr = []; let data_to_db = []; let date = listDate[i] let URL = `${process.env.slackapi}?type=member&date=${date}&pretty=1`; request(URL, { //fetch slack data from API headers: { Authorization: 'Bearer ' + process.env.accesstoken, }}) .pipe(zlib.createGunzip()) .pipe( concat(async (stringBuffer) => { result = stringBuffer.toString() fs.writeFileSync('gzip-file-' + date + '.json', result, (error) => { if (error) throw error; }); const allFileContents = fs.readFileSync('gzip-file-' + date + '.json', 'utf-8'); allFileContents.split(/\r?\n/).forEach(line => { arr.push(line) }); arr.map(obj => { if(obj){ const myObj = JSON.parse(obj); data_to_db["date"] = myObj.date data_to_db["number_of_users"] = ++number_of_users data_to_db["is_billable_seat"] = myObj.is_billable_seat == true ? is_billable_seat += 1 : is_billable_seat data_to_db["is_active_ios"] = myObj.is_active_ios == true ? is_active_ios += 1 : is_active_ios data_to_db["is_active_android"] = myObj.is_active_android == true ? is_active_android += 1 : is_active_android data_to_db["is_active_desktop"] = myObj.is_active_desktop == true ? is_active_desktop += 1 : is_active_desktop } }) console.log(data_to_db) connection.connect( async() => { console.log("DB connected!!", date) await pushSlackDataToDatabase(data_to_db, connection) //push slack data to DB }) }) ); }
错误原因排查
1. 计数器变量未隔离,存在竞态条件
代码中number_of_users、is_billable_seat等计数器变量未在循环内部声明初始化,属于全局/外部共享变量。由于request是异步操作,4个日期的请求会同时发起,异步回调会交叉执行,多个回调同时修改同一个计数器:
- 比如第一个日期的回调还在统计用户数,第二个日期的回调已经开始累加同一个
number_of_users,最终导致部分日期的统计值错误,写入数据库时可能因数据无效被拒绝,或者后执行的回调覆盖先执行的,导致仅部分数据留存。
2. 重复建立数据库连接
每次循环的异步回调中都调用connection.connect(),而数据库连接通常是单例模式,重复调用会导致:
- 连接池资源耗尽,后续连接请求被拒绝,无法写入数据库;
- 连接状态冲突,部分写入操作因连接异常失败。
3. 不必要的文件IO操作(间接隐患)
代码中先将解压后的内容写入本地JSON文件,再立即读取回来处理,完全可以直接使用result变量处理数据:
- 同步文件写入/读取会阻塞事件循环,拖慢异步回调的执行速度,加剧竞态条件的影响;
- 若文件系统出现异常(如权限不足),会导致数据处理中断,无法写入数据库。
4. 数据载体类型错误
data_to_db被定义为数组[],但实际当作对象使用(赋值键值对),虽然JS允许数组作为对象操作,但这种不规范的写法可能导致后续数据库写入函数解析数据时出错,直接导致写入失败。
修复建议
- 在循环内初始化计数器:将
number_of_users、is_billable_seat等变量放在循环内部,确保每个日期的统计独立:
for(let i=0; i<listDate.length; i++){ let arr = []; let data_to_db = {}; // 改为对象类型 let date = listDate[i]; // 初始化计数器 let number_of_users = 0; let is_billable_seat = 0; let is_active_ios = 0; let is_active_android = 0; let is_active_desktop = 0; // 后续代码... }
- 提前建立数据库连接:在循环外部完成数据库连接初始化,回调中直接使用已建立的连接:
// 循环外先连接数据库 connection.connect(async () => { console.log("DB connected!!"); for(let i=0; i<listDate.length; i++){ // 循环内直接使用connection,不再调用connect // 后续代码... await pushSlackDataToDatabase(data_to_db, connection); } });
- 移除冗余文件IO:直接使用解压后的
result处理数据,跳过写入文件再读取的步骤:
concat(async (stringBuffer) => { const result = stringBuffer.toString(); const allFileContents = result; allFileContents.split(/\r?\n/).forEach(line => { arr.push(line); }); // 后续统计代码... })
- 修正数据载体类型:将
data_to_db定义为对象{},符合键值对数据的使用场景。
内容的提问来源于stack exchange,提问作者Winterella
相关产品推荐
相关产品推荐

