Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Node.jsで「並列実行処理を一定間隔で繰り返す」

$
0
0

はじめに

自身の業務上の課題を解決するために、Node.jsのスクリプトを作りました。
そのメモです。

背景

ある会社にて、社員向けの業務システムがあります。
そのシステムでは、年に一度、社員情報の初期化を行う必要があります。
社員情報を初期化するには、社員情報更新APIを使用します。
1回のレスポンスタイムは1~2秒かかります。
そのAPIでは、引数に社員リストを指定できません。
1社員あたりにつき、1回APIを呼ぶ必要があります。
また、そのシステムで管理している社員は10,000人とします。

さて、10,000人分の社員情報初期化を考えると…

  • 10,000回のAPI呼び出しを同時実行 → サーバー側で処理を捌ききれない
  • 10,000回のAPI呼び出しを順次実行 → 時間がかかりすぎる(1回あたり2秒とすると、20,000秒 = 5時間)

つらい。

サーバーへの過剰負荷を避けつつも、効率的に全ての処理を完遂したい。

方針

APIの呼び出し方をクライアント側で工夫します。

10,000回のAPI呼び出しを同時に実行するのでもなく、順次に実行するのでもなく、中間をとります。

例えば、2,000回ずつのAPI呼び出し処理に分けて、2秒間隔で繰り返し実行する(10,000回に到達するまで)、というイメージ。

つまり、並列実行処理を、一定の間隔で繰り返します

これによって、サーバー側への過剰負荷を避けながら、効率的に処理を完遂することを目指します。

加えて、API実行エラーがあったら、その社員に対する処理はリトライしたいので、エラーが発生したAPI呼び出しは結果として出力できるようにします。

※1回のAPIでエラーが発生しても後続の処理に影響はないものとします。

サンプルプログラム(Node.js)

目的を実現するためのNode.jsスクリプトを準備しました。
Promise・await/async・resolveあたりを駆使して何とかできそうです。

尚、このスクリプトは並列実行処理を一定間隔で繰り返すためのサンプルプログラムであり、実際にはAPI呼び出しは行っていません。
最小単位の処理として、1秒待機するだけの処理をdoUnitTaskとして定義しています。
さらに、1%の確率でエラーが発生する処理として仮定しています。

example.js
// 最小単位のタスク(次の状況を想定したダミーの処理)を定義する。// - 1秒の遅延が発生する// - 1%の確率でエラーが発生するfunctiondoUnitTask(num){returnnewPromise(function(resolve,reject){setTimeout(function(){if(Math.random()<=0.01){// ※Promise.all().catch()は最初のrejectのみキャッチされる。//  複数のエラーハンドリングに対応できるように、rejectは使わずにresolveで例外を発生させる。resolve(newError(`Error occured! [id: ${num}]`));// reject();}else{resolve();}},1000);});}asyncfunctionmain(){// 全タスク数letTASK_NUM=1000;// いくつのタスクに分割するかletDEVIDE_LENGTH=100;// タスク群を実行する間隔[ms]letDURATION=2000;// 全てのタスクを実行するための配列を用意letallTasks=[];for(leti=0;i<TASK_NUM;i++){allTasks.push(i)} // 指定した数毎にタスクを分割するletdividedAllTasks=[]for(leti=0;i<allTasks.length;i+=DEVIDE_LENGTH){dividedAllTasks.push(allTasks.slice(i,i+DEVIDE_LENGTH));}// 「複数タスク」を「定期的」に「並列実行」するletsuccessedTaskNum=0leterroredTaskNum=0for(leti=0;i<dividedAllTasks.length;i++){letcurrentTasks=dividedAllTasks[i]constpromises=[];for(letj=0;j<currentTasks.length;j++){letunittask=currentTasks[j];promises.push(doUnitTask(unittask));}awaitnewPromise(function(resolve,reject){setTimeout(function(){Promise.all(promises).then(function(values){values.filter(function(item){// エラーハンドリングif(iteminstanceofError){console.log(item.message)erroredTaskNum++;}else{successedTaskNum++;}})// 進捗率を表示console.log(`Proccessing...(${Math.round((successedTaskNum+erroredTaskNum)/TASK_NUM*100)}%)`)resolve()})// ※Promise.all().catch()は最初のrejectのみキャッチする。//  複数のrejectに対応できないため使用しない。// .catch(function(reason){//     console.log(`${reason}`)// })},DURATION)});}console.log(`all done.`)console.log(`SUCCESS TASK NUM : ${successedTaskNum}`)console.log(`ERROR TASK NUM   : ${erroredTaskNum}`)}main()

実行例

$ node example.js
start.
Error occured! [id: 168]
Error occured! [id: 183]
Error occured! [id: 319]
Error occured! [id: 551]
Error occured! [id: 580]
Error occured! [id: 838]
Error occured! [id: 943]
Proccessing...(10%)
...(略)...
Proccessing...(90%)
Error occured! [id: 9152]
Error occured! [id: 9281]
Error occured! [id: 9814]
Error occured! [id: 9860]
Error occured! [id: 9972]
Proccessing...(100%)
all done.
SUCCESS TASK NUM : 9899
ERROR TASK NUM   : 101

さいごに

はやく業務で使ってみたい。


Viewing all articles
Browse latest Browse all 8691

Trending Articles