@@ -22,7 +22,8 @@ router.route('/').get(
2222 const config = getConfig ( request ) ;
2323 const isGlobal = config && config . global ;
2424 // Global stats may not be asked to the global API
25- if ( isGlobal ) return {
25+ const query = request . query . query ;
26+ if ( isGlobal && ! query ) return {
2627 headerError : BAD_REQUEST ,
2728 error : 'This is the global database and its stats may be missleading.'
2829 + ' In order to get the global stats all federated nodes must be asked.'
@@ -34,34 +35,61 @@ router.route('/').get(
3435 // Compute the real stored size of GridFS files for the queried projects
3536 // Without a query filter this would require aggregating all files in the DB,
3637 // so we skip it and fall back to the dbStats figure instead
37- const query = request . query . query ;
3838 let realDataSizeInTB ;
3939 if ( ! query ) {
4040 realDataSizeInTB = + ( dbStats . dataSize / 1e6 ) ;
4141 } else {
42- // This is expensive so we cache it with a TTL, keyed by query string
42+ // This is expensive so we cache it with a TTL (time-to-live) , keyed by query string
4343 if ( ! gridFsSizeCache . has ( query )
4444 || Date . now ( ) - gridFsSizeCache . get ( query ) . timestamp > CACHE_TTL_MS ) {
4545 const projectsFinder = database . getBaseFilter ( ) ;
4646 const processedQuery = await database . processProjectsQuery ( query ) ;
4747 if ( processedQuery . error ) return processedQuery ;
4848 if ( ! projectsFinder . $and ) projectsFinder . $and = processedQuery ;
4949 else projectsFinder . $and = projectsFinder . $and . concat ( processedQuery ) ;
50- // Get all file IDs, including those in MDs
51- const projectsCursor = await database . projects . find (
52- projectsFinder ,
53- { projection : { files : 1 , 'mds.files' : 1 } } ,
54- ) ;
55- const allProjects = await projectsCursor . toArray ( ) ;
56- // Flatten all file IDs
57- const fileIds = allProjects . flatMap ( project => [
58- ...( project . files ?? [ ] ) . map ( f => f . id ) ,
59- ...( project . mds ?? [ ] ) . flatMap ( md => ( md . files ?? [ ] ) . map ( f => f . id ) ) ,
60- ] ) ;
61- // Sum 'length' from fs.files for all those IDs
62- const sizeAgg = await database . db . collection ( 'fs.files' ) . aggregate ( [
63- { $match : { _id : { $in : fileIds } } } ,
64- { $group : { _id : null , totalBytes : { $sum : '$length' } } } ,
50+ // Single aggregation pipeline to do it all
51+ const sizeAgg = await database . projects . aggregate ( [
52+ // 1. Filter projects
53+ { $match : projectsFinder } ,
54+ // 2. Extract and flatten file IDs entirely on the DB side
55+ {
56+ $project : {
57+ fileIds : {
58+ $concatArrays : [
59+ { $ifNull : [ "$files.id" , [ ] ] } ,
60+ {
61+ $reduce : {
62+ input : { $ifNull : [ "$mds" , [ ] ] } ,
63+ initialValue : [ ] ,
64+ in : { $concatArrays : [ "$$value" , { $ifNull : [ "$$this.files.id" , [ ] ] } ] }
65+ }
66+ }
67+ ]
68+ }
69+ }
70+ } ,
71+ // 3. Flatten the arrays into individual document rows
72+ { $unwind : "$fileIds" } ,
73+ // 4. Deduplicate IDs across all projects to avoid double-counting shared files
74+ { $group : { _id : "$fileIds" } } ,
75+ // 5. Join with the fs.files collection using the indexed _id primary key
76+ {
77+ $lookup : {
78+ from : 'fs.files' ,
79+ localField : '_id' ,
80+ foreignField : '_id' ,
81+ as : 'gridFsFile'
82+ }
83+ } ,
84+ // 6. Unwind the joined file array (will be 1 item max per row)
85+ { $unwind : "$gridFsFile" } ,
86+ // 7. Sum up the lengths
87+ {
88+ $group : {
89+ _id : null ,
90+ totalBytes : { $sum : "$gridFsFile.length" }
91+ }
92+ }
6593 ] ) . toArray ( ) ;
6694 const totalBytes = sizeAgg . length > 0 ? sizeAgg [ 0 ] . totalBytes : 0 ;
6795 gridFsSizeCache . set ( query , { totalBytes, timestamp : Date . now ( ) } ) ;
0 commit comments