Docs Menu
Docs Home
/ /
Atlas App Services
/

関数でのエラーの処理

項目一覧

  • 基本的なエラー処理
  • ログの表示
  • 関数を再試行する
  • エラー処理ブロックでの関数の再帰呼び出し
  • データベーストリガーを使用した再試行

このページでは、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;

App Service ログでは、エラーにより実行が成功し なかった 関数の実行を含むすべての関数実行のレコードを表示できます。

関数が呼び出される方法によっては、ログに表示される方法が異なります。 たとえば、 Atlas Triggersによって呼び出される関数のログは「trigger」としてログに表示され、 Realmクライアント SDK から呼び出される関数のログは「Functions」としてログに表示されます。 詳細については、 「 ログ エントリ タイプのドキュメント 」を参照してください。

Atlas Function には再試行動作が組み込まれておらず、 カスタム再試行動作を追加できます。 たとえば、関数を呼び出すサードパーティ サービスに断続的な接続があり、サードパーティ サービスが一時的にダウンしても関数を再実行する場合は、再試行動作を追加する必要があります。

このセクションでは、関数に再試行動作を追加するための次の方法について説明します。

関数 を再帰的に呼び出すことで、失敗する可能性のある操作を処理できます。

高レベルでは、このプロセスには次のコンポーネントが含まれます。

  • tryステートメントで再試行する操作を実行し、 catchステートメントで関数自体を呼び出すようにします。

  • 無限の実行を防ぐには、最大再試行回数を設定します。 関数が失敗してcatchステートメントになるたびに、現在の再試行回数のカウントが増加します。 関数の現在の再試行回数が最大再試行回数に達したときに、再帰実行を停止します。

  • また、再試行を制限して、時間枠での実行の合計回数を減らすこともできます。

次の表では、再帰呼び出し戦略で関数の再試行を処理する際のいくつかの利点と不の問題を説明します。

利点
ディスカッション
  • すべての再試行ロジックは 1 つの関数内で発生します。

  • 関数は再試行後に値を返すことができます。

  • 追加コードは最小限に抑えられます。

  • すべての再試行は、1 つの関数の最大実行時間内に発生する必要があります。

次のコード例は、エラー処理ブロックで再帰を使用して関数を再試行する実装を示しています。

// 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 セットで複数のメイン関数をサポートできます。

利点
ディスカッション
  • 各再試行は独自の関数実行であり、独自の最大実行時間とリソースを使用します。

  • 関数を再試行すると、値を返すことができなくなります。

  • 各関数を呼び出すには、関数自体、1 つは再試行ハンドラーの 2 つの関数の呼び出しが必要です。

  • より複雑なロジックでは、書込み、デバッグ、モニターがより困難になる可能性があります。

1

まず、メインの関数を呼び出すハンドラー関数handleRetryを作成します。

handleRetry は、次のパラメーターを受け入れます。

Parameter
タイプ
説明
functionToRetry
JavaScript 関数
再試行する関数。
functionName
文字列
再試行する関数の名前。
operationId
ObjectId
再試行を含むメイン関数の実行のための一意の識別子。
previousRetries
番号
メイン関数が以前に試行された回数。
...args
残りのパラメータ
メイン関数に渡される引数の数は無制限。

handleRetry 次の操作を実行します。

  1. tryステートメントでfunctionToRetryの実行を試みます。 実行が成功した場合、 handleRetryfunctionToRetryによって返された値を返します。

  2. 前のステップでfunctionToRetryの実行でエラーがスローされた場合、 catchステートメントは次のようにエラーを処理します。

    1. 以前の再試行回数が最大再試行回数と等しいかどうかを確認します。 2 つの数値が同じ場合、最大再試行に達しているため、関数はエラーをスローします。 関数は再試行を行わなくなりました。

    2. データベースに挿入する関数の実行ログ エントリ オブジェクトを構築します。

    3. 失敗した実行追跡コレクションへの参照を取得します。

    4. 関数の実行ログ エントリを失敗した実行トレース機能のコレクションに挿入します。 この挿入操作により、次の手順で作成するデータベースtrigger関数が起動します。

メイン関数は引数functionToRetryとして渡されます。 handleRetryはメインの関数の実行を試みます。 実行が失敗した場合、この関数はメイン関数を再試行します。

Functionsに移動します。 Create New Function ] ボタンをクリックします。

フィールドNamehandleRetryを追加します。

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;

Tip

App Services CLI のインストールと設定

CLI を使用して App Services App を更新する場合は、まずApp Services CLI をインストールして設定する必要があります。

以下を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;

変更を App Services にプッシュします。

appservices push
2
  1. アプリの UI でTriggersに移動します。

  2. Add a Triggerボタンをクリックします。

  3. 次の構成で trigger を作成します。

    フィールド
    名前
    選択した名前(例: retryOperation
    enabled
    はい
    再有効化時にイベントをスキップ
    はい
    イベントの順序付け
    はい
    クラスター名
    選択した名前(例: mongodb-atlas
    databaseName
    選択した名前(例: logs
    コレクション名
    選択した名前(例: failed_execution_logs
    操作タイプ
    Insert
    完全な文書
    はい
    ドキュメントのプレイメージ
    No
    イベントタイプの選択
    関数
    関数
    [ + New Functionをクリックします。 関数の内容については、次の情報を参照してください。
    高度な構成
    詳細な構成は必要ありません。

データベース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 が呼び出す関数のコードを追加します。

関数retryOperationは、再試行ハンドラーが失敗した実行トグル コレクションに投稿されたドキュメントであるlogEntryをパラメーターとして受け取ります。 次に、 retryOperationcontext.functions.execute()を使用して、 logEntryからの情報でメイン関数を呼び出します。

フィールドFunction NameretryOperationDbTriggerを追加します。

フィールドFunctionに次のコードを追加し、trigger を保存します。

functions/retryOperationDbTrigger.js
async 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;

関数のメタデータをfunctions/config.jsonに追加します。

functions/config.json
[
// ...other configuration
{
"name": "retryOperationDbTrigger",
"private": true
}
]

ファイルfunctions/retryOperationDbTrigger.jsに次のコードを追加します。

retryOperationDbTrigger.js
async 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;

変更を App Services にプッシュします。

appservices push
3

関数ハンドラーと再試行データベースtrigger関数が用意できたら、メインの関数を記述できます。

次の例では、関数は追加を実行するときにランダムにエラーをスローします。 このロジックを実行する JavaScript 関数は次のとおりです。

  • getRandomOneTwoThree(): 例のエラーを生成するためのヘルパー関数。

  • additionOrFailure(): メイン ロジックを持つ関数。

再試行ハンドラーによってラップされたadditionOrFailure()の呼び出しは、エクスポートされた関数additionWithRetryHandler()で発生します。 再試行ハンドラー関数を使用するすべての関数は、この関数のようになります。

この関数が再試行ロジックの残りの部分で動作するようにするには、正しいパラメーターを含める必要があります。 これらのパラメーターは次のとおりです。

Parameter
タイプ
説明
...args
残りのパラメータ
メイン ロジックを持つ関数に渡す 0 個以上のパラメーター。 この例の場合、 additionOrFailure()num1num2に追加された 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 NameadditionWithRetryHandlerを追加します。

フィールド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;

関数のメタデータを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;

変更を App Services にプッシュします。

appservices push

これでadditionWithRetryHandlerを呼び出すと、失敗すると関数は再試行されます。

戻る

テスト関数