@@ -19,9 +19,13 @@ package cmd
19
19
20
20
import (
21
21
"context"
22
+ "log"
22
23
"net/http"
24
+ "sort"
23
25
"strconv"
24
26
"strings"
27
+ "sync"
28
+ "time"
25
29
26
30
"github.com/gorilla/mux"
27
31
"github.com/minio/minio/internal/logger"
@@ -46,12 +50,91 @@ func concurrentDecryptETag(ctx context.Context, objects []ObjectInfo) {
46
50
g .Wait ()
47
51
}
48
52
53
+ func mergeListObjects (l1 , l2 []ObjectInfo ) []ObjectInfo {
54
+ mergedMap := make (map [string ]ObjectInfo )
55
+
56
+ // Helper function to add/update map entries
57
+ addOrUpdate := func (obj ObjectInfo ) {
58
+ if existingObj , found := mergedMap [obj .Name ]; ! found || obj .ModTime .After (existingObj .ModTime ) {
59
+ mergedMap [obj .Name ] = obj
60
+ }
61
+ }
62
+ for _ , obj := range l1 {
63
+ addOrUpdate (obj )
64
+ }
65
+ for _ , obj := range l2 {
66
+ addOrUpdate (obj )
67
+ }
68
+
69
+ mergedList := make ([]ObjectInfo , 0 , len (mergedMap ))
70
+ for _ , obj := range mergedMap {
71
+ mergedList = append (mergedList , obj )
72
+ }
73
+
74
+ return mergedList
75
+ }
76
+
77
+ func mergePrefixes (l1 , l2 []string ) []string {
78
+ mergedMap := make (map [string ]bool )
79
+
80
+ // Helper function to add/update map entries
81
+ addOrUpdate := func (pre string ) {
82
+ if _ , found := mergedMap [pre ]; ! found {
83
+ mergedMap [pre ] = true
84
+ }
85
+ }
86
+ for _ , pre := range l1 {
87
+ addOrUpdate (pre )
88
+ }
89
+ for _ , pre := range l2 {
90
+ addOrUpdate (pre )
91
+ }
92
+
93
+ mergedList := make ([]string , 0 , len (mergedMap ))
94
+ for pre , _ := range mergedMap {
95
+ mergedList = append (mergedList , pre )
96
+ }
97
+
98
+ return mergedList
99
+ }
100
+
101
+ func limitMergeObjects (mergeObjects []ObjectInfo , mergePrefixes []string , maxKeys int ) ([]ObjectInfo , []string , string ) {
102
+ objPrefixMap := map [string ]ObjectInfo {}
103
+ for _ , ob := range mergeObjects {
104
+ objPrefixMap [ob .Name ] = ob
105
+ }
106
+ for _ , pre := range mergePrefixes {
107
+ objPrefixMap [pre ] = ObjectInfo {IsDir : true }
108
+ }
109
+
110
+ keys := make ([]string , 0 , len (objPrefixMap ))
111
+ for key := range objPrefixMap {
112
+ keys = append (keys , key )
113
+ }
114
+ sort .Strings (keys )
115
+ limitedObjs := []ObjectInfo {}
116
+ limitedPrefixes := []string {}
117
+ nextMarker := ""
118
+ for i , key := range keys {
119
+ if objPrefixMap [key ].IsDir {
120
+ limitedPrefixes = append (limitedPrefixes , key )
121
+ } else {
122
+ limitedObjs = append (limitedObjs , objPrefixMap [key ])
123
+ }
124
+ if i >= (maxKeys - 1 ) {
125
+ nextMarker = key
126
+ break
127
+ }
128
+ }
129
+ return limitedObjs , limitedPrefixes , nextMarker
130
+ }
131
+
49
132
// Validate all the ListObjects query arguments, returns an APIErrorCode
50
133
// if one of the args do not meet the required conditions.
51
134
// Special conditions required by MinIO server are as below
52
- // - delimiter if set should be equal to '/', otherwise the request is rejected.
53
- // - marker if set should have a common prefix with 'prefix' param, otherwise
54
- // the request is rejected.
135
+ // - delimiter if set should be equal to '/', otherwise the request is rejected.
136
+ // - marker if set should have a common prefix with 'prefix' param, otherwise
137
+ // the request is rejected.
55
138
func validateListObjectsArgs (marker , delimiter , encodingType string , maxKeys int ) APIErrorCode {
56
139
// Max keys cannot be negative.
57
140
if maxKeys < 0 {
@@ -200,6 +283,11 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
200
283
// NOTE: It is recommended that this API to be used for application development.
201
284
// MinIO continues to support ListObjectsV1 for supporting legacy tools.
202
285
func (api objectAPIHandlers ) ListObjectsV2Handler (w http.ResponseWriter , r * http.Request ) {
286
+ st := time .Now ()
287
+ defer func () {
288
+ elapsed := time .Since (st ).Milliseconds ()
289
+ log .Printf ("ListObjectsV2Handler took %d ms\n " , elapsed )
290
+ }()
203
291
ctx := newContext (r , w , "ListObjectsV2" )
204
292
205
293
defer logger .AuditLog (ctx , w , r , mustGetClaimsFromToken (r ))
@@ -235,9 +323,17 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
235
323
}
236
324
237
325
var (
238
- listObjectsV2Info ListObjectsV2Info
239
- err error
326
+ listObjectsV2Info ListObjectsV2Info
327
+ listObjectsV2InfoCache ListObjectsV2Info
328
+ err error
329
+ errC error
240
330
)
331
+ listObjectsV2Cache := objectAPI .ListObjectsV2
332
+ cacheEnabled := false
333
+ if api .CacheAPI () != nil {
334
+ cacheEnabled = true
335
+ listObjectsV2Cache = api .CacheAPI ().ListObjectsV2
336
+ }
241
337
242
338
if r .Header .Get (xMinIOExtract ) == "true" && strings .Contains (prefix , archivePattern ) {
243
339
// Inititate a list objects operation inside a zip file based in the input params
@@ -246,13 +342,38 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
246
342
// Inititate a list objects operation based on the input params.
247
343
// On success would return back ListObjectsInfo object to be
248
344
// marshaled into S3 compatible XML header.
249
- listObjectsV2Info , err = objectAPI .ListObjectsV2 (ctx , bucket , prefix , token , delimiter , maxKeys , fetchOwner , startAfter )
345
+ var wg sync.WaitGroup
346
+ wg .Add (2 )
347
+ go func () {
348
+ defer wg .Done ()
349
+ listObjectsV2Info , err = objectAPI .ListObjectsV2 (ctx , bucket , prefix , token , delimiter , maxKeys , fetchOwner , startAfter )
350
+ }()
351
+ go func () {
352
+ defer wg .Done ()
353
+ if cacheEnabled {
354
+ stc := time .Now ()
355
+ listObjectsV2InfoCache , errC = listObjectsV2Cache (ctx , bucket , prefix , token , delimiter , maxKeys , fetchOwner , startAfter )
356
+ elap := time .Since (stc )
357
+ log .Println ("ListV2 object cache time" , elap )
358
+ }
359
+ }()
360
+ wg .Wait ()
250
361
}
251
- if err != nil {
362
+ if err != nil || errC != nil {
252
363
writeErrorResponse (ctx , w , toAPIError (ctx , err ), r .URL )
253
364
return
254
365
}
255
366
367
+ mergeObjects := mergeListObjects (listObjectsV2Info .Objects , listObjectsV2InfoCache .Objects )
368
+ mergePrefixes := mergePrefixes (listObjectsV2Info .Prefixes , listObjectsV2InfoCache .Prefixes )
369
+ limitedObjects , limitedPrefix , nextMarker := limitMergeObjects (mergeObjects , mergePrefixes , maxKeys )
370
+ listObjectsV2Info .Objects = limitedObjects
371
+ listObjectsV2Info .Prefixes = limitedPrefix
372
+ if nextMarker != "" {
373
+ listObjectsV2Info .NextContinuationToken = nextMarker
374
+ listObjectsV2Info .IsTruncated = true
375
+ }
376
+
256
377
concurrentDecryptETag (ctx , listObjectsV2Info .Objects )
257
378
258
379
response := generateListObjectsV2Response (bucket , prefix , token , listObjectsV2Info .NextContinuationToken , startAfter ,
@@ -306,8 +427,12 @@ func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http
306
427
// This implementation of the GET operation returns some or all (up to 1000)
307
428
// of the objects in a bucket. You can use the request parameters as selection
308
429
// criteria to return a subset of the objects in a bucket.
309
- //
310
430
func (api objectAPIHandlers ) ListObjectsV1Handler (w http.ResponseWriter , r * http.Request ) {
431
+ st := time .Now ()
432
+ defer func () {
433
+ elapsed := time .Since (st ).Milliseconds ()
434
+ log .Printf ("ListObjectsV1Handler took %d ms\n " , elapsed )
435
+ }()
311
436
ctx := newContext (r , w , "ListObjectsV1" )
312
437
313
438
defer logger .AuditLog (ctx , w , r , mustGetClaimsFromToken (r ))
@@ -340,15 +465,52 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
340
465
}
341
466
342
467
listObjects := objectAPI .ListObjects
343
-
468
+ listObjectsCache := objectAPI .ListObjects
469
+ cacheEnabled := false
470
+ if api .CacheAPI () != nil {
471
+ cacheEnabled = true
472
+ listObjectsCache = api .CacheAPI ().ListObjects
473
+ }
344
474
// Inititate a list objects operation based on the input params.
345
475
// On success would return back ListObjectsInfo object to be
346
476
// marshaled into S3 compatible XML header.
347
- listObjectsInfo , err := listObjects (ctx , bucket , prefix , marker , delimiter , maxKeys )
348
- if err != nil {
477
+ var (
478
+ listObjectsInfo ListObjectsInfo
479
+ listObjectsInfoCache ListObjectsInfo
480
+ err error
481
+ errC error
482
+ )
483
+ var wg sync.WaitGroup
484
+ wg .Add (2 )
485
+ go func () {
486
+ defer wg .Done ()
487
+ listObjectsInfo , err = listObjects (ctx , bucket , prefix , marker , delimiter , maxKeys )
488
+ }()
489
+ go func () {
490
+ defer wg .Done ()
491
+ if cacheEnabled {
492
+ stc := time .Now ()
493
+ listObjectsInfoCache , errC = listObjectsCache (ctx , bucket , prefix , marker , delimiter , maxKeys )
494
+ elap := time .Since (stc )
495
+ log .Println ("ListV1 object cache time" , elap )
496
+ }
497
+ }()
498
+
499
+ wg .Wait ()
500
+
501
+ if err != nil || errC != nil {
349
502
writeErrorResponse (ctx , w , toAPIError (ctx , err ), r .URL )
350
503
return
351
504
}
505
+ mergeObjects := mergeListObjects (listObjectsInfo .Objects , listObjectsInfoCache .Objects )
506
+ mergePrefixes := mergePrefixes (listObjectsInfo .Prefixes , listObjectsInfoCache .Prefixes )
507
+ limitedObjects , limitedPrefix , nextMarker := limitMergeObjects (mergeObjects , mergePrefixes , maxKeys )
508
+ listObjectsInfo .Objects = limitedObjects
509
+ listObjectsInfo .Prefixes = limitedPrefix
510
+ if nextMarker != "" {
511
+ listObjectsInfo .NextMarker = nextMarker
512
+ listObjectsInfo .IsTruncated = true
513
+ }
352
514
353
515
concurrentDecryptETag (ctx , listObjectsInfo .Objects )
354
516
0 commit comments