Renderの無料プランでは15分でスリープするため、AI書類作成やデータ分析などの長時間処理が途中で失敗してしまいます。非同期処理+ポーリングに変更することで、スリープ環境でも確実に処理を完了させる方法を解説します。
🚨 こんな問題で困っていませんか?
- AI書類作成が10分で途中失敗する
- データ処理や画像変換がタイムアウトで中断
- 長時間APIが15分スリープでエラーになる
- 同期処理だとユーザーが長時間待たされる
目次
問題:同期処理だと15分スリープでタスクが失敗する
Renderの制限と長時間処理の相性の悪さを理解しましょう。
Render

Deploy for Free
Spin up basic web services and datastores at no charge. Explore new tech, build personal projects, and preview Render's developer experience.
同期処理での問題パターン
| 処理時間 | 同期処理の結果 | ユーザー体験 |
|---|---|---|
| 5分以内 | ✅ 正常完了 | 待機時間が長いが成功 |
| 10-15分 | ❌ スリープでエラー | 作業が水の泡 |
| 15分超 | ❌ 確実に失敗 | サービス使用不可 |
従来の同期処理フロー
// ❌ 問題のある同期処理
app.post('/generate-document', async (req, res) => {
try {
// 10分以上かかるAI処理
const result = await aiService.generateDocument(req.body);
res.json({ success: true, document: result });
} catch (error) {
// 15分でスリープして、ここでエラーになる
res.status(500).json({ error: 'タイムアウト' });
}
});
長時間処理の典型例(AI書類作成、データ分析、画像処理など)
Renderでスリープの影響を受けやすい処理パターンを確認しましょう。
スリープ影響を受けやすい処理一覧
| 処理種別 | 典型的な処理時間 | 具体例 |
|---|---|---|
| AI文書生成 | 5-20分 | GPT-4での契約書作成、報告書生成 |
| データ分析 | 10-30分 | 大容量CSVの集計、機械学習モデル実行 |
| 画像・動画処理 | 2-15分 | 高解像度画像変換、動画エンコード |
| 外部API連携 | 5-60分 | 複数APIの順次実行、大量データ取得 |
| ファイル処理 | 3-20分 | PDF結合、Excelレポート生成 |
実例:AI書類作成での失敗ケース
- 処理開始:ユーザーが契約書生成をリクエスト
- 10分経過:AI処理が進行中、ユーザーは待機画面
- 15分経過:Renderがスリープ、処理が中断
- 結果:エラーページ表示、生成データも消失
解決策:非同期処理+ポーリングパターンの実装
長時間処理を非同期化し、ポーリングで進捗確認する仕組みを作ります。
非同期+ポーリングの仕組み
- 即座にジョブID返却:処理開始と同時にIDを発行
- バックグラウンド処理:サーバー側で非同期実行
- フロントエンドでポーリング:定期的に進捗確認
- 完了時に結果取得:処理完了後にデータ受信
メリット
| 従来(同期) | 改善後(非同期+ポーリング) |
|---|---|
| 15分でタイムアウト | ✅ スリープしても処理継続 |
| ユーザーが固定で待機 | ✅ 進捗表示でUX向上 |
| 失敗時は最初からやり直し | ✅ 途中から再開可能 |
| 複数ユーザーで処理できない | ✅ 並列処理で複数対応 |
バックエンド:Job Queue APIの作成方法
非同期処理を管理するためのAPI設計と実装方法です。
Express.jsでのJob Queue実装
// jobs.js - ジョブ管理
const jobs = new Map(); // 本番環境ではRedisやDBを使用
// ジョブ作成API
app.post('/api/jobs/document-generate', async (req, res) => {
const jobId = `job_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// ジョブ情報を保存
jobs.set(jobId, {
id: jobId,
status: 'pending', // pending, processing, completed, failed
progress: 0,
createdAt: new Date(),
result: null,
error: null
});
// バックグラウンドで非同期処理開始
processDocumentGeneration(jobId, req.body);
// 即座にジョブIDを返却
res.json({
jobId,
status: 'pending',
message: '処理を開始しました'
});
});
// ジョブ状況確認API
app.get('/api/jobs/:jobId', (req, res) => {
const job = jobs.get(req.params.jobId);
if (!job) {
return res.status(404).json({ error: 'ジョブが見つかりません' });
}
res.json(job);
});
// 非同期処理関数
async function processDocumentGeneration(jobId, params) {
try {
// ステータス更新
const job = jobs.get(jobId);
job.status = 'processing';
job.progress = 10;
// AI書類作成処理(長時間処理)
const result = await aiService.generateDocument(params);
// 完了時の更新
job.status = 'completed';
job.progress = 100;
job.result = result;
job.completedAt = new Date();
} catch (error) {
// エラー時の更新
const job = jobs.get(jobId);
job.status = 'failed';
job.error = error.message;
}
}
ジョブステータスの管理
| ステータス | 説明 | フロントエンド表示 |
|---|---|---|
pending | 処理待ち | 「処理を開始しています…」 |
processing | 実行中 | 「文書を生成中… 45%」 |
completed | 完了 | 「生成完了!ダウンロード可能」 |
failed | 失敗 | 「エラーが発生しました」 |
フロントエンド:React/Vueでのポーリング実装
定期的にジョブの進捗を確認し、ユーザーに状況を表示する実装です。
Reactでのポーリング実装
import React, { useState, useEffect } from 'react';
function DocumentGenerator() {
const [jobId, setJobId] = useState(null);
const [jobStatus, setJobStatus] = useState(null);
const [isProcessing, setIsProcessing] = useState(false);
// 文書生成開始
const startGeneration = async (formData) => {
setIsProcessing(true);
try {
const response = await fetch('/api/jobs/document-generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const data = await response.json();
setJobId(data.jobId);
} catch (error) {
console.error('ジョブ開始エラー:', error);
setIsProcessing(false);
}
};
// ポーリング処理
useEffect(() => {
if (!jobId || !isProcessing) return;
const pollJob = async () => {
try {
const response = await fetch(`/api/jobs/${jobId}`);
const job = await response.json();
setJobStatus(job);
// 完了または失敗時にポーリング停止
if (job.status === 'completed' || job.status === 'failed') {
setIsProcessing(false);
}
} catch (error) {
console.error('ポーリングエラー:', error);
}
};
// 3秒おきにポーリング実行
const interval = setInterval(pollJob, 3000);
// 初回実行
pollJob();
return () => clearInterval(interval);
}, [jobId, isProcessing]);
// 進捗表示コンポーネント
const renderProgress = () => {
if (!jobStatus) return null;
switch (jobStatus.status) {
case 'pending':
return 処理を開始しています...;
case 'processing':
return (
文書を生成中...
{jobStatus.progress}% 完了
);
case 'completed':
return (
✅ 生成完了!
);
case 'failed':
return (
❌ エラーが発生しました: {jobStatus.error}
);
default:
return null;
}
};
return (
AI文書生成
{!isProcessing ? (
) : (
renderProgress()
)}
);
}
Vue.jsでの実装例
// Vue.js 3 Composition API
import { ref, onUnmounted, watch } from 'vue';
export default {
setup() {
const jobId = ref(null);
const jobStatus = ref(null);
const isProcessing = ref(false);
let pollInterval = null;
const startPolling = () => {
pollInterval = setInterval(async () => {
if (!jobId.value) return;
try {
const response = await fetch(`/api/jobs/${jobId.value}`);
const job = await response.json();
jobStatus.value = job;
if (job.status === 'completed' || job.status === 'failed') {
stopPolling();
}
} catch (error) {
console.error('ポーリングエラー:', error);
}
}, 3000);
};
const stopPolling = () => {
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
isProcessing.value = false;
};
// ジョブIDが設定されたらポーリング開始
watch(jobId, (newJobId) => {
if (newJobId && isProcessing.value) {
startPolling();
}
});
// コンポーネント破棄時にクリーンアップ
onUnmounted(() => {
stopPolling();
});
return {
jobId,
jobStatus,
isProcessing,
startGeneration: async (formData) => {
isProcessing.value = true;
const response = await fetch('/api/jobs/document-generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const data = await response.json();
jobId.value = data.jobId;
}
};
}
};
エラーハンドリングとユーザビリティ向上
安定したサービス提供のための重要な考慮事項です。
堅牢なエラーハンドリング
| エラーケース | 対応方法 | ユーザー表示 |
|---|---|---|
| ネットワークエラー | 自動リトライ(3回まで) | 「接続を再試行中…」 |
| サーバーエラー | エラー詳細をログに記録 | 「一時的なエラーです」 |
| ジョブタイムアウト | 30分でタイムアウト設定 | 「処理時間が上限を超えました」 |
| 不正なジョブID | 404エラーを適切に処理 | 「処理が見つかりません」 |
UX向上のポイント
- 進捗の可視化:プログレスバーと%表示
- 予想時間表示:「あと約5分で完了予定」
- キャンセル機能:長時間処理の中断オプション
- 通知機能:完了時のメール通知やブラウザ通知
- 履歴管理:過去のジョブ結果を保存・再ダウンロード
改善された実装例
// 堅牢なポーリング実装
const useJobPolling = (jobId) => {
const [jobStatus, setJobStatus] = useState(null);
const [error, setError] = useState(null);
const retryCount = useRef(0);
useEffect(() => {
if (!jobId) return;
const pollWithRetry = async () => {
try {
const response = await fetch(`/api/jobs/${jobId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const job = await response.json();
setJobStatus(job);
setError(null);
retryCount.current = 0; // 成功時はリトライカウントリセット
return job.status === 'completed' || job.status === 'failed';
} catch (err) {
retryCount.current++;
if (retryCount.current >= 3) {
setError('接続エラーが継続しています。ページを更新してください。');
return true; // ポーリング停止
}
setError(`接続エラー (${retryCount.current}/3回目)`);
return false; // ポーリング継続
}
};
const interval = setInterval(async () => {
const shouldStop = await pollWithRetry();
if (shouldStop) {
clearInterval(interval);
}
}, 3000);
// 初回実行
pollWithRetry();
return () => clearInterval(interval);
}, [jobId]);
return { jobStatus, error };
};
設定完了!長時間処理も安心して実行
これでRenderのスリープ制限に影響されない、堅牢な長時間処理システムが完成しました。
🎉 解決できたこと
- スリープ耐性:15分制限を回避して処理継続
- UX向上:進捗表示でユーザー満足度アップ
- エラー削減:タイムアウトによる処理失敗を根絶
- 拡張性:複数ユーザーの同時処理に対応
応用可能な処理例
- AI・機械学習:文書生成、画像解析、自然言語処理
- データ処理:大容量CSV分析、レポート生成
- ファイル処理:動画エンコード、PDF変換
- 外部API連携:複数サービスとの連携処理
重要なリンク集
非同期処理+ポーリングパターンで、Renderの制限を活かしながら長時間処理を確実に完了させましょう。AI書類作成からデータ分析まで、あらゆる重い処理に応用できる汎用的なソリューションです!
