@@ -2,6 +2,7 @@ import { type Database } from '@unocha/hpc-api-core/src/db';
2
2
import { type FlowId } from '@unocha/hpc-api-core/src/db/models/flow' ;
3
3
import { Op } from '@unocha/hpc-api-core/src/db/util/conditions' ;
4
4
import { type InstanceOfModel } from '@unocha/hpc-api-core/src/db/util/types' ;
5
+ import { splitIntoChunks } from '@unocha/hpc-api-core/src/util' ;
5
6
import {
6
7
createBrandedValue ,
7
8
getTableColumns ,
@@ -322,28 +323,39 @@ export class FlowService {
322
323
const entityCondKey = orderBy . entity as unknown ;
323
324
const entityCondKeyFlowObjectType = entityCondKey as FlowObjectType ;
324
325
325
- flowObjects = await database . flowObject . find ( {
326
- where : {
327
- objectType : entityCondKeyFlowObjectType ,
328
- refDirection,
329
- objectID : {
330
- [ Op . IN ] : entityIDsSorted ,
331
- } ,
332
- } ,
333
- distinct : [ 'flowID' , 'versionID' ] ,
334
- } ) ;
326
+ // Order map
327
+ const orderMap = new Map < number , number > ( ) ;
328
+ for ( const [ index , entityID ] of entityIDsSorted . entries ( ) ) {
329
+ orderMap . set ( entityID , index ) ;
330
+ }
335
331
332
+ const chunks = splitIntoChunks ( entityIDsSorted , 1000 ) ;
333
+ // Instead of doing a single query that may end up on a 'Memory Error'
334
+ // we will do a progressive search
335
+ // by chunks of 1000
336
+ for ( const chunk of chunks ) {
337
+ const flowObjectsBatch = await database . flowObject . find ( {
338
+ where : {
339
+ objectType : entityCondKeyFlowObjectType ,
340
+ refDirection,
341
+ objectID : {
342
+ [ Op . IN ] : chunk ,
343
+ } ,
344
+ } ,
345
+ distinct : [ 'flowID' , 'versionID' ] ,
346
+ } ) ;
347
+ flowObjects . push ( ...flowObjectsBatch ) ;
348
+ }
336
349
// Then, we need to filter the results from the flowObject table
337
350
// using the planVersions list as sorted reference
338
351
// this is because we cannot apply the order of a given list
339
352
// to the query directly
340
353
flowObjects = flowObjects
341
354
. map ( ( flowObject ) => ( {
342
355
...flowObject ,
343
- sortingKey : entityIDsSorted . indexOf ( flowObject . objectID . valueOf ( ) ) ,
356
+ sortingKey : orderMap . get ( flowObject . objectID . valueOf ( ) ) ,
344
357
} ) )
345
- . sort ( ( a , b ) => a . sortingKey - b . sortingKey ) ;
346
-
358
+ . sort ( ( a , b ) => ( a . sortingKey ?? 0 ) - ( b . sortingKey ?? 0 ) ) ;
347
359
return this . mapFlowsToUniqueFlowEntities ( flowObjects ) ;
348
360
}
349
361
@@ -454,17 +466,18 @@ export class FlowService {
454
466
models : Database ,
455
467
flowObjectFilters : FlowObjectFilterGrouped
456
468
) : Promise < UniqueFlowEntity [ ] > {
469
+ // 1. Retrieve the parked category
457
470
const parkedCategory = await models . category . findOne ( {
458
471
where : {
459
472
name : 'Parked' ,
460
473
group : 'flowType' ,
461
474
} ,
462
475
} ) ;
463
-
464
476
if ( ! parkedCategory ) {
465
477
throw new Error ( 'Parked category not found' ) ;
466
478
}
467
479
480
+ // 2. Get all category references for parked flows
468
481
const categoryRefs = await models . categoryRef . find ( {
469
482
where : {
470
483
categoryID : parkedCategory . id ,
@@ -473,91 +486,88 @@ export class FlowService {
473
486
distinct : [ 'objectID' , 'versionID' ] ,
474
487
} ) ;
475
488
489
+ // Build list of parent IDs from categoryRefs
490
+ const parentIDs = categoryRefs . map ( ( ref ) =>
491
+ createBrandedValue ( ref . objectID )
492
+ ) ;
493
+
494
+ // 3. Retrieve flow links where the parent is among those references and depth > 0
476
495
const flowLinks = await models . flowLink . find ( {
477
496
where : {
478
- depth : {
479
- [ Op . GT ] : 0 ,
480
- } ,
481
- parentID : {
482
- [ Op . IN ] : categoryRefs . map ( ( categoryRef ) =>
483
- createBrandedValue ( categoryRef . objectID )
484
- ) ,
485
- } ,
497
+ depth : { [ Op . GT ] : 0 } ,
498
+ parentID : { [ Op . IN ] : parentIDs } ,
486
499
} ,
487
500
distinct : [ 'parentID' , 'childID' ] ,
488
501
} ) ;
489
502
503
+ // Create a reference list of parent flows from the flow links
490
504
const parentFlowsRef : UniqueFlowEntity [ ] = flowLinks . map ( ( flowLink ) => ( {
491
505
id : createBrandedValue ( flowLink . parentID ) ,
492
506
versionID : null ,
493
507
} ) ) ;
494
508
495
- // Since this list can be really large in size: ~42k flow links
496
- // This can cause a performance issue when querying the database
497
- // and even end up with a error like:
498
- // could not resize shared memory segment \"/PostgreSQL.2154039724\"
499
- // to 53727360 bytes: No space left on device
500
-
501
- // We need to do this query by chunks
509
+ // 4. Query parent flows progressively in chunks
502
510
const parentFlows = await this . progresiveSearch (
503
511
models ,
504
512
parentFlowsRef ,
505
513
1000 ,
506
514
0 ,
507
- false , // Stop on batch size
515
+ false , // Do not stop on batch size
508
516
[ ] ,
509
517
{ activeStatus : true }
510
518
) ;
511
519
520
+ // 5. Retrieve flow objects using the flow object filters
512
521
const flowObjectsWhere =
513
522
buildWhereConditionsForFlowObjectFilters ( flowObjectFilters ) ;
514
-
515
523
const flowObjects = await this . flowObjectService . getFlowFromFlowObjects (
516
524
models ,
517
525
flowObjectsWhere
518
526
) ;
519
527
520
- // Once we get the flowObjects - we need to keep only those that are present in both lists
521
- const filteredParentFlows = parentFlows . filter ( ( parentFlow ) =>
522
- flowObjects . some (
523
- ( flowObject ) =>
524
- flowObject . id === parentFlow . id &&
525
- flowObject . versionID === parentFlow . versionID
528
+ // 6. Build a Set for flowObjects for fast lookup\n (using a composite key of id and versionID)\n
529
+ const flowObjectsSet = new Set (
530
+ flowObjects . map (
531
+ ( flowObject ) => `${ flowObject . id } |${ flowObject . versionID } `
526
532
)
527
533
) ;
528
534
529
- // Once we have the ParentFlows whose status are 'parked'
530
- // We keep look for the flowLinks of those flows to obtain the child flows
531
- // that are linked to them
532
- const childFlowsIDs : FlowId [ ] = [ ] ;
535
+ // 7. Filter parent flows that are present in the flowObjects list
536
+ const filteredParentFlows = parentFlows . filter ( ( parentFlow ) => {
537
+ const key = `${ parentFlow . id } |${ parentFlow . versionID } ` ;
538
+ return flowObjectsSet . has ( key ) ;
539
+ } ) ;
540
+
541
+ // 8. Build a Set of filtered parent flow IDs for quick membership checking
542
+ const filteredParentFlowIds = new Set (
543
+ filteredParentFlows . map ( ( flow ) => flow . id )
544
+ ) ;
545
+
546
+ // 9. Extract child flow IDs from flowLinks where the parent is in the filtered set
547
+ const childFlowsIDsSet = new Set < FlowId > ( ) ;
533
548
for ( const flowLink of flowLinks ) {
534
- if (
535
- filteredParentFlows . some (
536
- ( parentFlow ) => parentFlow . id === flowLink . parentID
537
- )
538
- ) {
539
- childFlowsIDs . push ( flowLink . childID ) ;
549
+ if ( filteredParentFlowIds . has ( flowLink . parentID ) ) {
550
+ childFlowsIDsSet . add ( createBrandedValue ( flowLink . childID ) ) ;
540
551
}
541
552
}
542
553
554
+ // 10. Retrieve child flows
543
555
const childFlows = await models . flow . find ( {
544
556
where : {
545
557
deletedAt : null ,
546
558
activeStatus : true ,
547
- id : {
548
- [ Op . IN ] : childFlowsIDs ,
549
- } ,
559
+ id : { [ Op . IN ] : [ ...childFlowsIDsSet ] } ,
550
560
} ,
551
561
distinct : [ 'id' , 'versionID' ] ,
552
562
} ) ;
553
563
554
- // Once we have the child flows, we need to filter them
555
- // using the flowObjectFilters
556
- // This search needs to be also done by chunks
557
- return childFlows . map ( ( ref ) => ( {
564
+ // 11. Map child flows to UniqueFlowEntity and return the result
565
+ const result = childFlows . map ( ( ref ) => ( {
558
566
id : createBrandedValue ( ref . id ) ,
559
567
versionID : ref . versionID ,
560
568
} ) ) ;
569
+
570
+ return result ;
561
571
}
562
572
563
573
/**
0 commit comments