🔥

ConvexのperformAsyncSyscallエラー完全解決ガイド

に公開

概要

ConvexのNode.js環境で発生するperformAsyncSyscallエラーの根本原因と解決方法について、実際の対応事例を基に詳しく解説します。このエラーは、ConvexのNode.js Action内でConvexランタイムAPIを呼び出す際に発生する、非常に根深い問題です。

問題の発生状況

エラーメッセージ

Error: performAsyncSyscall can only be called from within a Convex function

発生環境

  • Convex Node.js Action環境 ("use node"を使用)
  • AI処理関数内でのデータベース操作時
  • 並列処理でのランタイム境界問題

初期コード例

"use node";
export const processAudioFileAnalysis = internalAction({
  handler: async (ctx, args) => {
    // ❌ Node.js環境でConvexランタイムAPIを呼び出し
    await ctx.runMutation(internal.genAIResults.updateEvaluationResult, {
      transcriptionId: args.transcriptionId,
      result: evaluationResult,
      status: "completed",
    });
  },
});

根本原因の分析

Convexのランタイム境界

Convexには以下のランタイム境界があります:

  1. V8 Isolate環境 - 通常のQuery/Mutation
  2. Node.js環境 - "use node"を使用したAction
  3. ランタイムAPI - ctx.runQuery, ctx.runMutation, ctx.runAction

performAsyncSyscallの役割

performAsyncSyscallは、ユーザー関数とConvexサーバー間の内部APIブリッジです。Node.js環境からConvexランタイムAPIを呼び出す際に、ランタイム境界を越えることで発生するエラーです。

解決過程(試行錯誤の記録)

第1段階:ctx.runQueryの削除

// ❌ 失敗:まだperformAsyncSyscallエラーが発生
// ctx.runQueryを削除してパラメータ直接渡し
const transcriptText = args.transcriptionData.text; // パラメータで直接渡す

第2段階:ctx.runActionの削除

// ❌ 失敗:まだperformAsyncSyscallエラーが発生
// 並列Action呼び出しを内部ヘルパー関数に変更
const evaluationResult = await runEvaluationHelper(transcriptText);

第3段階:ctx.runMutationの削除

// ✅ 成功:performAsyncSyscallエラーが解消
// Node.js環境からすべてのConvexランタイムAPIを削除
export const processAudioFileAnalysis = internalAction({
  handler: async (ctx, args) => {
    // AI処理のみ実行、結果を返却
    const evaluationResult = await runEvaluationHelper(transcriptText);
    const meetingMinutesResult = await runMeetingMinutesHelper(transcriptText);
    
    // ❌ ctx.runMutation呼び出しを削除
    // ✅ 結果のみ返却
    return {
      success: true,
      results: {
        evaluation: evaluationResult,
        meetingMinutes: meetingMinutesResult,
      },
    };
  },
});

最終的な解決策

アーキテクチャの分離

Before (問題のある構造)

processAudioFileAnalysis (Node.js環境)
├── AI処理
└── ctx.runMutation ❌ performAsyncSyscallエラー

After (解決後の構造)

processWithSpeakerTranscription (Action環境)
├── processAudioFileAnalysis (Node.js環境)
│   ├── AI処理のみ
│   └── 結果を返却 ✅
└── ctx.runMutation (Action環境) ✅

実装コード

Node.js環境:AI処理専用

"use node";
export const processAudioFileAnalysis = internalAction({
  handler: async (ctx, args) => {
    // ✅ AI処理のみ実行
    const evaluationResult = await runEvaluationHelper(transcriptText);
    const meetingMinutesResult = await runMeetingMinutesHelper(transcriptText);
    
    // ✅ 結果のみ返却(データベース操作なし)
    return {
      success: true,
      results: {
        evaluation: evaluationResult,
        meetingMinutes: meetingMinutesResult,
      },
    };
  },
});

Action環境:データベース操作

export const processWithSpeakerTranscription = internalAction({
  handler: async (ctx, args) => {
    // ✅ Node.js環境のAI処理を呼び出し
    const analysisResult = await ctx.runAction(
      internal.googleGenAIActions.processAudioFileAnalysis,
      { transcriptionId, transcriptionData }
    );
    
    // ✅ Action環境でデータベース保存
    if (analysisResult?.success && analysisResult.results) {
      await ctx.runMutation(internal.genAIResults.updateEvaluationResult, {
        transcriptionId,
        result: analysisResult.results.evaluation.result,
        status: "completed",
      });
    }
  },
});

重要な学習ポイント

1. ランタイム境界の理解

  • Node.js環境内ではConvexランタイムAPIを使用してはいけない
  • ctx.runQuery, ctx.runMutation, ctx.runActionは通常のAction環境でのみ使用

2. 責任の分離

  • Node.js環境: 外部API呼び出し、AI処理など
  • Action環境: Convexデータベース操作、ランタイムAPI呼び出し

3. エラーの段階的解決

performAsyncSyscallエラーは、以下の順序で発生源を特定:

  1. ctx.runQuery → パラメータ直接渡しで解決
  2. ctx.runAction → ヘルパー関数で解決
  3. ctx.runMutation → 呼び出し元移動で解決

今後の対策

設計原則

// ✅ 良い設計
"use node";
export const nodeJsAction = internalAction({
  handler: async (ctx, args) => {
    // Node.js環境では外部処理のみ
    const aiResult = await callExternalAI(args.text);
    return { result: aiResult }; // 結果のみ返却
  },
});

// 呼び出し元でデータベース操作
export const callerAction = internalAction({
  handler: async (ctx, args) => {
    const result = await ctx.runAction(internal.nodeJsAction, args);
    await ctx.runMutation(internal.saveResult, result); // ✅
  },
});

チェックポイント

  • Node.js環境でctx.runQuery/runMutation/runActionを使用していないか
  • AI処理とデータベース操作が適切に分離されているか
  • 並列処理でランタイム境界を越えていないか

まとめ

performAsyncSyscallエラーは、ConvexのNode.js環境とランタイムAPI間の境界問題が原因です。AI処理とデータベース操作の完全分離により根本解決が可能です。このアーキテクチャにより、Convexの制約に準拠しながら複雑なAI処理システムを構築できます。

Discussion