Queue concurrent uploads instead of rejecting with 503
Server: replace hard 503 rejection with a bounded queue (50 slots, 2min timeout) so requests wait for a processing slot instead of failing. Client: upload files sequentially instead of via Promise.all, and retry with exponential backoff on 503/504 responses.
This commit is contained in:
parent
4cbc3163de
commit
b52d6b1a6f
2 changed files with 66 additions and 21 deletions
|
|
@ -44,6 +44,31 @@ function tieredUploadLimiter(req: AuthenticatedRequest, res: Response, next: Nex
|
|||
|
||||
let activeUploads = 0;
|
||||
const MAX_CONCURRENT_UPLOADS = 5;
|
||||
const MAX_QUEUE_SIZE = 50;
|
||||
const QUEUE_TIMEOUT_MS = 120_000; // 2 minutes
|
||||
|
||||
const waitForSlot = (): Promise<void> => {
|
||||
if (activeUploads < MAX_CONCURRENT_UPLOADS) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
let wrappedResolve: () => void;
|
||||
const timeout = setTimeout(() => {
|
||||
const idx = uploadQueue.indexOf(wrappedResolve);
|
||||
if (idx !== -1) uploadQueue.splice(idx, 1);
|
||||
reject(new Error('Upload queue timeout'));
|
||||
}, QUEUE_TIMEOUT_MS);
|
||||
wrappedResolve = () => { clearTimeout(timeout); resolve(); };
|
||||
uploadQueue.push(wrappedResolve);
|
||||
});
|
||||
};
|
||||
|
||||
const uploadQueue: Array<() => void> = [];
|
||||
|
||||
const drainQueue = () => {
|
||||
while (uploadQueue.length > 0 && activeUploads < MAX_CONCURRENT_UPLOADS) {
|
||||
const next = uploadQueue.shift();
|
||||
if (next) next();
|
||||
}
|
||||
};
|
||||
|
||||
router.use(requireAuth);
|
||||
router.use(tieredUploadLimiter);
|
||||
|
|
@ -66,10 +91,16 @@ const upload = multer({
|
|||
});
|
||||
|
||||
router.post('/', upload.single('document'), async (req: AuthenticatedRequest, res) => {
|
||||
if (activeUploads >= MAX_CONCURRENT_UPLOADS) {
|
||||
if (activeUploads >= MAX_CONCURRENT_UPLOADS && uploadQueue.length >= MAX_QUEUE_SIZE) {
|
||||
return res.status(503).json({ error: 'Server busy processing uploads. Please try again shortly.' });
|
||||
}
|
||||
|
||||
try {
|
||||
await waitForSlot();
|
||||
} catch {
|
||||
return res.status(504).json({ error: 'Upload timed out waiting in queue. Please try again.' });
|
||||
}
|
||||
|
||||
activeUploads++;
|
||||
|
||||
try {
|
||||
|
|
@ -114,6 +145,7 @@ router.post('/', upload.single('document'), async (req: AuthenticatedRequest, re
|
|||
});
|
||||
} finally {
|
||||
activeUploads--;
|
||||
drainQueue();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue