2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

指数バックオフ + ジッターをTypeScriptでやるだけ

Posted at

導入

外部API叩く系のコード、テストで通ってもスケーリングしたら死ぬことがあります
単純に一回に大量に叩いたり、そうでなくてもエラーから復旧したりした時に全員が同期して叩くみたいなことがあると、それはもはやDDoS攻撃だからです

それを解決するのが指数バックオフです
ミスったらいい感じに待ってやり直すというアルゴリズムです

その待つ時間まで一緒だと同期攻撃が繰り返されるだけなので乱数でずらします それがジッターです

GCPのCloudStorageのドキュメントとかでも自分で実装しろと書いてあります 悲しい

本質

このアルゴリズムの本質は待ち時間です

expBackoff.ts
    const MIN_OFFSET_MSEC = 2000
    await sleep( (2 ** retries) * MIN_OFFSET_MSEC * ( 1 + Math.random() ) )

今回は1回目は2〜4秒、2回目は4〜8秒、3回目は8〜16秒....の中で一様分布にしています
冪乗具合やrandom具合はお好みでアレンジしてください

コード

関数と最大リトライ数を引数に、指数バックオフを実行する関数です

expBackoff.ts
const MIN_OFFSET_MSEC = 2000 // 最初は2秒からスタート、それ以降は倍倍で増える

export const expBackOff = async (func: Function, MAX_RETRIES: number) => {
    let retries = 0 // やり直した回数
    let retry = true // やり直すかフラグ
    let res

    while(retry && retries < MAX_RETRIES){
        await sleep( (2 ** retries) * MIN_OFFSET_MSEC * ( 1 + Math.random() ) ) // 本質
        try {
            res = await func() // undefinedは返さないで
            retry = false
        }
        catch (e) {
            console.log("retryCount: ", retries + 1)
            console.log("expbackoff ERROR: ", e)
        }
        retries += 1
    }
    if(res == undefined) {
        throw new Error("Expbackoff failed. retryCount: " + retries)
    }
    return res 
}

async function sleep(msec: number) {
   return new Promise(function(resolve) {
      setTimeout(function() {resolve()}, msec);
   })
}

使い方

index.ts
const fetch = require('node-fetch');

const fetchFunc = async () => { // 指数バックオフさせたい関数を作る
    return await fetch(URL, options).then((response: any) => { // ちゃんとパースしてエラーに昇華させる
        if(isYokunai(response)){ // レスポンスがよくなかった時
            throw new Error(response)
        } else {
            return response
        }
    })
    // return await someGoodApi() // ちゃんとエラーハンドリングされるならこれだけでもよい
}

const response = await expBackOff(fetchFunc, 5).then((res: any) => { // 5回やり直す
    console.log("RESULT: ", res)
    return res
}).catch((e) => {
    console.log("ERROR: ", e)
    return new Error(e) // 好きにハンドリングしてください
})

まとめ

良いスケーリングライフを!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?