Repository: dragonflyoss/Dragonfly2
Pull request #1494 (2022-07-27 14:43:37)

feat: auto switch to concurrent back source based on download speed
Signed-off-by: greenhandatsjtu <sunhengke@sjtu.edu.cn>

<!--- Provide a general summary of your changes in the Title above -->

## Description
If the download speed of back source is slower than the threshold, dfdaemon wil automatically switch from single thread download to concurrent download.
Related PR: https://github.com/dragonflyoss/Dragonfly2/pull/1426

<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.



Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..68e24a4b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+- package-ecosystem: "gomod"
+  directory: "/"
+  schedule:
+    interval: "weekly"
diff --git a/.github/workflows/check-size.yml b/.github/workflows/check-size.yml
index b917046b..71b56fe4 100644
--- a/.github/workflows/check-size.yml
+++ b/.github/workflows/check-size.yml
@@ -26,3 +26,6 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
           filesizelimit: "524288"
+          exclusionPatterns: |
+            deploy/helm-charts/*
+            manager/console/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 056de04f..172c239b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,150 @@
 <a name="unreleased"></a>
 ## [Unreleased]
 
+### Feat
+- scheduler adds filter range limit ([#1497](https://github.com/dragonflyoss/Dragonfly2/issues/1497))
+
+### Fix
+- random vertices ([#1496](https://github.com/dragonflyoss/Dragonfly2/issues/1496))
+
+
+<a name="v2.0.5-rc.0"></a>
+## [v2.0.5-rc.0] - 2022-07-27
+### Feat
+- scheduler set workhome ([#1493](https://github.com/dragonflyoss/Dragonfly2/issues/1493))
+
+### Fix
+- dfstore command-line tool name ([#1492](https://github.com/dragonflyoss/Dragonfly2/issues/1492))
+
+
+<a name="v2.0.5-beta.5"></a>
+## [v2.0.5-beta.5] - 2022-07-26
+### Fix
+- oss client judge directory bug ([#1488](https://github.com/dragonflyoss/Dragonfly2/issues/1488))
+- dfdaemon unix socket ([#1489](https://github.com/dragonflyoss/Dragonfly2/issues/1489))
+
+### Refactor
+- set and dag with generics ([#1490](https://github.com/dragonflyoss/Dragonfly2/issues/1490))
+
+
+<a name="v2.0.5-beta.4"></a>
+## [v2.0.5-beta.4] - 2022-07-25
+### Feat
+- remove test print
+
+### Fix
+- init storage error ([#1486](https://github.com/dragonflyoss/Dragonfly2/issues/1486))
+- back source error ([#1485](https://github.com/dragonflyoss/Dragonfly2/issues/1485))
+- keepalive with ip
+
+### Refactor
+- cache key for peer ([#1483](https://github.com/dragonflyoss/Dragonfly2/issues/1483))
+- scheduler training configuration
+- dag GetSourceVertices and GetSinkVertices func
+
+### Test
+- find parent with ancestor ([#1482](https://github.com/dragonflyoss/Dragonfly2/issues/1482))
+
+
+<a name="v2.0.5-beta.3"></a>
+## [v2.0.5-beta.3] - 2022-07-22
+### Feat
+- rename steal peers to candidate peers ([#1476](https://github.com/dragonflyoss/Dragonfly2/issues/1476))
+- scheduler merge end of piece and piece from seed peer ([#1474](https://github.com/dragonflyoss/Dragonfly2/issues/1474))
+
+
+<a name="v2.0.5-beta.2"></a>
+## [v2.0.5-beta.2] - 2022-07-21
+### Feat
+- dfdaemon add default healthy config ([#1472](https://github.com/dragonflyoss/Dragonfly2/issues/1472))
+- dag adds LenVertex and RangeVertex func ([#1470](https://github.com/dragonflyoss/Dragonfly2/issues/1470))
+
+### Fix
+- upload_manager write header in time ([#1471](https://github.com/dragonflyoss/Dragonfly2/issues/1471))
+- infinite loop in peer.Ancestors() ([#1469](https://github.com/dragonflyoss/Dragonfly2/issues/1469))
+
+
+<a name="v2.0.5-beta.1"></a>
+## [v2.0.5-beta.1] - 2022-07-18
+### Feat
+- generate dag mock
+- add directed acyclic graph package ([#1468](https://github.com/dragonflyoss/Dragonfly2/issues/1468))
+
+### Fix
+- upload_manager write header immediately when it is ready ([#1466](https://github.com/dragonflyoss/Dragonfly2/issues/1466))
+
+
+<a name="v2.0.5-beta.0"></a>
+## [v2.0.5-beta.0] - 2022-07-14
+### Feat
+- proxy add defaultTag field ([#1462](https://github.com/dragonflyoss/Dragonfly2/issues/1462))
+- manager support postgres ([#1459](https://github.com/dragonflyoss/Dragonfly2/issues/1459))
+- use os.PathSeparator to generate object key
+- scheduler add data dir ([#1453](https://github.com/dragonflyoss/Dragonfly2/issues/1453))
+
+### Fix
+- metrics reduces labels ([#1457](https://github.com/dragonflyoss/Dragonfly2/issues/1457))
+
+
+<a name="v2.0.5-alpha.3"></a>
+## [v2.0.5-alpha.3] - 2022-07-12
+### Chore
+- check header length before update ([#1445](https://github.com/dragonflyoss/Dragonfly2/issues/1445))
+
+### Feat
+- dfdaemon is compatible with v2.0.2 ([#1452](https://github.com/dragonflyoss/Dragonfly2/issues/1452))
+- add slices util package
+- reload proxy option ([#1443](https://github.com/dragonflyoss/Dragonfly2/issues/1443))
+- if peer back-to-source failed, return source metadata. ([#1444](https://github.com/dragonflyoss/Dragonfly2/issues/1444))
+- report peer result with source error detail ([#1439](https://github.com/dragonflyoss/Dragonfly2/issues/1439))
+
+### Fix
+- depth limit ([#1451](https://github.com/dragonflyoss/Dragonfly2/issues/1451))
+- dfpath creates redundant directories ([#1446](https://github.com/dragonflyoss/Dragonfly2/issues/1446))
+
+### Refactor
+- rewrite math max and min with generics ([#1447](https://github.com/dragonflyoss/Dragonfly2/issues/1447))
+
+
+<a name="v2.0.5-alpha.2"></a>
+## [v2.0.5-alpha.2] - 2022-07-07
+### Chore
+- update test/tools/no-content-length/main.go ([#1440](https://github.com/dragonflyoss/Dragonfly2/issues/1440))
+
+### Fix
+- release package name ([#1442](https://github.com/dragonflyoss/Dragonfly2/issues/1442))
+
+
+<a name="v2.0.5-alpha.1"></a>
+## [v2.0.5-alpha.1] - 2022-07-07
+### Feat
+- add dfstore command ([#1441](https://github.com/dragonflyoss/Dragonfly2/issues/1441))
+- back source error detail ([#1437](https://github.com/dragonflyoss/Dragonfly2/issues/1437))
+- change local cache ttl ([#1436](https://github.com/dragonflyoss/Dragonfly2/issues/1436))
+- if service can not found fqdn, replace fqdn with hostname ([#1435](https://github.com/dragonflyoss/Dragonfly2/issues/1435))
+
+
+<a name="v2.0.5-alpha.0"></a>
+## [v2.0.5-alpha.0] - 2022-07-05
+### Chore
+- upgrade kind node version ([#1433](https://github.com/dragonflyoss/Dragonfly2/issues/1433))
+- update docker compose ([#1431](https://github.com/dragonflyoss/Dragonfly2/issues/1431))
+- exit when receive context done ([#1432](https://github.com/dragonflyoss/Dragonfly2/issues/1432))
+- update codeql version ([#1428](https://github.com/dragonflyoss/Dragonfly2/issues/1428))
+
+### Feat
+- remove errors package ([#1434](https://github.com/dragonflyoss/Dragonfly2/issues/1434))
+- concurrent multiple pieces back source ([#1426](https://github.com/dragonflyoss/Dragonfly2/issues/1426))
+
+### Fix
+- seed task metric panic ([#1427](https://github.com/dragonflyoss/Dragonfly2/issues/1427))
+
+
+<a name="v2.0.4"></a>
+## [v2.0.4] - 2022-07-01
+### Chore
+- release v2.0.4 ([#1425](https://github.com/dragonflyoss/Dragonfly2/issues/1425))
+
 
 <a name="v2.0.4-rc.3"></a>
 ## [v2.0.4-rc.3] - 2022-06-30
@@ -921,11 +1065,11 @@
 - when cdn download failed, scheduler should set cdn peer state PeerStateFailed ([#1067](https://github.com/dragonflyoss/Dragonfly2/issues/1067))
 
 
-<a name="v2.0.2-rc.5"></a>
-## [v2.0.2-rc.5] - 2022-02-14
-
 <a name="v2.0.2-rc.6"></a>
 ## [v2.0.2-rc.6] - 2022-02-14
+
+<a name="v2.0.2-rc.5"></a>
+## [v2.0.2-rc.5] - 2022-02-14
 ### Chore
 - enable range feature gate in e2e ([#1059](https://github.com/dragonflyoss/Dragonfly2/issues/1059))
 - add content length for fast stream peer task ([#1061](https://github.com/dragonflyoss/Dragonfly2/issues/1061))
@@ -1854,7 +1998,19 @@
 - idgen add digest ([#243](https://github.com/dragonflyoss/Dragonfly2/issues/243))
 
 
-[Unreleased]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.4-rc.3...HEAD
+[Unreleased]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-rc.0...HEAD
+[v2.0.5-rc.0]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-beta.5...v2.0.5-rc.0
+[v2.0.5-beta.5]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-beta.4...v2.0.5-beta.5
+[v2.0.5-beta.4]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-beta.3...v2.0.5-beta.4
+[v2.0.5-beta.3]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-beta.2...v2.0.5-beta.3
+[v2.0.5-beta.2]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-beta.1...v2.0.5-beta.2
+[v2.0.5-beta.1]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-beta.0...v2.0.5-beta.1
+[v2.0.5-beta.0]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-alpha.3...v2.0.5-beta.0
+[v2.0.5-alpha.3]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-alpha.2...v2.0.5-alpha.3
+[v2.0.5-alpha.2]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-alpha.1...v2.0.5-alpha.2
+[v2.0.5-alpha.1]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.5-alpha.0...v2.0.5-alpha.1
+[v2.0.5-alpha.0]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.4...v2.0.5-alpha.0
+[v2.0.4]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.4-rc.3...v2.0.4
 [v2.0.4-rc.3]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.4-rc.1...v2.0.4-rc.3
 [v2.0.4-rc.1]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.4-rc.2...v2.0.4-rc.1
 [v2.0.4-rc.2]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.4-rc.0...v2.0.4-rc.2
@@ -1899,9 +2055,9 @@
 [v2.0.2-rc.10]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.9...v2.0.2-rc.10
 [v2.0.2-rc.9]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.8...v2.0.2-rc.9
 [v2.0.2-rc.8]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.7...v2.0.2-rc.8
-[v2.0.2-rc.7]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.5...v2.0.2-rc.7
-[v2.0.2-rc.5]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.6...v2.0.2-rc.5
-[v2.0.2-rc.6]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.4...v2.0.2-rc.6
+[v2.0.2-rc.7]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.6...v2.0.2-rc.7
+[v2.0.2-rc.6]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.5...v2.0.2-rc.6
+[v2.0.2-rc.5]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.4...v2.0.2-rc.5
 [v2.0.2-rc.4]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.3...v2.0.2-rc.4
 [v2.0.2-rc.3]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.2...v2.0.2-rc.3
 [v2.0.2-rc.2]: https://github.com/dragonflyoss/Dragonfly2/compare/v2.0.2-rc.1...v2.0.2-rc.2
diff --git a/Makefile b/Makefile
index 7d857bf9..c4ad9b14 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ PROJECT_NAME := "d7y.io/dragonfly/v2"
 DFGET_NAME := "dfget"
 DFCACHE_NAME := "dfcache"
 DFSTORE_NAME := "dfstore"
-SEMVER := "2.0.4"
+SEMVER := "2.0.5"
 VERSION_RELEASE := "1"
 PKG := "$(PROJECT_NAME)"
 PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/ | grep -v '\(/test/\)')
diff --git a/api/manager/docs.go b/api/manager/docs.go
index f5cd7c98..4af62951 100644
--- a/api/manager/docs.go
+++ b/api/manager/docs.go
@@ -1,4 +1,4 @@
-// Package manager GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
+// Package manager GENERATED BY SWAG; DO NOT EDIT
 // This file was generated by swaggo/swag
 package manager
 
@@ -3761,7 +3761,9 @@ const docTemplate = `{
         },
         "model.JSONMap": {
             "type": "object",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "any"
+            }
         },
         "model.Job": {
             "type": "object",
@@ -4237,14 +4239,18 @@ const docTemplate = `{
             "properties": {
                 "args": {
                     "type": "object",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "any"
+                    }
                 },
                 "bio": {
                     "type": "string"
                 },
                 "result": {
                     "type": "object",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "any"
+                    }
                 },
                 "scheduler_cluster_ids": {
                     "type": "array",
@@ -4599,6 +4605,11 @@ const docTemplate = `{
                     "type": "integer",
                     "maximum": 100,
                     "minimum": 1
+                },
+                "filter_parent_range_limit": {
+                    "type": "integer",
+                    "maximum": 1000,
+                    "minimum": 1
                 }
             }
         },
diff --git a/api/manager/swagger.json b/api/manager/swagger.json
index 7e303913..e86f90ca 100644
--- a/api/manager/swagger.json
+++ b/api/manager/swagger.json
@@ -3754,7 +3754,9 @@
         },
         "model.JSONMap": {
             "type": "object",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "any"
+            }
         },
         "model.Job": {
             "type": "object",
@@ -4230,14 +4232,18 @@
             "properties": {
                 "args": {
                     "type": "object",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "any"
+                    }
                 },
                 "bio": {
                     "type": "string"
                 },
                 "result": {
                     "type": "object",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "any"
+                    }
                 },
                 "scheduler_cluster_ids": {
                     "type": "array",
@@ -4592,6 +4598,11 @@
                     "type": "integer",
                     "maximum": 100,
                     "minimum": 1
+                },
+                "filter_parent_range_limit": {
+                    "type": "integer",
+                    "maximum": 1000,
+                    "minimum": 1
                 }
             }
         },
diff --git a/api/manager/swagger.yaml b/api/manager/swagger.yaml
index 4716cd5e..4cd21011 100644
--- a/api/manager/swagger.yaml
+++ b/api/manager/swagger.yaml
@@ -49,7 +49,8 @@ definitions:
         type: string
     type: object
   model.JSONMap:
-    additionalProperties: true
+    additionalProperties:
+      type: any
     type: object
   model.Job:
     properties:
@@ -363,12 +364,14 @@ definitions:
   types.CreateJobRequest:
     properties:
       args:
-        additionalProperties: true
+        additionalProperties:
+          type: any
         type: object
       bio:
         type: string
       result:
-        additionalProperties: true
+        additionalProperties:
+          type: any
         type: object
       scheduler_cluster_ids:
         items:
@@ -614,6 +617,10 @@ definitions:
         maximum: 100
         minimum: 1
         type: integer
+      filter_parent_range_limit:
+        maximum: 1000
+        minimum: 1
+        type: integer
     type: object
   types.SchedulerClusterScopes:
     properties:
diff --git a/build/package/docs/dfcache/dfcache-delete.1 b/build/package/docs/dfcache/dfcache-delete.1
index 321acb1b..ac983989 100644
--- a/build/package/docs/dfcache/dfcache-delete.1
+++ b/build/package/docs/dfcache/dfcache-delete.1
@@ -1,27 +1,20 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache delete
+.SH NAME
 .PP
-delete file from P2P cache system
-.SS Synopsis
+\f[B]dfcache delete\f[R] \[em] delete file from P2P cache system
+.SH SYNOPSIS
 .PP
-delete file from P2P cache system
+Delete file from P2P cache system.
 .IP
 .nf
 \f[C]
 dfcache delete <-i cid> [flags]
 \f[R]
 .fi
-.SS Options
-.IP
-.nf
-\f[C]
-  -h, --help   help for delete
-\f[R]
-.fi
-.SS Options inherited from parent commands
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -37,8 +30,9 @@ dfcache delete <-i cid> [flags]
       --timeout duration      Timeout for this cache operation, 0 is infinite
       --verbose               whether logger use debug level
       --workhome string       Dfcache working directory
+  -h, --help   help for delete
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache - the P2P cache client of dragonfly
diff --git a/build/package/docs/dfcache/dfcache-doc.1 b/build/package/docs/dfcache/dfcache-doc.1
index 1fd7a378..5f195e6e 100644
--- a/build/package/docs/dfcache/dfcache-doc.1
+++ b/build/package/docs/dfcache/dfcache-doc.1
@@ -1,28 +1,20 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache doc
+.SH NAME
 .PP
-generate documents
-.SS Synopsis
+\f[B]dfcache doc\f[R] \[em] generate documents
+.SH SYNOPSIS
 .PP
-generate markdown documents for cmd: dfcache .
+Generate markdown documents for cmd: dfcache.
 .IP
 .nf
 \f[C]
 dfcache doc [flags]
 \f[R]
 .fi
-.SS Options
-.IP
-.nf
-\f[C]
-  -h, --help          help for doc
-      --path string   destination dir of generated markdown documents (default \[dq]./\[dq])
-\f[R]
-.fi
-.SS Options inherited from parent commands
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -38,8 +30,10 @@ dfcache doc [flags]
       --timeout duration      Timeout for this cache operation, 0 is infinite
       --verbose               whether logger use debug level
       --workhome string       Dfcache working directory
+  -h, --help          help for doc
+      --path string   destination dir of generated markdown documents (default \[dq]./\[dq])
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache - the P2P cache client of dragonfly
diff --git a/build/package/docs/dfcache/dfcache-export.1 b/build/package/docs/dfcache/dfcache-export.1
index 85a4a413..b6e5128c 100644
--- a/build/package/docs/dfcache/dfcache-export.1
+++ b/build/package/docs/dfcache/dfcache-export.1
@@ -1,29 +1,20 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache export
+.SH NAME
 .PP
-export file from P2P cache system
-.SS Synopsis
+\f[B]dfcache export\f[R] \[em] export file from P2P cache system
+.SH SYNOPSIS
 .PP
-export file from P2P cache system
+Export file from P2P cache system.
 .IP
 .nf
 \f[C]
 dfcache export <-i cid> <output>|<-O output> [flags]
 \f[R]
 .fi
-.SS Options
-.IP
-.nf
-\f[C]
-  -h, --help            help for export
-  -l, --local           only export file from local cache
-  -O, --output string   export file path
-\f[R]
-.fi
-.SS Options inherited from parent commands
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -39,8 +30,11 @@ dfcache export <-i cid> <output>|<-O output> [flags]
       --timeout duration      Timeout for this cache operation, 0 is infinite
       --verbose               whether logger use debug level
       --workhome string       Dfcache working directory
+  -h, --help            help for export
+  -l, --local           only export file from local cache
+  -O, --output string   export file path
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache - the P2P cache client of dragonfly
diff --git a/build/package/docs/dfcache/dfcache-import.1 b/build/package/docs/dfcache/dfcache-import.1
index face4950..e9b46597 100644
--- a/build/package/docs/dfcache/dfcache-import.1
+++ b/build/package/docs/dfcache/dfcache-import.1
@@ -1,28 +1,20 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache import
+.SH NAME
 .PP
-import file into P2P cache system
-.SS Synopsis
+\f[B]dfcache import\f[R] \[em] import file into P2P cache system
+.SH SYNOPSIS
 .PP
-import file into P2P cache system
+Import file into P2P cache system.
 .IP
 .nf
 \f[C]
 dfcache import <-i cid> <file>|<-I file> [flags]
 \f[R]
 .fi
-.SS Options
-.IP
-.nf
-\f[C]
-  -h, --help           help for import
-  -I, --input string   import the given file into P2P network
-\f[R]
-.fi
-.SS Options inherited from parent commands
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -38,8 +30,10 @@ dfcache import <-i cid> <file>|<-I file> [flags]
       --timeout duration      Timeout for this cache operation, 0 is infinite
       --verbose               whether logger use debug level
       --workhome string       Dfcache working directory
+  -h, --help           help for import
+  -I, --input string   import the given file into P2P network
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache - the P2P cache client of dragonfly
diff --git a/build/package/docs/dfcache/dfcache-plugin.1 b/build/package/docs/dfcache/dfcache-plugin.1
index 506330e3..9760a590 100644
--- a/build/package/docs/dfcache/dfcache-plugin.1
+++ b/build/package/docs/dfcache/dfcache-plugin.1
@@ -1,27 +1,20 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache plugin
+.SH NAME
 .PP
-show plugin
-.SS Synopsis
+\f[B]dfcache plugin\f[R] \[em] show plugin
+.SH SYNOPSIS
 .PP
-show the plugin details of dragonfly.
+Show the plugin details of dragonfly.
 .IP
 .nf
 \f[C]
 dfcache plugin [flags]
 \f[R]
 .fi
-.SS Options
-.IP
-.nf
-\f[C]
-  -h, --help   help for plugin
-\f[R]
-.fi
-.SS Options inherited from parent commands
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -37,8 +30,9 @@ dfcache plugin [flags]
       --timeout duration      Timeout for this cache operation, 0 is infinite
       --verbose               whether logger use debug level
       --workhome string       Dfcache working directory
+  -h, --help   help for plugin
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache - the P2P cache client of dragonfly
diff --git a/build/package/docs/dfcache/dfcache-stat.1 b/build/package/docs/dfcache/dfcache-stat.1
index f4eb59c2..057d12dc 100644
--- a/build/package/docs/dfcache/dfcache-stat.1
+++ b/build/package/docs/dfcache/dfcache-stat.1
@@ -1,28 +1,21 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache stat
+.SH NAME
 .PP
-stat checks if a file exists in P2P cache system
-.SS Synopsis
+\f[B]dfcache stat\f[R] \[em] stat checks if a file exists in P2P cache
+system
+.SH SYNOPSIS
 .PP
-stat checks if a file exists in P2P cache system
+Stat checks if a file exists in P2P cache system.
 .IP
 .nf
 \f[C]
 dfcache stat <-i cid> [flags]
 \f[R]
 .fi
-.SS Options
-.IP
-.nf
-\f[C]
-  -h, --help    help for stat
-  -l, --local   only check task exists locally, and don\[aq]t check other peers in P2P network
-\f[R]
-.fi
-.SS Options inherited from parent commands
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -38,8 +31,10 @@ dfcache stat <-i cid> [flags]
       --timeout duration      Timeout for this cache operation, 0 is infinite
       --verbose               whether logger use debug level
       --workhome string       Dfcache working directory
+  -h, --help    help for stat
+  -l, --local   only check task exists locally, and don\[aq]t check other peers in P2P network
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache - the P2P cache client of dragonfly
diff --git a/build/package/docs/dfcache/dfcache-version.1 b/build/package/docs/dfcache/dfcache-version.1
index 2c217a82..96484886 100644
--- a/build/package/docs/dfcache/dfcache-version.1
+++ b/build/package/docs/dfcache/dfcache-version.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
 .SH NAME
 .PP
diff --git a/build/package/docs/dfcache/dfcache.1 b/build/package/docs/dfcache/dfcache.1
index 9809987c..dee111e1 100644
--- a/build/package/docs/dfcache/dfcache.1
+++ b/build/package/docs/dfcache/dfcache.1
@@ -1,11 +1,11 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfcache\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfcache\[rq] Documentation"
 .hy
-.SS dfcache
+.SH NAME
 .PP
-the P2P cache client of dragonfly
-.SS Synopsis
+\f[B]dfcache\f[R] \[em] the P2P cache client of dragonfly
+.SH SYNOPSIS
 .PP
 dfcache is the cache client to of dragonfly that communicates with
 dfdaemon and operates on files in P2P network, where the P2P network
@@ -18,7 +18,7 @@ file from source; but dfcache could only export or download a file that
 has been imported or added into P2P network by other peer, it\[cq]s the
 user\[cq]s responsibility to go back to source and add file into P2P
 network.
-.SS Options
+.SS OPTIONS
 .IP
 .nf
 \f[C]
@@ -37,7 +37,7 @@ network.
       --workhome string       Dfcache working directory
 \f[R]
 .fi
-.SS SEE ALSO
+.SH SEE ALSO
 .IP \[bu] 2
 dfcache completion - generate the autocompletion script for the
 specified shell
@@ -55,3 +55,6 @@ dfcache plugin - show plugin
 dfcache stat - stat checks if a file exists in P2P cache system
 .IP \[bu] 2
 dfcache version - show version
+.SH BUGS
+.PP
+See GitHub Issues: <https://github.com/dragonflyoss/Dragonfly2/issues>
diff --git a/build/package/docs/dfcache/dfcache.md b/build/package/docs/dfcache/dfcache.md
index a44247f0..dac1a033 100644
--- a/build/package/docs/dfcache/dfcache.md
+++ b/build/package/docs/dfcache/dfcache.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_delete.md b/build/package/docs/dfcache/dfcache_delete.md
index d13c334a..e0d09989 100644
--- a/build/package/docs/dfcache/dfcache_delete.md
+++ b/build/package/docs/dfcache/dfcache_delete.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_doc.md b/build/package/docs/dfcache/dfcache_doc.md
index e2b804ae..00a3ae55 100644
--- a/build/package/docs/dfcache/dfcache_doc.md
+++ b/build/package/docs/dfcache/dfcache_doc.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_export.md b/build/package/docs/dfcache/dfcache_export.md
index 7fa9968b..fed0a6f8 100644
--- a/build/package/docs/dfcache/dfcache_export.md
+++ b/build/package/docs/dfcache/dfcache_export.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_import.md b/build/package/docs/dfcache/dfcache_import.md
index de2adc38..4a247bae 100644
--- a/build/package/docs/dfcache/dfcache_import.md
+++ b/build/package/docs/dfcache/dfcache_import.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_plugin.md b/build/package/docs/dfcache/dfcache_plugin.md
index 302a5244..d60ec361 100644
--- a/build/package/docs/dfcache/dfcache_plugin.md
+++ b/build/package/docs/dfcache/dfcache_plugin.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_stat.md b/build/package/docs/dfcache/dfcache_stat.md
index e2543bc7..f80c896e 100644
--- a/build/package/docs/dfcache/dfcache_stat.md
+++ b/build/package/docs/dfcache/dfcache_stat.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfcache/dfcache_version.md b/build/package/docs/dfcache/dfcache_version.md
index e55c77b3..3ebffd2a 100644
--- a/build/package/docs/dfcache/dfcache_version.md
+++ b/build/package/docs/dfcache/dfcache_version.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfcache" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfcache" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfget.1 b/build/package/docs/dfget.1
index 4739f089..b3f5a22c 100644
--- a/build/package/docs/dfget.1
+++ b/build/package/docs/dfget.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFGET" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfget\[rq] Documentation"
+.TH "DFGET" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfget\[rq] Documentation"
 .hy
 .SH NAME
 .PP
@@ -17,121 +17,40 @@ pieces from it if it owns them.
 In addition, dfget has the abilities to provide more advanced
 functionality, such as network bandwidth limit, transmission encryption
 and so on.
-.SS Options
-.TP
-\[en]alivetime
-alive duration for which uploader keeps no accessing by any uploading
-requests, after this period uploader will automatically exit (default
-5m0s)
-.TP
-\[en]cacerts
-the cacert file which is used to verify remote server when supernode
-interact with the source.
-.TP
-\[en]callsystem
-the name of dfget caller which is for debugging.
-Once set, it will be passed to all components around the request to make
-debugging easy
-.TP
-\[en]clientqueue
-specify the size of client queue which controls the number of pieces
-that can be processed simultaneously
-.TP
-\[en]console
-show log on console, it\[cq]s conflict with `\[en]showbar'
-.TP
-\[en]daemon-pid
-the daemon pid (default \[lq]/tmp/dfdaemon.pid\[rq])
-.TP
-\[en]daemon-sock
-the unix domain socket address for grpc with daemon (default
-\[lq]/tmp/dfdamon.sock\[rq])
-.TP
-\[en]dfdaemon
-identify whether the request is from dfdaemon
-.TP
-\[en]expiretime
-caching duration for which cached file keeps no accessed by any process,
-after this period cache file will be deleted (default 3m0s)
-.TP
--f, \[en]filter
-filter some query params of URL, use char `&' to separate different
-params
-.TP
-\[en]header
-http header
-.TP
--h, \[en]help
-help for dfget
-.TP
-\[en]home
-the work home directory of dfget (default
-\[lq]/Users/jim/.dragonfly/dfdaemon/\[rq])
-.TP
--i, \[en]identifier
-the usage of identifier is making different downloading tasks generate
-different downloading task IDs even if they have the same URLs.
-conflict with \[en]md5.
-.TP
-\[en]insecure
-identify whether supernode should skip secure verify when interact with
-the source.
-.TP
-\[en]ip
-IP address that server will listen on (default \[lq]0.0.0.0\[rq])
-.TP
--m, \[en]md5
-md5 value input from user for the requested downloading file to enhance
-security
-.TP
-\[en]more-daemon-options
-more options passed to daemon by command line, please confirm your
-options with \[lq]dfget daemon \[en]help\[rq]
-.TP
--n, \[en]node
-deprecated, please use schedulers instead.
-specify the addresses(host:port=weight) of supernodes where the host is
-necessary, the port(default: 8002) and the weight(default:1) are
-optional.
-And the type of weight must be integer
-.TP
-\[en]notbacksource
-disable back source downloading for requested file when p2p fails to
-download it
-.TP
--o, \[en]output
-destination path which is used to store the requested downloading file.
-It must contain detailed directory and specific filename, for example,
-`/tmp/file.mp4'
-.TP
--p, \[en]pattern
-download pattern, must be p2p/seed-peer/source, seed-peer and source do
-not support flag \[en]totallimit (default \[lq]p2p\[rq])
-.TP
-\[en]port
-port number that server will listen on (default 65002)
-.TP
-\[en]schedulers
-the scheduler addresses
-.TP
--b, \[en]showbar
-show progress bar, it is conflict with `\[en]console'
-.TP
--e, \[en]timeout
-timeout set for file downloading task.
-If dfget has not finished downloading all pieces of file before
-\[en]timeout, the dfget will throw an error and exit
-.TP
-\[en]totallimit
-network bandwidth rate limit for the whole host, in format of
-G(B)/g/M(B)/m/K(B)/k/B, pure number will also be parsed as Byte (default
-104857600.000000)
-.TP
--u, \[en]url
-URL of user requested downloading file(only HTTP/HTTPs supported)
-.TP
-\[en]verbose
-enable verbose mode, all debug log will be display
+.SS OPTIONS
+.IP
+.nf
+\f[C]
+      --accept-regex string   Recursively download only. Specify a regular expression to accept the complete URL. In this case, you have to enclose the pattern into quotes to prevent your shell from expanding it
+      --callsystem string     The caller name which is mainly used for statistics and access control
+      --config string         the path of configuration file with yaml extension name, default is /Users/qiwenbo/.dragonfly/config/dfget.yaml, it can also be set by env var: DFGET_CONFIG
+      --console               whether logger output records to the stdout
+      --digest string         Check the integrity of the downloaded file with digest, in format of md5:xxx or sha256:yyy
+      --disable-back-source   Disable downloading directly from source when the daemon fails to download file
+      --filter string         Filter the query parameters of the url, P2P overlay is the same one if the filtered url is same, in format of key&sign, which will filter \[aq]key\[aq] and \[aq]sign\[aq] query parameters
+  -H, --header strings        url header, eg: --header=\[aq]Accept: *\[aq] --header=\[aq]Host: abc\[aq]
+  -h, --help                  help for dfget
+      --jaeger string         jaeger endpoint url, like: http://localhost:14250/api/traces
+      --level uint            Recursively download only. Set the maximum number of subdirectories that dfget will recurse into. Set to 0 for no limit (default 5)
+  -l, --list                  Recursively download only. List all urls instead of downloading them.
+      --logdir string         Dfget log directory
+      --original-offset       Range request only. Download ranged data into target file with original offset. Daemon will make a hardlink to target file. Client can download many ranged data into one file for same url. When enabled, back source in client will be disabled
+  -O, --output string         Destination path which is used to store the downloaded file, it must be a full path
+  -p, --pattern string        The downloading pattern: p2p/seed-peer/source
+      --pprof-port int        listen port for pprof, 0 represents random port (default -1)
+      --range string          Download range. Like: 0-9, stands download 10 bytes from 0 -9, [0:9] in real url
+      --ratelimit string      The downloading network bandwidth limit per second in format of G(B)/g/M(B)/m/K(B)/k/B, pure number will be parsed as Byte, 0 is infinite (default \[dq]100.0MB\[dq])
+  -r, --recursive             Recursively download all resources in target url, the target source client must support list action
+      --reject-regex string   Recursively download only. Specify a regular expression to reject the complete URL. In this case, you have to enclose the pattern into quotes to prevent your shell from expanding it
+      --service-name string   name of the service for tracer (default \[dq]dragonfly-dfget\[dq])
+  -b, --show-progress         Show progress bar, it conflicts with --console
+      --tag string            Different tags for the same url will be divided into different P2P overlay, it conflicts with --digest
+      --timeout duration      Timeout for the downloading task, 0 is infinite
+  -u, --url string            Download one file from the url, equivalent to the command\[aq]s first position argument
+      --verbose               whether logger use debug level
+      --workhome string       Dfget working directory
+\f[R]
+.fi
 .SH BUGS
 .PP
 See GitHub Issues: <https://github.com/dragonflyoss/Dragonfly2/issues>
diff --git a/build/package/docs/dfget.1.md b/build/package/docs/dfget.1.md
index 58c0d886..d90fff95 100644
--- a/build/package/docs/dfget.1.md
+++ b/build/package/docs/dfget.1.md
@@ -1,4 +1,4 @@
-% DFGET(1) Version v2.0.4 | Frivolous "Dfget" Documentation
+% DFGET(1) Version v2.0.5 | Frivolous "Dfget" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfstore/dfstore-copy.1 b/build/package/docs/dfstore/dfstore-copy.1
index cf90821b..81a73fc7 100644
--- a/build/package/docs/dfstore/dfstore-copy.1
+++ b/build/package/docs/dfstore/dfstore-copy.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfstore\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfstore\[rq] Documentation"
 .hy
 .SH NAME
 .PP
diff --git a/build/package/docs/dfstore/dfstore-remove.1 b/build/package/docs/dfstore/dfstore-remove.1
index 69d89f8d..525a88a4 100644
--- a/build/package/docs/dfstore/dfstore-remove.1
+++ b/build/package/docs/dfstore/dfstore-remove.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfstore\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfstore\[rq] Documentation"
 .hy
 .SH NAME
 .PP
diff --git a/build/package/docs/dfstore/dfstore-version.1 b/build/package/docs/dfstore/dfstore-version.1
index 4da2ec09..1a9b3b43 100644
--- a/build/package/docs/dfstore/dfstore-version.1
+++ b/build/package/docs/dfstore/dfstore-version.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfstore\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfstore\[rq] Documentation"
 .hy
 .SH NAME
 .PP
diff --git a/build/package/docs/dfstore/dfstore.1 b/build/package/docs/dfstore/dfstore.1
index cef91403..83cef7ca 100644
--- a/build/package/docs/dfstore/dfstore.1
+++ b/build/package/docs/dfstore/dfstore.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.13
 .\"
-.TH "DFCACHE" "1" "" "Version v2.0.4" "Frivolous \[lq]Dfstore\[rq] Documentation"
+.TH "DFCACHE" "1" "" "Version v2.0.5" "Frivolous \[lq]Dfstore\[rq] Documentation"
 .hy
 .SH NAME
 .PP
diff --git a/build/package/docs/dfstore/dfstore.md b/build/package/docs/dfstore/dfstore.md
index fb284c28..7017e17a 100644
--- a/build/package/docs/dfstore/dfstore.md
+++ b/build/package/docs/dfstore/dfstore.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfstore" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfstore" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfstore/dfstore_copy.md b/build/package/docs/dfstore/dfstore_copy.md
index ee6a67ff..ab6944c4 100644
--- a/build/package/docs/dfstore/dfstore_copy.md
+++ b/build/package/docs/dfstore/dfstore_copy.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfstore" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfstore" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfstore/dfstore_remove.md b/build/package/docs/dfstore/dfstore_remove.md
index 873aeca8..afbecef3 100644
--- a/build/package/docs/dfstore/dfstore_remove.md
+++ b/build/package/docs/dfstore/dfstore_remove.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfstore" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfstore" Documentation
 
 # NAME
 
diff --git a/build/package/docs/dfstore/dfstore_version.md b/build/package/docs/dfstore/dfstore_version.md
index 48bcd0d0..3e318799 100644
--- a/build/package/docs/dfstore/dfstore_version.md
+++ b/build/package/docs/dfstore/dfstore_version.md
@@ -1,4 +1,4 @@
-% DFCACHE(1) Version v2.0.4 | Frivolous "Dfstore" Documentation
+% DFCACHE(1) Version v2.0.5 | Frivolous "Dfstore" Documentation
 
 # NAME
 
diff --git a/client/config/peerhost.go b/client/config/peerhost.go
index 760b0c5e..d4cddb5c 100644
--- a/client/config/peerhost.go
+++ b/client/config/peerhost.go
@@ -254,6 +254,8 @@ type TransportOption struct {
 type ConcurrentOption struct {
 	// ThresholdSize indicates the threshold to download pieces concurrently
 	ThresholdSize util.Size `mapstructure:"thresholdSize" yaml:"thresholdSize"`
+	// ThresholdSpeed indicates the threshold download speed to download pieces concurrently
+	ThresholdSpeed unit.Bytes `mapstructure:"thresholdSpeed" yaml:"thresholdSpeed"`
 	// GoroutineCount indicates the concurrent goroutine count for every task
 	GoroutineCount int `mapstructure:"goroutineCount" yaml:"goroutineCount"`
 	// InitBackoff second for every piece failed, default: 0.5
diff --git a/client/daemon/peer/peertask_conductor.go b/client/daemon/peer/peertask_conductor.go
index 4d72dce1..de12b816 100644
--- a/client/daemon/peer/peertask_conductor.go
+++ b/client/daemon/peer/peertask_conductor.go
@@ -877,7 +877,11 @@ func (pt *peerTaskConductor) pullPiecesFromPeers(pieceRequestCh chan *DownloadPi
 	)
 
 	// ensure first peer packet is not nil
-	peerPacket := pt.peerPacket.Load().(*scheduler.PeerPacket)
+	peerPacket, ok := pt.peerPacket.Load().(*scheduler.PeerPacket)
+	if !ok {
+		pt.Warn("pull pieces canceled")
+		return
+	}
 	if len(peerPacket.CandidatePeers) == 0 {
 		num, ok = pt.waitAvailablePeerPacket()
 		if !ok {
@@ -999,6 +1003,12 @@ func (pt *peerTaskConductor) initDownloadPieceWorkers(count int32, pieceRequestC
 func (pt *peerTaskConductor) waitFirstPeerPacket() (done bool, backSource bool) {
 	// wait first available peer
 	select {
+	case <-pt.successCh:
+		pt.Infof("peer task succeed, no need to wait first peer")
+		return true, false
+	case <-pt.failCh:
+		pt.Warnf("peer task failed, no need to wait first peer")
+		return true, false
 	case _, ok := <-pt.peerPacketReady:
 		if ok {
 			// preparePieceTasksByPeer func already send piece result with error
diff --git a/client/daemon/peer/piece_manager.go b/client/daemon/peer/piece_manager.go
index af7f53c0..17a60012 100644
--- a/client/daemon/peer/piece_manager.go
+++ b/client/daemon/peer/piece_manager.go
@@ -291,19 +291,21 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, peerTaskReq
 	if err != nil {
 		return err
 	}
-
+	var (
+		metadata            *source.Metadata
+		supportConcurrent   bool
+		targetContentLength int64
+	)
 	if pm.concurrentOption != nil {
 		// check metadata
 		// 1. support range request
 		// 2. target content length is greater than concurrentOption.ThresholdSize
-		metadata, err := source.GetMetadata(backSourceRequest)
+		metadata, err = source.GetMetadata(backSourceRequest)
 		if err == nil && metadata.Validate != nil && metadata.Validate() == nil {
 			if !metadata.SupportRange || metadata.TotalContentLength == -1 {
 				goto singleDownload
 			}
-			var (
-				targetContentLength int64
-			)
+			supportConcurrent = true
 			if parsedRange != nil {
 				if parsedRange.Length > -1 {
 					targetContentLength = parsedRange.Length
@@ -337,7 +339,7 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, peerTaskReq
 					return err
 				}
 				// use concurrent piece download mode
-				return pm.concurrentDownloadSource(ctx, pt, peerTaskRequest, parsedRange, metadata)
+				return pm.concurrentDownloadSource(ctx, pt, peerTaskRequest, parsedRange, metadata, 0)
 			}
 		}
 	}
@@ -417,10 +419,10 @@ singleDownload:
 		return pm.downloadUnknownLengthSource(pt, pieceSize, reader)
 	}
 
-	return pm.downloadKnownLengthSource(pt, contentLength, pieceSize, reader)
+	return pm.downloadKnownLengthSource(ctx, pt, contentLength, pieceSize, reader, response, peerTaskRequest, parsedRange, metadata, supportConcurrent, targetContentLength)
 }
 
-func (pm *pieceManager) downloadKnownLengthSource(pt Task, contentLength int64, pieceSize uint32, reader io.Reader) error {
+func (pm *pieceManager) downloadKnownLengthSource(ctx context.Context, pt Task, contentLength int64, pieceSize uint32, reader io.Reader, response *source.Response, peerTaskRequest *scheduler.PeerTaskRequest, parsedRange *clientutil.Range, metadata *source.Metadata, supportConcurrent bool, targetContentLength int64) error {
 	log := pt.Log()
 	maxPieceNum := util.ComputePieceCount(contentLength, pieceSize)
 	pt.SetContentLength(contentLength)
@@ -466,6 +468,26 @@ func (pm *pieceManager) downloadKnownLengthSource(pt Task, contentLength int64,
 
 		pt.ReportPieceResult(request, result, nil)
 		pt.PublishPieceInfo(pieceNum, uint32(result.Size))
+		if supportConcurrent {
+			speed := int64(pieceSize) / (result.FinishTime - result.BeginTime)
+			if speed < int64(pm.concurrentOption.ThresholdSpeed) {
+				err = pt.GetStorage().UpdateTask(ctx,
+					&storage.UpdateTaskRequest{
+						PeerTaskMetadata: storage.PeerTaskMetadata{
+							PeerID: pt.GetPeerID(),
+							TaskID: pt.GetTaskID(),
+						},
+						ContentLength: targetContentLength,
+						Header:        &metadata.Header,
+					})
+				if err != nil {
+					log.Errorf("update task error: %s", err)
+					return err
+				}
+				response.Body.Close()
+				return pm.concurrentDownloadSource(ctx, pt, peerTaskRequest, parsedRange, metadata, pieceNum+1)
+			}
+		}
 	}
 
 	log.Infof("download from source ok")
@@ -724,7 +746,7 @@ func (pm *pieceManager) Import(ctx context.Context, ptm storage.PeerTaskMetadata
 	return nil
 }
 
-func (pm *pieceManager) concurrentDownloadSource(ctx context.Context, pt Task, peerTaskRequest *scheduler.PeerTaskRequest, parsedRange *clientutil.Range, metadata *source.Metadata) error {
+func (pm *pieceManager) concurrentDownloadSource(ctx context.Context, pt Task, peerTaskRequest *scheduler.PeerTaskRequest, parsedRange *clientutil.Range, metadata *source.Metadata, startPieceNum int32) error {
 	// parsedRange is always exist
 	pieceSize := pm.computePieceSize(parsedRange.Length)
 	pieceCount := util.ComputePieceCount(parsedRange.Length, pieceSize)
@@ -744,7 +766,7 @@ func (pm *pieceManager) concurrentDownloadSource(ctx context.Context, pt Task, p
 	var pieceCh = make(chan int32, con)
 
 	wg := sync.WaitGroup{}
-	wg.Add(int(pieceCount))
+	wg.Add(int(pieceCount - startPieceNum))
 
 	downloadedPieceCount := atomic.NewInt32(0)
 
@@ -784,7 +806,7 @@ func (pm *pieceManager) concurrentDownloadSource(ctx context.Context, pt Task, p
 		}(i)
 	}
 
-	for i := int32(0); i < pieceCount; i++ {
+	for i := startPieceNum; i < pieceCount; i++ {
 		select {
 		case <-ctx.Done():
 			log.Warnf("context cancelled")
diff --git a/deploy/docker-compose/docker-compose.yaml b/deploy/docker-compose/docker-compose.yaml
index 582a7e06..cea80a57 100644
--- a/deploy/docker-compose/docker-compose.yaml
+++ b/deploy/docker-compose/docker-compose.yaml
@@ -28,7 +28,7 @@ services:
       retries: 30
 
   manager:
-    image: dragonflyoss/manager:v2.0.4
+    image: dragonflyoss/manager:v2.0.5
     container_name: manager
     network_mode: host
     depends_on:
@@ -45,7 +45,7 @@ services:
       retries: 30
 
   dfdaemon:
-    image: dragonflyoss/dfdaemon:v2.0.4
+    image: dragonflyoss/dfdaemon:v2.0.5
     depends_on:
       - manager
       - scheduler
@@ -63,7 +63,7 @@ services:
       - ./config/dfget.yaml:/etc/dragonfly/dfget.yaml:ro
 
   scheduler:
-    image: dragonflyoss/scheduler:v2.0.4
+    image: dragonflyoss/scheduler:v2.0.5
     depends_on:
       - manager
     container_name: scheduler
@@ -79,7 +79,7 @@ services:
       - ./config/scheduler.yaml:/etc/dragonfly/scheduler.yaml:ro
 
   seed-peer:
-    image: dragonflyoss/dfdaemon:v2.0.4
+    image: dragonflyoss/dfdaemon:v2.0.5
     depends_on:
       - manager
       - scheduler
diff --git a/deploy/docker-compose/template/dfget.template.yaml b/deploy/docker-compose/template/dfget.template.yaml
index ae978865..7e23e5f4 100644
--- a/deploy/docker-compose/template/dfget.template.yaml
+++ b/deploy/docker-compose/template/dfget.template.yaml
@@ -89,6 +89,7 @@ host:
 download:
   concurrent:
     thresholdSize: 10M
+    thresholdSpeed: 2M
     goroutineCount: 4
   # calculate digest when transfer files, set false to save memory
   calculateDigest: true
diff --git a/deploy/helm-charts b/deploy/helm-charts
index 32b790ec..f3f27785 160000
--- a/deploy/helm-charts
+++ b/deploy/helm-charts
@@ -1,1 +1,1 @@
-Subproject commit 32b790eceb6e936b9f29b9d10889580f89c4c9b6
+Subproject commit f3f277850f2b4ceccc8c021340141cc2f7c5f168
diff --git a/manager/database/database.go b/manager/database/database.go
index 85a1f3d4..6015982c 100644
--- a/manager/database/database.go
+++ b/manager/database/database.go
@@ -100,7 +100,8 @@ func seed(cfg *config.Config, db *gorm.DB) error {
 			},
 			Name: DefaultSchedulerClusterName,
 			Config: map[string]any{
-				"filter_parent_limit": schedulerconfig.DefaultSchedulerFilterParentLimit,
+				"filter_parent_limit":       schedulerconfig.DefaultSchedulerFilterParentLimit,
+				"filter_parent_range_limit": schedulerconfig.DefaultSchedulerFilterParentRangeLimit,
 			},
 			ClientConfig: map[string]any{
 				"load_limit":     schedulerconfig.DefaultClientLoadLimit,
diff --git a/manager/types/scheduler_cluster.go b/manager/types/scheduler_cluster.go
index a5124ebb..f21cd2d1 100644
--- a/manager/types/scheduler_cluster.go
+++ b/manager/types/scheduler_cluster.go
@@ -54,7 +54,8 @@ type GetSchedulerClustersQuery struct {
 }
 
 type SchedulerClusterConfig struct {
-	FilterParentLimit uint32 `yaml:"filterParentLimit" mapstructure:"filterParentLimit" json:"filter_parent_limit" binding:"omitempty,gte=1,lte=100"`
+	FilterParentLimit      uint32 `yaml:"filterParentLimit" mapstructure:"filterParentLimit" json:"filter_parent_limit" binding:"omitempty,gte=1,lte=100"`
+	FilterParentRangeLimit uint32 `yaml:"filterParentRangeLimit" mapstructure:"filterParentRangeLimit" json:"filter_parent_range_limit" binding:"omitempty,gte=1,lte=1000"`
 }
 
 type SchedulerClusterClientConfig struct {
diff --git a/pkg/dag/dag.go b/pkg/dag/dag.go
index 08b47c33..835b5269 100644
--- a/pkg/dag/dag.go
+++ b/pkg/dag/dag.go
@@ -59,16 +59,16 @@ type DAG[T comparable] interface {
 	GetVertices() map[string]*Vertex[T]
 
 	// GetRandomVertices returns random map of vertices.
-	GetRandomVertices(n uint) map[string]*Vertex[T]
+	GetRandomVertices(n uint) []*Vertex[T]
 
 	// GetVertexKeys returns keys of vertices.
 	GetVertexKeys() []string
 
 	// GetSourceVertices returns source vertices.
-	GetSourceVertices() map[string]*Vertex[T]
+	GetSourceVertices() []*Vertex[T]
 
 	// GetSinkVertices returns sink vertices.
-	GetSinkVertices() map[string]*Vertex[T]
+	GetSinkVertices() []*Vertex[T]
 
 	// VertexCount returns count of vertices.
 	VertexCount() int
@@ -147,22 +147,23 @@ func (d *dag[T]) GetVertices() map[string]*Vertex[T] {
 }
 
 // GetRandomVertices returns random map of vertices.
-func (d *dag[T]) GetRandomVertices(n uint) map[string]*Vertex[T] {
+func (d *dag[T]) GetRandomVertices(n uint) []*Vertex[T] {
 	d.mu.RLock()
 	defer d.mu.RUnlock()
 
 	keys := d.GetVertexKeys()
-	vertices := d.GetVertices()
 	if int(n) >= len(keys) {
-		return vertices
+		n = uint(len(keys))
 	}
 
 	rand.Seed(time.Now().Unix())
 	permutation := rand.Perm(len(keys))[:n]
-	randomVertices := make(map[string]*Vertex[T])
+	randomVertices := make([]*Vertex[T], 0, n)
 	for _, v := range permutation {
 		key := keys[v]
-		randomVertices[key] = vertices[key]
+		if vertex, err := d.GetVertex(key); err == nil {
+			randomVertices = append(randomVertices, vertex)
+		}
 	}
 
 	return randomVertices
@@ -270,14 +271,14 @@ func (d *dag[T]) DeleteEdge(fromVertexID, toVertexID string) error {
 }
 
 // GetSourceVertices returns source vertices.
-func (d *dag[T]) GetSourceVertices() map[string]*Vertex[T] {
+func (d *dag[T]) GetSourceVertices() []*Vertex[T] {
 	d.mu.RLock()
 	defer d.mu.RUnlock()
 
-	sourceVertices := make(map[string]*Vertex[T])
-	for k, v := range d.vertices.Items() {
-		if v.InDegree() == 0 {
-			sourceVertices[k] = v
+	var sourceVertices []*Vertex[T]
+	for _, vertex := range d.vertices.Items() {
+		if vertex.InDegree() == 0 {
+			sourceVertices = append(sourceVertices, vertex)
 		}
 	}
 
@@ -285,14 +286,14 @@ func (d *dag[T]) GetSourceVertices() map[string]*Vertex[T] {
 }
 
 // GetSinkVertices returns sink vertices.
-func (d *dag[T]) GetSinkVertices() map[string]*Vertex[T] {
+func (d *dag[T]) GetSinkVertices() []*Vertex[T] {
 	d.mu.RLock()
 	defer d.mu.RUnlock()
 
-	sinkVertices := make(map[string]*Vertex[T])
-	for k, v := range d.vertices.Items() {
-		if v.OutDegree() == 0 {
-			sinkVertices[k] = v
+	var sinkVertices []*Vertex[T]
+	for _, vertex := range d.vertices.Items() {
+		if vertex.OutDegree() == 0 {
+			sinkVertices = append(sinkVertices, vertex)
 		}
 	}
 
diff --git a/pkg/dag/mocks/dag_mock.go b/pkg/dag/mocks/dag_mock.go
index d272f267..2465794d 100644
--- a/pkg/dag/mocks/dag_mock.go
+++ b/pkg/dag/mocks/dag_mock.go
@@ -103,10 +103,10 @@ func (mr *MockDAGMockRecorder[T]) DeleteVertex(id interface{}) *gomock.Call {
 }
 
 // GetRandomVertices mocks base method.
-func (m *MockDAG[T]) GetRandomVertices(n uint) map[string]*dag.Vertex[T] {
+func (m *MockDAG[T]) GetRandomVertices(n uint) []*dag.Vertex[T] {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetRandomVertices", n)
-	ret0, _ := ret[0].(map[string]*dag.Vertex[T])
+	ret0, _ := ret[0].([]*dag.Vertex[T])
 	return ret0
 }
 
@@ -117,10 +117,10 @@ func (mr *MockDAGMockRecorder[T]) GetRandomVertices(n interface{}) *gomock.Call
 }
 
 // GetSinkVertices mocks base method.
-func (m *MockDAG[T]) GetSinkVertices() map[string]*dag.Vertex[T] {
+func (m *MockDAG[T]) GetSinkVertices() []*dag.Vertex[T] {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetSinkVertices")
-	ret0, _ := ret[0].(map[string]*dag.Vertex[T])
+	ret0, _ := ret[0].([]*dag.Vertex[T])
 	return ret0
 }
 
@@ -131,10 +131,10 @@ func (mr *MockDAGMockRecorder[T]) GetSinkVertices() *gomock.Call {
 }
 
 // GetSourceVertices mocks base method.
-func (m *MockDAG[T]) GetSourceVertices() map[string]*dag.Vertex[T] {
+func (m *MockDAG[T]) GetSourceVertices() []*dag.Vertex[T] {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetSourceVertices")
-	ret0, _ := ret[0].(map[string]*dag.Vertex[T])
+	ret0, _ := ret[0].([]*dag.Vertex[T])
 	return ret0
 }
 
diff --git a/scheduler/config/constants.go b/scheduler/config/constants.go
index 0a0d978b..be0a8b41 100644
--- a/scheduler/config/constants.go
+++ b/scheduler/config/constants.go
@@ -33,6 +33,9 @@ const (
 
 	// DefaultSchedulerFilterParentLimit is default limit the number for filter traversals.
 	DefaultSchedulerFilterParentLimit = 4
+
+	// DefaultSchedulerFilterParentRangeLimit is default limit the range for filter traversals.
+	DefaultSchedulerFilterParentRangeLimit = 40
 )
 
 // DefaultServerListen is default listen for server.
diff --git a/scheduler/config/dynconfig.go b/scheduler/config/dynconfig.go
index 2b0c762a..f1ed790a 100644
--- a/scheduler/config/dynconfig.go
+++ b/scheduler/config/dynconfig.go
@@ -148,7 +148,7 @@ func (d *dynconfig) GetSchedulerClusterConfig() (types.SchedulerClusterConfig, b
 		return types.SchedulerClusterConfig{}, false
 	}
 
-	if data.SchedulerCluster != nil {
+	if data.SchedulerCluster == nil {
 		return types.SchedulerClusterConfig{}, false
 	}
 
diff --git a/scheduler/scheduler/scheduler.go b/scheduler/scheduler/scheduler.go
index 2eb2813f..7e577dff 100644
--- a/scheduler/scheduler/scheduler.go
+++ b/scheduler/scheduler/scheduler.go
@@ -233,16 +233,27 @@ func (s *scheduler) FindParent(ctx context.Context, peer *resource.Peer, blockli
 // Filter the candidate parent that can be scheduled.
 func (s *scheduler) filterCandidateParents(peer *resource.Peer, blocklist set.SafeSet[string]) []*resource.Peer {
 	filterParentLimit := config.DefaultSchedulerFilterParentLimit
-	if config, ok := s.dynconfig.GetSchedulerClusterConfig(); ok && filterParentLimit > 0 {
-		filterParentLimit = int(config.FilterParentLimit)
+	filterParentRangeLimit := config.DefaultSchedulerFilterParentRangeLimit
+	if config, ok := s.dynconfig.GetSchedulerClusterConfig(); ok {
+		if config.FilterParentLimit > 0 {
+			filterParentLimit = int(config.FilterParentLimit)
+		}
+
+		if config.FilterParentRangeLimit > 0 {
+			filterParentRangeLimit = int(config.FilterParentRangeLimit)
+		}
 	}
 
 	var (
 		candidateParents   []*resource.Peer
 		candidateParentIDs []string
 	)
+	for _, candidateParent := range peer.Task.LoadRandomPeers(uint(filterParentRangeLimit)) {
+		// Parent length limit after filtering.
+		if len(candidateParents) >= filterParentLimit {
+			break
+		}
 
-	for _, candidateParent := range peer.Task.LoadRandomPeers(uint(filterParentLimit)) {
 		// Candidate parent is in blocklist.
 		if blocklist.Contains(candidateParent.ID) {
 			peer.Log.Debugf("candidate parent %s is not selected because it is in blocklist", candidateParent.ID)
diff --git a/version/version.go b/version/version.go
index b813ac02..47be4ea2 100644
--- a/version/version.go
+++ b/version/version.go
@@ -28,7 +28,7 @@ import (
 var (
 	Major      = "2"
 	Minor      = "0"
-	GitVersion = "v2.0.4"
+	GitVersion = "v2.0.5"
 	GitCommit  = "unknown"
 	Platform   = osArch
 	BuildTime  = "unknown"


Test patch:
diff --git a/client/config/peerhost_test.go b/client/config/peerhost_test.go
index ef5774f4..72a24c1e 100644
--- a/client/config/peerhost_test.go
+++ b/client/config/peerhost_test.go
@@ -337,6 +337,7 @@ func TestPeerHostOption_Load(t *testing.T) {
 				ThresholdSize: util.Size{
 					Limit: 1,
 				},
+				ThresholdSpeed: unit.Bytes(1),
 				GoroutineCount: 1,
 				InitBackoff:    1,
 				MaxBackoff:     1,
diff --git a/client/config/testdata/config/daemon.yaml b/client/config/testdata/config/daemon.yaml
index 4066af53..48c33faa 100644
--- a/client/config/testdata/config/daemon.yaml
+++ b/client/config/testdata/config/daemon.yaml
@@ -79,6 +79,7 @@ download:
   watchdogTimeout: 1s
   concurrent:
     thresholdSize: 1
+    thresholdSpeed: 1
     goroutineCount: 1
     initBackoff: 1
     maxBackoff: 1
diff --git a/client/daemon/peer/piece_manager_test.go b/client/daemon/peer/piece_manager_test.go
index 8fe88bc6..d33dc40d 100644
--- a/client/daemon/peer/piece_manager_test.go
+++ b/client/daemon/peer/piece_manager_test.go
@@ -34,12 +34,14 @@ import (
 	testifyassert "github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"go.uber.org/atomic"
+	"golang.org/x/time/rate"
 
 	"d7y.io/dragonfly/v2/client/config"
 	"d7y.io/dragonfly/v2/client/daemon/storage"
 	"d7y.io/dragonfly/v2/client/daemon/test"
-	"d7y.io/dragonfly/v2/client/util"
+	clientutil "d7y.io/dragonfly/v2/client/util"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/internal/util"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	_ "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
@@ -67,7 +69,7 @@ func TestPieceManager_DownloadSource(t *testing.T) {
 		config.SimpleLocalTaskStoreStrategy,
 		&config.StorageOption{
 			DataPath: t.TempDir(),
-			TaskExpireTime: util.Duration{
+			TaskExpireTime: clientutil.Duration{
 				Duration: -1 * time.Second,
 			},
 		}, func(request storage.CommonTaskRequest) {})
@@ -77,11 +79,13 @@ func TestPieceManager_DownloadSource(t *testing.T) {
 	digest := hex.EncodeToString(hash.Sum(nil)[:16])
 
 	testCases := []struct {
-		name              string
-		pieceSize         uint32
-		withContentLength bool
-		checkDigest       bool
-		concurrentOption  *config.ConcurrentOption
+		name               string
+		pieceSize          uint32
+		withContentLength  bool
+		checkDigest        bool
+		recordDownloadTime bool
+		bandwidth          clientutil.Size
+		concurrentOption   *config.ConcurrentOption
 	}{
 		{
 			name:              "multiple pieces with content length, check digest",
@@ -90,23 +94,201 @@ func TestPieceManager_DownloadSource(t *testing.T) {
 			withContentLength: true,
 		},
 		{
-			name:              "multiple pieces with content length",
-			pieceSize:         1024,
-			checkDigest:       false,
-			withContentLength: true,
+			name:               "multiple pieces with content length",
+			pieceSize:          1024,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
 		},
 		{
-			name:              "multiple pieces with content length, concurrent download",
-			pieceSize:         2048,
-			checkDigest:       false,
-			withContentLength: true,
+			name:               "multiple pieces with content length, concurrent download with 2 goroutines",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 2,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024,
+				},
+			},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 4 goroutines",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
 			concurrentOption: &config.ConcurrentOption{
 				GoroutineCount: 4,
-				ThresholdSize: util.Size{
+				ThresholdSize: clientutil.Size{
+					Limit: 1024,
+				},
+			},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 8 goroutines",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 8,
+				ThresholdSize: clientutil.Size{
 					Limit: 1024,
 				},
 			},
 		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 16 goroutines",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 16,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024,
+				},
+			},
+		},
+		{
+			name:               "multiple pieces with content length, single-thread download with download bandwidth 2KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 2048},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 2 goroutines, download bandwidth 2KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 2048},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 2,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 4 goroutines, download bandwidth 2KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 2048},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 4,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 8 goroutines, download bandwidth 2KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 2048},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 8,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with 16 goroutines, download bandwidth 2KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 2048},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 16,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
+		{
+			name:               "multiple pieces with content length, single-thread download with download bandwidth 4KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 4096},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with download bandwidth 4KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 4096},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 4,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
+		{
+			name:               "multiple pieces with content length, single-thread download with download bandwidth 8KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 8192},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with download bandwidth 8KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 8192},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 4,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
+		{
+			name:               "multiple pieces with content length, single-thread download with download bandwidth 16KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 16384},
+		},
+		{
+			name:               "multiple pieces with content length, concurrent download with download bandwidth 16KB/s",
+			pieceSize:          2048,
+			checkDigest:        false,
+			withContentLength:  true,
+			recordDownloadTime: true,
+			bandwidth:          clientutil.Size{Limit: 16384},
+			concurrentOption: &config.ConcurrentOption{
+				GoroutineCount: 4,
+				ThresholdSize: clientutil.Size{
+					Limit: 1024 * 1024,
+				},
+				ThresholdSpeed: 8192,
+			},
+		},
 		{
 			name:              "multiple pieces without content length, check digest",
 			pieceSize:         1024,
@@ -214,7 +396,7 @@ func TestPieceManager_DownloadSource(t *testing.T) {
 					buf = bytes.NewBuffer(testBytes)
 					l = len(testBytes)
 				} else {
-					parsedRange, err := util.ParseRange(rg, int64(len(testBytes)))
+					parsedRange, err := clientutil.ParseRange(rg, int64(len(testBytes)))
 					assert.Nil(err)
 					w.Header().Set(headers.ContentRange,
 						fmt.Sprintf("bytes %d-%d/%d",
@@ -226,9 +408,32 @@ func TestPieceManager_DownloadSource(t *testing.T) {
 					buf = bytes.NewBuffer(testBytes[parsedRange[0].Start : parsedRange[0].Start+parsedRange[0].Length])
 					l = int(parsedRange[0].Length)
 				}
-				n, err := io.Copy(w, buf)
-				assert.Nil(err)
-				assert.Equal(int64(l), n)
+				if tc.bandwidth.Limit > 0 {
+					ctx := context.Background()
+					limiter := rate.NewLimiter(tc.bandwidth.Limit, int(tc.bandwidth.Limit))
+					limiter.AllowN(time.Now(), int(tc.bandwidth.Limit))
+					maxPieceNum := util.ComputePieceCount(int64(l), uint32(tc.bandwidth.Limit))
+					for pieceNum := int32(0); pieceNum < maxPieceNum; pieceNum++ {
+						size := int(tc.bandwidth.Limit)
+						offset := uint64(pieceNum) * uint64(size)
+						// calculate piece size for last piece
+						if int64(offset)+int64(size) > int64(l) {
+							size = int(int64(l) - int64(offset))
+						}
+						err := limiter.WaitN(ctx, size)
+						assert.Nil(err)
+						n, err := io.CopyN(w, buf, int64(size))
+						if err != nil {
+							// broken pipe due to auto switch to concurrent download, which will close conn
+							return
+						}
+						assert.Equal(int64(size), n)
+					}
+				} else {
+					n, err := io.Copy(w, buf)
+					assert.Nil(err)
+					assert.Equal(int64(l), n)
+				}
 			}))
 			defer ts.Close()
 
@@ -249,9 +454,17 @@ func TestPieceManager_DownloadSource(t *testing.T) {
 			if tc.checkDigest {
 				request.UrlMeta.Digest = digest
 			}
-
+			var start time.Time
+			if tc.recordDownloadTime {
+				start = time.Now()
+			}
 			err = pm.DownloadSource(context.Background(), mockPeerTask, request, nil)
 			assert.Nil(err)
+			if tc.recordDownloadTime {
+				elapsed := time.Since(start)
+				log := mockPeerTask.Log()
+				log.Infof("download took %s", elapsed)
+			}
 
 			err = storageManager.Store(context.Background(),
 				&storage.StoreRequest{
diff --git a/pkg/dag/dag_test.go b/pkg/dag/dag_test.go
index 73ceb1ec..8a1742fa 100644
--- a/pkg/dag/dag_test.go
+++ b/pkg/dag/dag_test.go
@@ -744,7 +744,7 @@ func TestDAGSourceVertices(t *testing.T) {
 
 				sourceVertices := d.GetSourceVertices()
 				assert.Equal(len(sourceVertices), 1)
-				assert.Equal(sourceVertices[mockVertexEID].Value, mockVertexValue)
+				assert.Equal(sourceVertices[0].Value, mockVertexValue)
 			},
 		},
 		{
@@ -791,7 +791,7 @@ func TestDAGSinkVertices(t *testing.T) {
 
 				sinkVertices := d.GetSinkVertices()
 				assert.Equal(len(sinkVertices), 1)
-				assert.Equal(sinkVertices[mockVertexFID].Value, mockVertexValue)
+				assert.Equal(sinkVertices[0].Value, mockVertexValue)
 			},
 		},
 		{
diff --git a/test/testdata/charts/config.yaml b/test/testdata/charts/config.yaml
index cb2920e8..89ab857a 100644
--- a/test/testdata/charts/config.yaml
+++ b/test/testdata/charts/config.yaml
@@ -78,6 +78,7 @@ dfdaemon:
       prefetch: true
       concurrent:
         thresholdSize: 10M
+        thresholdSpeed: 2M
         goroutineCount: 4
     scheduler:
       disableAutoBackSource: true
diff --git a/test/testdata/k8s/proxy.yaml b/test/testdata/k8s/proxy.yaml
index 8cc7aa7f..15481307 100644
--- a/test/testdata/k8s/proxy.yaml
+++ b/test/testdata/k8s/proxy.yaml
@@ -43,6 +43,7 @@ data:
       totalRateLimit: 200Mi
       concurrent:
         thresholdSize: 10M
+        thresholdSpeed: 2M
         goroutineCount: 4
     upload:
       rateLimit: 100Mi