関数でのエラーの処理
このページでは、Atlas Function でエラーを処理する方法について説明します。
注意
AWS Eventbridgeを使用したデータベーストリガーのカスタムエラー処理
AWS Eventbridgeを使用して、データベーストリガー専用のカスタムエラーハンドラーを作成できます。 詳細については、「 カスタムエラー処理 」を参照してください。
try...catch ステートメントなどの標準の JavaScript エラー処理手法を使用して、関数エラーを処理できます。
function willThrowAndHandleError() { try { throw new Error("This will always happen"); } catch (err) { console.error("An error occurred. Error message:" + err.message); } } exports = willThrowAndHandleError;
ログの表示
Atlas trigger によって呼び出されるすべての関数実行のレコードは、 Logsページで表示できます。 これには、エラーにより正常に実行されなかった関数が含まれます。
詳しくは、「 trigger ログ 」を参照してください。
関数を再試行する
Atlas Function には再試行動作が組み込まれておらず、
カスタム再試行動作を追加できます。 例、関数を呼び出すサードパーティ サービスに断続的な接続があり、サードパーティ サービスが一時的にダウンした場合でも関数を再実行する場合は、再試行動作を追加する必要があります。
次のセクションでは、関数に再試行動作を追加するために利用可能な方法について説明します。
エラー処理ブロックでの関数の再帰呼び出し
関数 を再帰的に呼び出すことで、失敗する可能性のある操作を処理できます。
高レベルでは、このプロセスには次のコンポーネントが含まれます。
try
ステートメントで再試行する操作を実行し、catch
ステートメントで関数自体を呼び出すようにします。無限の実行を防ぐには、最大再試行回数を設定します。 関数が失敗して
catch
ステートメントになるたびに、現在の再試行回数のカウントが増加します。 関数の現在の再試行回数が最大再試行回数に達したときに、再帰実行を停止します。また、再試行を制限して、時間枠での実行の合計回数を減らすこともできます。
次の表では、再帰呼び出し戦略で関数の再試行を処理する際のいくつかの利点と不の問題を説明します。
利点 | ディスカッション |
---|---|
|
|
次のコード例は、エラー処理ブロックで再帰を使用して関数を再試行する実装を示しています。
// Utility function to suspend execution of current process async function sleep(milliseconds) { await new Promise((resolve) => setTimeout(resolve, milliseconds)); } // Set variables to be used by all calls to `mightFail` // Tip: You could also store `MAX_RETRIES` and `THROTTLE_TIME_MS` // in App Services Values const MAX_RETRIES = 5; const THROTTLE_TIME_MS = 5000; let currentRetries = 0; let errorMessage = ""; async function mightFail(...inputVars) { if (currentRetries === MAX_RETRIES) { console.error( `Reached maximum number of retries (${MAX_RETRIES}) without successful execution.` ); console.error("Error Message:", errorMessage); return; } let res; try { // operation that might fail res = await callFlakyExternalService(...inputVars); } catch (err) { errorMessage = err.message; // throttle retries await sleep(THROTTLE_TIME_MS); currentRetries++; res = await mightFail(...inputVars); } return res; } exports = mightFail;
データベーストリガーを使用した再試行
また、データベースtriggerを使用して再試行を実行し、 MongoDBコレクションを使用して以前に失敗した実行を追跡することで、関数を再試行することもできます。
高レベルでは、このプロセスには次のコンポーネントが含まれます。
再試行したいロジックを実行するメイン関数は、 ハンドラー関数にラップされています。
メインの 関数 の失敗した実行を追跡する失敗した実行トグルの MongoDB コレクション。
メイン関数を呼び出し、関数が失敗したときに失敗した実行追跡コレクションにログを記録するハンドラー関数。
ハンドラー関数が失敗した実行追跡コレクションにエラーを追加するたびにハンドラー関数を再実行するデータベースtrigger関数。
ハンドラー関数、実行追跡コレクション、データベースtrigger関数の 1 セットで複数のメイン関数をサポートできます。
利点 | ディスカッション |
---|---|
|
|
Atlas UIまたは App Services CLI を使用して、関数の再試行メカニズムを作成できます。 次の手順では、ハンドラー、再試行データベースtrigger関数、およびメイン関数の作成手順を説明します。
ハンドラー関数の作成
まず、メインの関数を呼び出すハンドラー関数handleRetry
を作成します。
handleRetry
は、次のパラメーターを受け入れます。
Parameter | タイプ | 説明 |
---|---|---|
functionToRetry | JavaScript 関数 | 再試行する関数。 |
functionName | 文字列 | 再試行する関数の名前。 |
operationId | ObjectId | 再試行を含むメイン関数の実行のための一意の識別子。 |
previousRetries | 番号 | メイン関数が以前に試行された回数。 |
...args | 残りのパラメータ | メイン関数に渡される引数の数は無制限。 |
handleRetry
次の操作を実行します。
try
ステートメントでfunctionToRetry
の実行を試みます。 実行が成功した場合、handleRetry
はfunctionToRetry
によって返された値を返します。前のステップで
functionToRetry
の実行でエラーがスローされた場合、catch
ステートメントは次のようにエラーを処理します。以前の再試行回数が最大再試行回数と等しいかどうかを確認します。 2 つの数値が同じ場合、最大再試行に達しているため、関数はエラーをスローします。 関数は再試行を行わなくなりました。
データベースに挿入する関数の実行ログ エントリ オブジェクトを構築します。
失敗した実行追跡コレクションへの参照を取得します。
関数の実行ログエントリを 失敗した実行追跡追跡コレクションに挿入しログ。 この挿入操作により、次の手順で作成するデータベースtrigger関数が起動します。
メイン関数は引数functionToRetry
として渡されます。 handleRetry
はメインの関数の実行を試みます。 実行が失敗した場合、この関数はメイン関数を再試行します。
Functionsに移動します。 [ Create New Functionボタンをクリックします。
フィールドNameに
handleRetry
を追加します。Function Editorに次のコードを追加し、関数を保存します。
handleRetry.js// Tip: You could also put this in an App Services Value const MAX_FUNC_RETRIES = 5; async function handleRetry( functionToRetry, functionName, operationId, previousRetries, ...args ) { try { // Try to execute the main function const response = await functionToRetry(...args); return response; } catch (err) { // Evaluates if should retry function again. // If no retry, throws error and stops retrying. if (previousRetries === MAX_FUNC_RETRIES) { throw new Error( `Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}` ); } // Build function execution log entry for insertion into database. const logEntry = { operationId, errorMessage: err.message, timestamp: new Date(), retries: previousRetries + 1, args, functionName, }; // Get reference to database collection const executionLog = context.services .get("mongodb-atlas") .db("logs") .collection("failed_execution_logs"); // Add execution log entry to database await executionLog.insertOne(logEntry); return; } } exports = handleRetry;
以下を
functions/config.json
に追加します。functions/config.json[ { "name": "handleRetry", "private": true, "run_as_system": true } // ...other configuration ] 関数
functions/handleRetry.js
の ファイルを作成します。functions/handleRetry.js// Tip: You could also put this in an App Services Value const MAX_FUNC_RETRIES = 5; async function handleRetry( functionToRetry, functionName, operationId, previousRetries, ...args ) { try { // Try to execute the main function const response = await functionToRetry(...args); return response; } catch (err) { // Evaluates if should retry function again. // If no retry, throws error and stops retrying. if (previousRetries === MAX_FUNC_RETRIES) { throw new Error( `Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}` ); } // Build function execution log entry for insertion into database. const logEntry = { operationId, errorMessage: err.message, timestamp: new Date(), retries: previousRetries + 1, args, functionName, }; // Get reference to database collection const executionLog = context.services .get("mongodb-atlas") .db("logs") .collection("failed_execution_logs"); // Add execution log entry to database await executionLog.insertOne(logEntry); return; } } exports = handleRetry; 変更を配置します。
次のコマンドを実行して、変更を配置します。
appservices push
データベース再試行trigger作成する
Triggersページに移動します。
まだ表示されていない場合は、プロジェクトを含む組織をナビゲーション バーの Organizations メニューで選択します。
まだ表示されていない場合は、ナビゲーション バーの Projects メニューからプロジェクトを選択します。
サイドバーで、 Services見出しの下のTriggersをクリックします。
Triggersページが表示されます。
[Add a Trigger] をクリックします。
次の構成で trigger を作成します。
フィールド値名前選択した名前(例:retryOperation
)enabledはい再有効化時にイベントをスキップはいイベントの順序付けはいクラスター名選択した名前(例:mongodb-atlas
)databaseName選択した名前(例:logs
)コレクション名選択した名前(例:failed_execution_logs
)操作タイプInsert完全な文書はいドキュメントのプレイメージNoイベントタイプの選択関数関数[ + New Functionをクリックします。 関数の内容については、次の情報を参照してください。高度な構成該当なし - 詳細な構成は必要ありません。ここで、trigger が呼び出す関数にコードを追加します。
関数
retryOperation
は、再試行ハンドラーが失敗した実行トレースコレクションに投稿されたドキュメントであるlogEntry
をパラメーターとして受け取ります。 次に、retryOperation
はcontext.functions.execute()を使用して、logEntry
からの情報でメイン関数を呼び出します。フィールドFunction Nameに
retryOperationDbTrigger
を追加します。フィールドFunctionに次のコードを追加し、trigger を保存します。
functions/retryOperationDbTrigger.jsasync function retryOperation({ fullDocument: logEntry }) { // parse values from log entry posted to database const { args, retries, functionName, operationId } = logEntry; // Re-execute the main function await context.functions.execute(functionName, ...args, operationId, retries); } exports = retryOperation;
MongoDB Atlasユーザーを認証します。
MongoDB Atlas Administration APIキーを使用して、App Services CLI にログします。
appservices login --api-key="<API KEY>" --private-api-key="<PRIVATE KEY>" アプリの最新の構成ファイルを取得します。
次のコマンドを実行して、構成ファイルのローカルコピーを取得します。
appservices pull --remote=<App ID> デフォルトでは 、コマンドは現在の 作業ディレクトリにファイルをプルします。 任意の
--local
フラグを使用してディレクトリパスを指定できます。データベースtriggerの構成を追加します。 詳細については、 「 trigger 構成参照」 を参照してください。
triggers/retryOperation.json{ "name": "retry", "type": "DATABASE", "config": { "operation_types": ["INSERT"], "database": "logs", "collection": "failed_execution_logs", "service_name": "mongodb-atlas", "project": {}, "full_document": true, "full_document_before_change": false, "unordered": false, "skip_catchup_events": false }, "disabled": false, "event_processors": { "FUNCTION": { "config": { "function_name": "retryOperationDbTrigger" } } } } ここで、trigger が呼び出す
functions/config.json
関数にコードを追加します。関数
retryOperation
は、再試行ハンドラーが失敗した実行トレースコレクションに投稿されたドキュメントであるlogEntry
をパラメーターとして受け取ります。 次に、retryOperation
はcontext.functions.execute()を使用して、logEntry
からの情報でメイン関数を呼び出します。functions/config.json[ // ...other configuration { "name": "retryOperationDbTrigger", "private": true } ] ファイル
functions/retryOperationDbTrigger.js
に次のコードを追加します。retryOperationDbTrigger.jsasync function retryOperation({ fullDocument: logEntry }) { // parse values from log entry posted to database const { args, retries, functionName, operationId } = logEntry; // Re-execute the main function await context.functions.execute(functionName, ...args, operationId, retries); } exports = retryOperation; 変更を配置します。
次のコマンドを実行して、変更を配置します。
appservices push
メイン関数を作成する
関数ハンドラーと再試行データベースtrigger関数が用意できたら、メインの関数を記述できます。
次の例では、関数は追加を実行するときにランダムにエラーをスローします。 このロジックを実行する JavaScript 関数は次のとおりです。
getRandomOneTwoThree()
: 例のエラーを生成するためのヘルパー関数。additionOrFailure()
: メイン ロジックを持つ関数。
再試行ハンドラーによってラップされたadditionOrFailure()
の呼び出しは、エクスポートされた関数additionWithRetryHandler()
で発生します。 再試行ハンドラー関数を使用するすべての関数は、この関数のようになります。
この関数が再試行ロジックの残りの部分で動作するようにするには、正しいパラメーターを含める必要があります。 これらのパラメーターは次のとおりです。
Parameter | タイプ | 説明 |
---|---|---|
...args | 残りのパラメータ | メイン ロジックを持つ関数に渡す 0 個以上のパラメーター。 この例の場合、 additionOrFailure() 、 num1 、 num2 に追加された 2 つの数値。 |
operationId | 関数の呼び出しと再試行のための一意の識別子。 デフォルト値を new BSON.ObjectId() に設定します。 | |
retries | 番号 | デフォルト値を 0 に設定します。 |
additionWithRetryHandler
の本体は、 context.functions.execute()
によって呼び出される再試行ハンドラーhandleRetry
であり、これによりadditionOrFailure
が呼び出されます。 context.functions.execute()
に渡す引数は次のとおりです。
Argument | タイプ | 説明 |
---|---|---|
"handleRetry" | 文字列 | メイン関数が正しく実行されない場合にメイン関数を呼び出し、再試行ログに投稿するために定義した関数の名前。 |
additionOrFailure | JavaScript 関数 | handleRetry() が呼び出すメイン関数。 |
operationId | BSON.ObjectId | additionWithRetryHandler() のパラメータoperationId からの引数として渡されます。 |
retries | 番号 | additionWithRetryHandler() のパラメータretries からの引数として渡されます。 |
...args | 引数を分散する | メイン ロジックを持つ関数に渡す 0 個以上の引数。 additionWithRetryHandler() のパラメータ...args からの引数として渡されます |
フィールドFunction Nameに
additionWithRetryHandler
を追加します。フィールドFunctionに、次のコードを追加します。
additionalWithRetryHandler.js// randomly generates 1, 2, or 3 function getRandomOneTwoThree() { return Math.floor(Math.random() * 3) + 1; } function additionOrFailure(num1, num2) { // Throw error if getRandomOneTwoThree returns 1 const rand = getRandomOneTwoThree(); if (rand === 1) throw new Error("Uh oh!!"); const sum = num1 + num2; console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`); // Otherwise return the sum return sum; } async function additionWithRetryHandler( inputVar1, inputVar2, // create a new `operation_id` if one not provided operationId = new BSON.ObjectId(), // count number of attempts retries = 0 ) { const res = await context.functions.execute( "handleRetry", additionOrFailure, "additionWithRetryHandler", // MUST BE NAME OF FUNCTION operationId, retries, inputVar1, inputVar2 ); return res; } exports = additionWithRetryHandler; [Save] をクリックします。
関数のメタデータを
functions/config.json
に追加します。functions/config.json[ // ...other configuration { "name": "additionWithRetryHandler", "private": false } ] ファイル
functions/additionWithRetryHandler.js
に次のコードを追加します。functions/additionWithRetryHandler.js// randomly generates 1, 2, or 3 function getRandomOneTwoThree() { return Math.floor(Math.random() * 3) + 1; } function additionOrFailure(num1, num2) { // Throw error if getRandomOneTwoThree returns 1 const rand = getRandomOneTwoThree(); if (rand === 1) throw new Error("Uh oh!!"); const sum = num1 + num2; console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`); // Otherwise return the sum return sum; } async function additionWithRetryHandler( inputVar1, inputVar2, // create a new `operation_id` if one not provided operationId = new BSON.ObjectId(), // count number of attempts retries = 0 ) { const res = await context.functions.execute( "handleRetry", additionOrFailure, "additionWithRetryHandler", // MUST BE NAME OF FUNCTION operationId, retries, inputVar1, inputVar2 ); return res; } exports = additionWithRetryHandler; 変更を配置します。
次のコマンドを実行して、変更を配置します。
appservices push
これでadditionWithRetryHandler
を呼び出すと、失敗すると関数は再試行されます。