メインコンテンツまでスキップ

エラーカタログ

このカタログでは、MBC CQRS Serverlessで発生するエラーの原因、解決策、復旧戦略を含む包括的なドキュメントを提供します。

クイックリファレンス

このテーブルを使用してエラーを素早く特定し、解決策にジャンプできます。

コマンド&データエラー

コードエラーメッセージ重大度クイックフィックス
MBC-CMD-001入力が無効: アイテムが見つからないかバージョン不一致最新バージョンを取得するかversion: -1を使用
MBC-CMD-002アイテムが見つからない更新前にアイテムが存在するか確認
MBC-CMD-003無効な入力バージョンgetItem()から最新バージョンを使用

テナントエラー

コードエラーメッセージ重大度クイックフィックス
MBC-TNT-001テナントが見つからないlistTenants()でテナントの存在を確認
MBC-TNT-002テナントが既に存在作成前に存在確認

シーケンス&タスクエラー

コードエラーメッセージ重大度クイックフィックス
MBC-SEQ-001シーケンスが見つからないシーケンスは初回使用時に自動初期化
MBC-TSK-001タスクが見つからないNotFoundExceptionでタスクの存在を確認

バリデーションエラー

コードエラーメッセージ重大度クイックフィックス
MBC-VAL-001バリデーション失敗DTOの制約と入力データを確認

DynamoDBエラー

コードエラーメッセージ重大度クイックフィックス
MBC-DDB-001ProvisionedThroughputExceededException指数バックオフリトライを実装
MBC-DDB-002ConditionalCheckFailedExceptionアイテムを更新し新バージョンでリトライ
MBC-DDB-003ResourceNotFoundException重大テーブルの存在と環境変数を確認
MBC-DDB-004ValidationException空文字列を避け、予約語をエスケープ

認証エラー

コードエラーメッセージ重大度クイックフィックス
MBC-COG-001NotAuthorizedExceptionトークンを更新または再認証
MBC-COG-002UserNotFoundExceptionプールにユーザーが存在するか確認
MBC-COG-003UserNotConfirmedException確認コードを再送信

インポートモジュールエラー

コードエラーメッセージ重大度クイックフィックス
MBC-IMP-001Step Functionsタイムアウト重大適切な失敗処理のためv1.0.18以降にアップグレード
MBC-IMP-002インポート戦略が見つからないモジュール設定にImportStrategyを登録
MBC-IMP-003インポートがPROCESSINGでスタックDynamoDBストリームとSNSトピックを確認

Step Functionsエラー

コードエラーメッセージ重大度クイックフィックス
MBC-SFN-001TaskTimedOutLambdaタイムアウトを増やすかチャンク処理
MBC-SFN-002TaskFailedsendTaskFailureで適切なエラーハンドリングを追加

AWSサービスエラー

コードエラーメッセージ重大度クイックフィックス
MBC-S3-001NoSuchKeyheadObjectでオブジェクトの存在を確認
MBC-S3-002AccessDenied必要なIAM権限を追加
MBC-SQS-001MessageNotInflight可視性タイムアウト内に処理

コマンドサービスエラー

BadRequestException: "Invalid input: item not found or version mismatch"

場所: packages/core/src/commands/command.service.ts

原因: 楽観的ロックの失敗。リクエストのバージョン番号がデータベースの現在のバージョンと一致しません。

バージョンに関する注記

v1.0.25 より前のバージョンでは、この例外のメッセージは「The input is not a valid, item not found or version not match」でした。v1.0.25 で文言が修正されましたが、原因と解決策は同じです。

解決策:

// オプション1: 更新前に最新バージョンを取得
const latest = await dataService.getItem({ pk, sk });
if (!latest) throw new NotFoundException('Item not found');
await commandService.publishPartialUpdateSync({
pk,
sk,
version: latest.version,
name: 'Updated Name',
}, options);

