@@ -20,6 +20,7 @@ import type { ChunkingStrategy, StrategyOptions } from '@/lib/chunkers/types'
2020import { resolveTriggerRegion } from '@/lib/core/async-jobs/region'
2121import { env , envNumber } from '@/lib/core/config/env'
2222import { getCostMultiplier , isTriggerDevEnabled } from '@/lib/core/config/env-flags'
23+ import { mapWithConcurrency } from '@/lib/core/utils/concurrency'
2324import { processDocument } from '@/lib/knowledge/documents/document-processor'
2425import {
2526 buildTagFilterCondition ,
@@ -471,22 +472,27 @@ async function dispatchViaBatchTrigger(
471472 return dispatched
472473}
473474
475+ /** Each in-process job runs chunking + embedding + many DB inserts. */
476+ const IN_PROCESS_DISPATCH_CONCURRENCY = 5
477+
474478async function dispatchInProcess (
475479 jobPayloads : DocumentProcessingPayload [ ] ,
476480 requestId : string
477481) : Promise < number > {
478- const results = await Promise . allSettled (
479- jobPayloads . map ( ( p ) =>
480- processDocumentAsync ( p . knowledgeBaseId , p . documentId , p . docData , p . processingOptions )
481- )
482+ const results = await mapWithConcurrency (
483+ jobPayloads ,
484+ IN_PROCESS_DISPATCH_CONCURRENCY ,
485+ async ( p ) => {
486+ try {
487+ await processDocumentAsync ( p . knowledgeBaseId , p . documentId , p . docData , p . processingOptions )
488+ return true
489+ } catch ( error ) {
490+ logger . error ( `[${ requestId } ] Document dispatch failed` , { error : getErrorMessage ( error ) } )
491+ return false
492+ }
493+ }
482494 )
483- let dispatched = 0
484- for ( const r of results ) {
485- if ( r . status === 'fulfilled' ) dispatched ++
486- else
487- logger . error ( `[${ requestId } ] Document dispatch failed` , { error : getErrorMessage ( r . reason ) } )
488- }
489- return dispatched
495+ return results . filter ( Boolean ) . length
490496}
491497
492498export async function processDocumentAsync (
@@ -1844,6 +1850,9 @@ function getKnowledgeBaseStorageKey(fileUrl: string | null): string | null {
18441850 }
18451851}
18461852
1853+ /** Each entry deletes a storage object plus its metadata row. */
1854+ const STORAGE_DELETE_CONCURRENCY = 10
1855+
18471856export async function deleteDocumentStorageFiles (
18481857 documentsToDelete : Array < { id : string ; fileUrl : string | null ; workspaceId ?: string | null } > ,
18491858 requestId : string
@@ -1870,46 +1879,44 @@ export async function deleteDocumentStorageFiles(
18701879 }
18711880 }
18721881
1873- await Promise . allSettled (
1874- entries . map ( async ( { doc, storageKey } ) => {
1875- if ( ! storageKey ) {
1876- return
1877- }
1882+ await mapWithConcurrency ( entries , STORAGE_DELETE_CONCURRENCY , async ( { doc, storageKey } ) => {
1883+ if ( ! storageKey ) {
1884+ return
1885+ }
18781886
1879- // Only delete a kb/ object when its trusted ownership binding confirms the
1880- // deleting document's workspace owns it. Prevents deleting another tenant's
1881- // object via a document with a planted fileUrl.
1882- if ( storageKey . startsWith ( 'kb/' ) ) {
1883- const bindingWorkspaceId = ownerByKey . get ( storageKey )
1884- if ( ! bindingWorkspaceId ) {
1885- logger . warn ( `[${ requestId } ] Skipping storage delete: no ownership binding for key` , {
1886- documentId : doc . id ,
1887- storageKey,
1888- } )
1889- return
1890- }
1891- if ( ! doc . workspaceId || bindingWorkspaceId !== doc . workspaceId ) {
1892- logger . warn ( `[${ requestId } ] Skipping storage delete: ownership binding mismatch` , {
1893- documentId : doc . id ,
1894- storageKey,
1895- bindingWorkspaceId,
1896- documentWorkspaceId : doc . workspaceId ?? null ,
1897- } )
1898- return
1899- }
1887+ // Only delete a kb/ object when its trusted ownership binding confirms the
1888+ // deleting document's workspace owns it. Prevents deleting another tenant's
1889+ // object via a document with a planted fileUrl.
1890+ if ( storageKey . startsWith ( 'kb/' ) ) {
1891+ const bindingWorkspaceId = ownerByKey . get ( storageKey )
1892+ if ( ! bindingWorkspaceId ) {
1893+ logger . warn ( `[${ requestId } ] Skipping storage delete: no ownership binding for key` , {
1894+ documentId : doc . id ,
1895+ storageKey,
1896+ } )
1897+ return
19001898 }
1901-
1902- try {
1903- await deleteFile ( { key : storageKey , context : 'knowledge-base' } )
1904- await deleteFileMetadata ( storageKey )
1905- } catch ( error ) {
1906- logger . warn ( `[${ requestId } ] Failed to delete document storage file` , {
1899+ if ( ! doc . workspaceId || bindingWorkspaceId !== doc . workspaceId ) {
1900+ logger . warn ( `[${ requestId } ] Skipping storage delete: ownership binding mismatch` , {
19071901 documentId : doc . id ,
1908- error : toError ( error ) . message ,
1902+ storageKey,
1903+ bindingWorkspaceId,
1904+ documentWorkspaceId : doc . workspaceId ?? null ,
19091905 } )
1906+ return
19101907 }
1911- } )
1912- )
1908+ }
1909+
1910+ try {
1911+ await deleteFile ( { key : storageKey , context : 'knowledge-base' } )
1912+ await deleteFileMetadata ( storageKey )
1913+ } catch ( error ) {
1914+ logger . warn ( `[${ requestId } ] Failed to delete document storage file` , {
1915+ documentId : doc . id ,
1916+ error : toError ( error ) . message ,
1917+ } )
1918+ }
1919+ } )
19131920}
19141921
19151922async function excludeConnectorDocuments (
0 commit comments