// オプション2: version: -1を使用して自動取得(非同期モードのみ)
await commandService.publishPartialUpdateAsync({
pk,
sk,
version: -1,
name: 'Updated Name',
}, options);

// オプション3: リトライロジックを実装
async function updateWithRetry(data, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const latest = await dataService.getItem({ pk: data.pk, sk: data.sk });
if (!latest) throw new NotFoundException('Item not found');
return await commandService.publishPartialUpdateSync({
...data,
version: latest.version,
}, options);
} catch (error) {
if (error.message.includes('version mismatch') && i < maxRetries - 1) {
await new Promise(r => setTimeout(r, 100 * (i + 1)));
continue;
}
throw error;
}
}
}

BadRequestException: "The input key is not a valid, item not found"

場所: packages/core/src/commands/command.service.ts

原因: データベースに存在しないアイテムを更新しようとしています。

解決策:

// まずアイテムが存在するか確認
const existing = await dataService.getItem({ pk, sk });
if (!existing) {
// 新しいアイテムを作成
await commandService.publishAsync(newItem, options);
} else {
// 既存のアイテムを更新
await commandService.publishPartialUpdateAsync({
pk,
sk,
version: existing.version,
...updates,
}, options);
}

BadRequestException: "Invalid input version"

場所: packages/core/src/commands/command.service.ts

原因: publishSyncで最新の保存バージョンと一致しないバージョンを使用しています。

解決策: 最新のアイテムを取得してそのバージョンを使用するか、非同期メソッドでversion: -1を使用してください。


テナントエラー

BadRequestException: "Tenant not found"

場所: packages/tenant/src/services/tenant.service.ts

原因: 指定されたテナントが存在しないか削除されています。

解決策:

// テナントが存在することを確認
try {
const tenant = await tenantService.getTenant(tenantCode);
} catch (error) {
if (error.message === 'Tenant not found') {
// 利用可能なテナントを一覧表示
const tenants = await tenantService.listTenants();
console.log('Available tenants:', tenants.items.map(t => t.code));
}
}

BadRequestException: "Tenant already exist"

場所: packages/tenant/src/services/tenant.service.ts

原因: 既に存在するコードでテナントを作成しようとしています。

解決策:

// 作成前にテナントが存在するか確認
const existing = await tenantService.getTenant(tenantCode).catch(() => null);
if (existing) {
console.log('Tenant already exists, using existing tenant');
} else {
await tenantService.createTenant({ code: tenantCode, name: tenantName });
}

シーケンスエラー

BadRequestException: "Sequence not found"

場所: packages/sequence/src/services/sequence.service.ts

原因: リクエストされたシーケンスキーが存在しません。

解決策:

// シーケンスを生成 - 初回使用時に自動初期化
try {
const result = await sequencesService.generateSequenceItem(
{
tenantCode,
typeCode: 'ORDER',
},
{ invokeContext },
);
} catch (error) {
// エラーが続く場合はDynamoDBテーブルの権限を確認
}

タスクエラー

NotFoundException: "Task not found"

場所: packages/task/src/task.controller.ts

原因: 指定されたタスクが存在しないか、完了/削除されています。

解決策:

// 操作前にタスクステータスを確認
const task = await taskService.getTask({ pk, sk });
if (!task) {
throw new NotFoundException('Task not found');
}
if (task.status === 'completed') {
throw new BadRequestException('Task already completed');
}

バリデーションエラー

BadRequestException: "Validation failed"

場所: packages/core/src/pipe/class.validation.pipe.ts

原因: リクエストDTOがclass-validatorのバリデーションに失敗しました。

一般的なバリデーションエラー:

// 検証付きDTOの例
export class CreateOrderDto {
@IsNotEmpty({ message: 'Name is required' })
@IsString()
@MaxLength(100)
name: string;

@IsNotEmpty({ message: 'Code is required' })
@Matches(/^[A-Z0-9-]+$/, { message: 'Code must be uppercase alphanumeric' })
code: string;

@IsOptional()
@IsNumber()
@Min(0)
amount?: number;
}

// よくある検証エラーと修正方法:
// - "name must be a string" -> Ensure name is string type
// - "code should not be empty" -> Provide code value
// - "amount must not be less than 0" -> Use positive number

解決策:

// 送信前に検証
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

const dto = plainToInstance(CreateOrderDto, requestBody);
const errors = await validate(dto);
if (errors.length > 0) {
console.log('Validation errors:', errors.map(e => e.constraints));
}

DynamoDBエラー

ProvisionedThroughputExceededException

場所: AWS DynamoDB

原因: オンデマンドまたはプロビジョニングテーブルで読み取りまたは書き込み容量を超過しました。

解決策:

// 指数バックオフリトライを実装
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 5,
baseDelay = 100
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.name === 'ProvisionedThroughputExceededException') {
const delay = baseDelay * Math.pow(2, i) + Math.random() * 100;
console.log(`Throughput exceeded, retrying in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}

予防策:

  • 予測困難なワークロードにはオンデマンドキャパシティモードを使用
  • 書き込み操作を減らすためにリクエストバッチングを実装
  • 読み取りが多いワークロードにはDAXを使用

ConditionalCheckFailedException

場所: AWS DynamoDB

原因: 楽観的ロック条件が失敗(バージョン不一致)またはユニーク制約違反。

解決策:

// 条件チェック失敗を処理
try {
await commandService.publishSync(item, options);
} catch (error) {
if (error.name === 'ConditionalCheckFailedException') {
// 更新して再試行
const latest = await dataService.getItem({ pk, sk });
if (!latest) throw new NotFoundException('Item not found');
await commandService.publishSync({
...item,
version: latest.version,
}, options);
}
}

ResourceNotFoundException

場所: AWS DynamoDB

原因: 指定されたテーブルまたはインデックスが存在しません。

解決策:

# Verify table exists
aws dynamodb describe-table --table-name your-table-name

# Check environment variable
echo $DYNAMODB_TABLE_NAME

ValidationException: "One or more parameter values were invalid"

場所: AWS DynamoDB

原因: 無効なキー構造、非キー属性の空文字列、または予約語の競合。

解決策:

// 空文字列を避ける
const item = {
pk: 'ORDER#tenant001',
sk: 'ORDER#ORD001',
name: value || null, // Use null instead of empty string
};

// 予約語には式属性名を使用
const params = {
ExpressionAttributeNames: {
'#name': 'name',
'#status': 'status',
},
};

Cognito認証エラー

NotAuthorizedException

場所: AWS Cognito

原因: 無効な認証情報またはトークンの有効期限切れ。

解決策:

// フロントエンド: トークンをリフレッシュ
try {
await Auth.currentSession(); // Auto-refreshes if needed
} catch (error) {
if (error.name === 'NotAuthorizedException') {
// ログインにリダイレクト
await Auth.signOut();
window.location.href = '/login';
}
}

UserNotFoundException

場所: AWS Cognito

原因: ユーザープールにユーザーが存在しません。

解決策:

// 操作前にユーザーが存在することを確認
try {
const user = await adminGetUser({ Username: email });
} catch (error) {
if (error.name === 'UserNotFoundException') {
// 新しいユーザーを作成するか登録フォームを表示
}
}

UserNotConfirmedException

場所: AWS Cognito

原因: ユーザーがメール/電話を確認していません。

解決策:

try {
await Auth.signIn(email, password);
} catch (error) {
if (error.name === 'UserNotConfirmedException') {
// 確認コードを再送信
await Auth.resendSignUp(email);
// 確認ページにリダイレクト
}
}

インポートモジュールエラー

Related Documentation

APIの詳細と使用パターンについてはImportStatusHandler APIを参照してください。バージョン履歴については変更履歴 v1.0.18を参照してください。

Step Functionsタイムアウト(インポートジョブ)

場所: packages/import/src/event/import-status.queue.event.handler.ts

症状: インポートジョブでStep Functions実行が無期限にRUNNING状態のまま。

原因: バージョン1.0.18より前は、ImportStatusHandlerは完了したジョブに対してのみSendTaskSuccessCommandを送信していました。インポートジョブが失敗した場合、Step Functionsにコールバックが送信されず、waitForTaskTokenコールバックを無期限に待機していました。

解決策 (1.0.18以降で修正): ハンドラーはインポートジョブが失敗した場合に適切にSendTaskFailureCommandを送信するようになりました:

// 内部動作(自動、ユーザー操作不要):
// - COMPLETED status → SendTaskSuccessCommand
// - FAILED status → SendTaskFailureCommand

古いバージョンを使用している場合:

  1. @mbc-cqrs-serverless/import@^1.0.18にアップグレード
  2. スタックした実行については、AWSコンソールまたはCLIで手動停止:
    aws stepfunctions stop-execution --execution-arn <execution-arn>

BadRequestException: "No import strategy found for table: {tableName}"

場所: packages/import/src/import.service.ts

原因: 指定されたテーブル名にインポート戦略が登録されていません。

解決策: ImportModuleを設定する際にインポート戦略を登録:

ImportModule.register({
profiles: [
{
tableName: 'your-table-name', // Must match the tableName in your import request
importStrategy: YourImportStrategy,
processStrategy: YourProcessStrategy,
},
],
})

インポートジョブがPROCESSINGステータスでスタック

場所: packages/import/src/event/import.queue.event.handler.ts

原因: インポート処理中にエラーが発生したがジョブステータスが正しく更新されなかった、またはDynamoDBストリームが次のステップをトリガーできなかった。

解決策:

  1. LambdaエラーについてはCloudWatchログを確認
  2. import_tmpテーブルでDynamoDBストリームが有効になっているか確認
  3. インポートステータス通知用のSNSトピックが存在するか確認
  4. スタックしたレコードをクリーンアップ:
    await importService.updateStatus(
    { pk: 'CSV_IMPORT#tenant', sk: 'table#taskCode' },
    ImportStatusEnum.FAILED,
    { error: 'Manual cleanup' }
    );

Step Functionsエラー

TaskTimedOut

場所: AWS Step Functions

原因: Lambda関数が設定されたタイムアウト内に応答しませんでした。

解決策:

// serverless.ymlでLambdaタイムアウトを増加
functions:
processTask:
handler: handler.process
timeout: 900 # 15 minutes max

// またはより小さなチャンクに分割
async function processInChunks(items, chunkSize = 100) {
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
await processChunk(chunk);
}
}

TaskFailed

場所: AWS Step Functions

原因: Lambda関数が未処理のエラーをスローしました。

解決策:

import { SFNClient, SendTaskSuccessCommand, SendTaskFailureCommand } from '@aws-sdk/client-sfn';

const sfnClient = new SFNClient({});

// Step Functionsを使った適切なエラー処理
export async function handler(event: StepFunctionEvent) {
try {
const result = await processTask(event.input);

// 成功コールバックを送信
await sfnClient.send(new SendTaskSuccessCommand({
taskToken: event.taskToken,
output: JSON.stringify(result),
}));
} catch (error) {
// 失敗コールバックを送信
await sfnClient.send(new SendTaskFailureCommand({
taskToken: event.taskToken,
error: error.name,
cause: error.message,
}));
}
}

S3エラー

NoSuchKey

場所: AWS S3

原因: 指定されたオブジェクトがバケットに存在しません。

解決策:

import { S3Client, HeadObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';

const s3Client = new S3Client({});

// ダウンロード前にオブジェクトが存在するか確認
try {
await s3Client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
const data = await s3Client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
} catch (error) {
if (error.name === 'NoSuchKey' || error.name === 'NotFound') {
console.log('File does not exist:', key);
return null;
}
throw error;
}

AccessDenied

場所: AWS S3

原因: IAMポリシーがリクエストされた操作を許可していません。

解決策:

# Add required permissions in serverless.yml
provider:
iam:
role:
statements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource:
- arn:aws:s3:::${self:custom.bucketName}/*

SQSエラー

MessageNotInflight

場所: AWS SQS

原因: フライト中でなくなったメッセージの削除または可視性の変更を試みています。

解決策:

// 可視性タイムアウト内にメッセージを処理
export async function handler(event: SQSEvent) {
for (const record of event.Records) {
try {
await processMessage(record);
// 正常終了時にメッセージを自動削除
} catch (error) {
// リトライのためにメッセージをキューに保持するためスロー
throw error;
}
}
}

HTTPステータスコードリファレンス

ステータス例外意味復旧戦略
400BadRequestException無効な入力またはビジネスルール違反リクエストデータを修正
401UnauthorizedException認証が欠落しているか無効トークンを更新または再ログイン
403ForbiddenException認証済みだが権限がないユーザー権限を確認
404NotFoundExceptionリソースが見つからないリソースの存在を確認
409ConflictExceptionバージョン競合(楽観的ロック)更新して再試行
422UnprocessableEntityExceptionバリデーション失敗バリデーションエラーを修正
429TooManyRequestsExceptionレート制限超過バックオフリトライを実装
500InternalServerErrorException予期しないサーバーエラーログを確認し、バグを報告
502BadGatewayException上流サービスエラーバックオフで再試行
503ServiceUnavailableExceptionサービスが一時的に利用不可後で再試行
504GatewayTimeoutException上流サービスタイムアウトタイムアウトを増やすか最適化

エラーハンドリングのベストプラクティス

1. 集中エラーハンドラー

// グローバル例外フィルターを作成
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();

const status = exception instanceof HttpException
? exception.getStatus()
: 500;

const message = exception instanceof HttpException
? exception.message
: 'Internal server error';

// コンテキスト付きでエラーをログ記録
console.error({
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
status,
message,
stack: exception instanceof Error ? exception.stack : undefined,
});

response.status(status).json({
statusCode: status,
message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}

2. 指数バックオフでリトライ

async function retryWithBackoff<T>(
operation: () => Promise<T>,
options: {
maxRetries?: number;
baseDelay?: number;
maxDelay?: number;
retryableErrors?: string[];
} = {}
): Promise<T> {
const {
maxRetries = 3,
baseDelay = 100,
maxDelay = 10000,
retryableErrors = [
'ProvisionedThroughputExceededException',
'ThrottlingException',
'ServiceUnavailable',
],
} = options;

let lastError: Error;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;

if (!retryableErrors.includes(error.name) || attempt === maxRetries) {
throw error;
}

const delay = Math.min(
baseDelay * Math.pow(2, attempt) + Math.random() * 100,
maxDelay
);

console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}

throw lastError;
}

3. サーキットブレーカーパターン

class CircuitBreaker {
private failures = 0;
private lastFailure: number = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';

constructor(
private threshold: number = 5,
private timeout: number = 60000
) {}

async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailure > this.timeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open');
}
}

try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}

private onSuccess() {
this.failures = 0;
this.state = 'closed';
}

private onFailure() {
this.failures++;
this.lastFailure = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
}
}
}

デバッグのヒント

  1. デバッグログを有効にする:

    DEBUG=* npm run offline
  2. LambdaエラーについてはCloudWatchログを確認:

    aws logs tail /aws/lambda/your-function-name --follow
  3. トレーシングにはリクエストIDを使用:

    console.log('RequestId:', context.awsRequestId);
  4. 環境変数を確認:

    console.log('Config:', {
    table: process.env.DYNAMODB_TABLE_NAME,
    region: process.env.AWS_REGION,
    });
  5. serverless-offlineでローカルテスト:

    npm run offline -- --stage dev

関連ドキュメント