You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celeborn.apache.org by et...@apache.org on 2024/05/23 13:10:35 UTC

(celeborn) branch main updated: [CELEBORN-914] Support memory file storage

This is an automated email from the ASF dual-hosted git repository.

ethanfeng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/celeborn.git


The following commit(s) were added to refs/heads/main by this push:
     new 89d56c9bb [CELEBORN-914] Support memory file storage
89d56c9bb is described below

commit 89d56c9bbcef3249ff92d5e727ab1cbfd4a0057b
Author: mingji <fe...@alibaba-inc.com>
AuthorDate: Thu May 23 21:05:52 2024 +0800

    [CELEBORN-914] Support memory file storage
    
    ### What changes were proposed in this pull request?
    To support memory file storage.
    
    ### Why are the changes needed?
    To improve shuffle performance for small shuffle files.
    
    Design doc: https://docs.google.com/document/d/1SM-oOM0JHEIoRHTYhE9PYH60_1D3NMxDR50LZIM7uW0/edit?usp=sharing
    
    ### Does this PR introduce _any_ user-facing change?
    No.
    
    ### How was this patch tested?
    Pass GA and manually test on a cluster.
    
    Closes #2300 from FMX/B914.
    
    Authored-by: mingji <fe...@alibaba-inc.com>
    Signed-off-by: mingji <fe...@alibaba-inc.com>
---
 assets/grafana/celeborn-dashboard.json             | 587 ++++++++++++++++----
 .../celeborn/client/read/CelebornInputStream.java  |   5 +-
 .../celeborn/client/read/DfsPartitionReader.java   |   3 +-
 .../apache/celeborn/common/meta/DiskFileInfo.java  |  57 +-
 .../org/apache/celeborn/common/meta/FileInfo.java  |  55 +-
 .../celeborn/common/meta/MemoryFileInfo.java       |  46 +-
 .../celeborn/common/meta/ReduceFileMeta.java       |  28 +-
 .../buffer/ChunkBuffers.java}                      |  40 +-
 .../buffer/FileChunkBuffers.java}                  |  37 +-
 .../buffer/MemoryChunkBuffers.java}                |  36 +-
 .../celeborn/common/protocol/StorageInfo.java      |  11 +-
 .../common/util/ShuffleBlockInfoUtils.java         |  78 ++-
 .../org/apache/celeborn/common/CelebornConf.scala  |  31 +-
 .../common/network/CelebornRackResolverSuite.scala |   1 -
 .../celeborn/common/util/PbSerDeUtilsTest.scala    |   4 +-
 docs/configuration/worker.md                       |   3 +-
 .../tests/spark/memory/MemorySkewJoinSuite.scala   | 113 ++++
 .../tests/spark/memory/MemorySparkTestBase.scala   |  62 +++
 .../memory/MemoryStorageReusedExchangeSuite.scala  |  83 +++
 .../deploy/worker/memory/MemoryManager.java        | 144 +++--
 .../deploy/worker/storage/ChunkStreamManager.java  |  31 +-
 .../worker/storage/MapPartitionDataWriter.java     |  30 +-
 .../deploy/worker/storage/PartitionDataWriter.java | 380 +++++++++----
 .../worker/storage/PartitionDataWriterContext.java |  99 ++++
 .../worker/storage/PartitionFilesSorter.java       | 238 +++++---
 .../worker/storage/ReducePartitionDataWriter.java  | 122 ++---
 .../service/deploy/worker/Controller.scala         |  42 +-
 .../service/deploy/worker/FetchHandler.scala       |  96 ++--
 .../service/deploy/worker/PushDataHandler.scala    |  52 +-
 .../celeborn/service/deploy/worker/Worker.scala    |  19 +-
 .../service/deploy/worker/WorkerSource.scala       |   5 +
 .../service/deploy/worker/storage/FlushTask.scala  |   9 +-
 .../service/deploy/worker/storage/Flusher.scala    |  35 +-
 .../deploy/worker/storage/StorageManager.scala     | 282 +++++++---
 .../service/deploy/worker/FetchHandlerSuiteJ.java  |  14 +-
 .../worker/storage/ChunkStreamManagerSuiteJ.java   |  10 +-
 .../worker/storage/CreditStreamManagerSuiteJ.java  |   6 +-
 .../storage/PartitionDataWriterSuiteUtils.java     | 113 +++-
 .../SSLReducePartitionDataWriterSuiteJ.java        |   7 +-
 .../DiskMapPartitionDataWriterSuiteJ.java}         |  44 +-
 .../DiskPartitionFilesSorterSuiteJ.java}           |  29 +-
 .../DiskReducePartitionDataWriterSuiteJ.java}      | 367 +++++++------
 .../memory/MemoryPartitionFilesSorterSuiteJ.java   | 159 ++++++
 .../MemoryReducePartitionDataWriterSuiteJ.java}    | 600 +++++++++++----------
 .../service/deploy/memory/MemoryManagerSuite.scala |   4 +-
 45 files changed, 2954 insertions(+), 1263 deletions(-)

diff --git a/assets/grafana/celeborn-dashboard.json b/assets/grafana/celeborn-dashboard.json
index f7b4de9cf..f914886cd 100644
--- a/assets/grafana/celeborn-dashboard.json
+++ b/assets/grafana/celeborn-dashboard.json
@@ -130,7 +130,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 1
+            "y": 33
           },
           "id": 2,
           "options": {
@@ -220,7 +220,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 1
+            "y": 33
           },
           "id": 94,
           "options": {
@@ -595,7 +595,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 20
+            "y": 52
           },
           "id": 121,
           "options": {
@@ -688,7 +688,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 20
+            "y": 52
           },
           "id": 120,
           "options": {
@@ -781,7 +781,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 28
+            "y": 60
           },
           "id": 122,
           "options": {
@@ -873,7 +873,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 28
+            "y": 60
           },
           "id": 124,
           "options": {
@@ -965,7 +965,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 36
+            "y": 68
           },
           "id": 100,
           "options": {
@@ -1055,7 +1055,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 36
+            "y": 68
           },
           "id": 102,
           "options": {
@@ -1145,7 +1145,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 44
+            "y": 76
           },
           "id": 36,
           "options": {
@@ -1236,7 +1236,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 44
+            "y": 76
           },
           "id": 117,
           "options": {
@@ -1432,7 +1432,7 @@
             "h": 10,
             "w": 24,
             "x": 0,
-            "y": 61
+            "y": 85
           },
           "id": 84,
           "options": {
@@ -1612,7 +1612,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 69
+            "y": 95
           },
           "id": 60,
           "options": {
@@ -1702,7 +1702,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 69
+            "y": 95
           },
           "id": 62,
           "options": {
@@ -1791,7 +1791,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 77
+            "y": 104
           },
           "id": 90,
           "options": {
@@ -1881,7 +1881,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 77
+            "y": 104
           },
           "id": 92,
           "options": {
@@ -1972,7 +1972,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 85
+            "y": 112
           },
           "id": 182,
           "options": {
@@ -2063,7 +2063,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 85
+            "y": 112
           },
           "id": 184,
           "options": {
@@ -2155,7 +2155,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 93
+            "y": 120
           },
           "id": 181,
           "options": {
@@ -2178,8 +2178,8 @@
               },
               "editorMode": "builder",
               "expr": "metrics_ActiveShuffleSize_Value{role=\"Worker\"}",
-              "legendFormat": "${baseLegend}",
               "instant": false,
+              "legendFormat": "${baseLegend}",
               "range": true,
               "refId": "A"
             }
@@ -2248,7 +2248,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 93
+            "y": 120
           },
           "id": 183,
           "options": {
@@ -2271,8 +2271,8 @@
               },
               "editorMode": "builder",
               "expr": "metrics_ActiveShuffleFileCount_Value{role=\"Worker\"}",
-              "legendFormat": "${baseLegend}",
               "instant": false,
+              "legendFormat": "${baseLegend}",
               "range": true,
               "refId": "A"
             }
@@ -2340,7 +2340,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 101
+            "y": 128
           },
           "id": 179,
           "options": {
@@ -2445,7 +2445,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 110
+            "y": 137
           },
           "id": 68,
           "options": {
@@ -2535,7 +2535,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 110
+            "y": 137
           },
           "id": 70,
           "options": {
@@ -2625,7 +2625,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 118
+            "y": 146
           },
           "id": 72,
           "options": {
@@ -2715,7 +2715,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 118
+            "y": 146
           },
           "id": 74,
           "options": {
@@ -2804,7 +2804,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 126
+            "y": 155
           },
           "id": 83,
           "options": {
@@ -2895,7 +2895,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 126
+            "y": 155
           },
           "id": 76,
           "options": {
@@ -2986,7 +2986,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 134
+            "y": 163
           },
           "id": 128,
           "options": {
@@ -3077,7 +3077,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 134
+            "y": 163
           },
           "id": 129,
           "options": {
@@ -3168,7 +3168,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 142
+            "y": 171
           },
           "id": 130,
           "options": {
@@ -3259,7 +3259,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 142
+            "y": 171
           },
           "id": 132,
           "options": {
@@ -3350,7 +3350,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 150
+            "y": 179
           },
           "id": 131,
           "options": {
@@ -3441,7 +3441,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 150
+            "y": 179
           },
           "id": 133,
           "options": {
@@ -3638,7 +3638,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 167
+            "y": 188
           },
           "id": 66,
           "options": {
@@ -3728,7 +3728,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 167
+            "y": 188
           },
           "id": 96,
           "options": {
@@ -3818,7 +3818,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 175
+            "y": 196
           },
           "id": 17,
           "options": {
@@ -3908,7 +3908,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 175
+            "y": 196
           },
           "id": 18,
           "options": {
@@ -3997,7 +3997,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 183
+            "y": 204
           },
           "id": 81,
           "options": {
@@ -4088,7 +4088,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 183
+            "y": 204
           },
           "id": 77,
           "options": {
@@ -4179,7 +4179,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 191
+            "y": 212
           },
           "id": 82,
           "options": {
@@ -4270,7 +4270,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 191
+            "y": 212
           },
           "id": 75,
           "options": {
@@ -4361,7 +4361,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 199
+            "y": 220
           },
           "id": 73,
           "options": {
@@ -4467,7 +4467,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 208
+            "y": 155
           },
           "id": 78,
           "options": {
@@ -4557,7 +4557,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 208
+            "y": 155
           },
           "id": 80,
           "options": {
@@ -4647,7 +4647,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 216
+            "y": 163
           },
           "id": 4,
           "options": {
@@ -4737,7 +4737,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 216
+            "y": 163
           },
           "id": 6,
           "options": {
@@ -4827,7 +4827,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 224
+            "y": 172
           },
           "id": 56,
           "options": {
@@ -4917,7 +4917,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 224
+            "y": 172
           },
           "id": 58,
           "options": {
@@ -5005,7 +5005,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5021,7 +5022,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 233
+            "y": 7
           },
           "id": 19,
           "options": {
@@ -5096,7 +5097,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5104,7 +5106,192 @@
                   }
                 ]
               },
-              "unit": "bytes"
+              "unit": "percentunit"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 12,
+            "y": 7
+          },
+          "id": 185,
+          "options": {
+            "legend": {
+              "calcs": [],
+              "displayMode": "list",
+              "placement": "bottom",
+              "showLegend": true
+            },
+            "tooltip": {
+              "mode": "single",
+              "sort": "none"
+            }
+          },
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "${DS_PROMETHEUS}"
+              },
+              "editorMode": "builder",
+              "expr": "metrics_DirectMemoryUsageRatio_Value",
+              "instant": false,
+              "range": true,
+              "refId": "A"
+            }
+          ],
+          "title": "DirectMemoryUsageRatio",
+          "type": "timeseries"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "${DS_PROMETHEUS}"
+          },
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisCenteredZero": false,
+                "axisColorMode": "text",
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 0,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": false,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              },
+              "unit": "decbytes"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 0,
+            "y": 15
+          },
+          "id": 186,
+          "options": {
+            "legend": {
+              "calcs": [],
+              "displayMode": "list",
+              "placement": "bottom",
+              "showLegend": true
+            },
+            "tooltip": {
+              "mode": "single",
+              "sort": "none"
+            }
+          },
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "${DS_PROMETHEUS}"
+              },
+              "editorMode": "builder",
+              "expr": "metrics_MemoryFileStorageSize_Value",
+              "instant": false,
+              "range": true,
+              "refId": "A"
+            }
+          ],
+          "title": "MemoryFileStorageSize",
+          "type": "timeseries"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "${DS_PROMETHEUS}"
+          },
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisCenteredZero": false,
+                "axisColorMode": "text",
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 0,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": false,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              }
             },
             "overrides": []
           },
@@ -5112,7 +5299,100 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 233
+            "y": 15
+          },
+          "id": 188,
+          "options": {
+            "legend": {
+              "calcs": [],
+              "displayMode": "list",
+              "placement": "bottom",
+              "showLegend": true
+            },
+            "tooltip": {
+              "mode": "single",
+              "sort": "none"
+            }
+          },
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "${DS_PROMETHEUS}"
+              },
+              "editorMode": "builder",
+              "expr": "metrics_MemoryStorageFileCount_Value",
+              "instant": false,
+              "range": true,
+              "refId": "A"
+            }
+          ],
+          "title": "MemoryStorageFileCount",
+          "type": "timeseries"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "${DS_PROMETHEUS}"
+          },
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisCenteredZero": false,
+                "axisColorMode": "text",
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 0,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": false,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              },
+              "unit": "bytes"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 0,
+            "y": 23
           },
           "id": 20,
           "options": {
@@ -5187,7 +5467,100 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              }
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 12,
+            "y": 23
+          },
+          "id": 187,
+          "options": {
+            "legend": {
+              "calcs": [],
+              "displayMode": "list",
+              "placement": "bottom",
+              "showLegend": true
+            },
+            "tooltip": {
+              "mode": "single",
+              "sort": "none"
+            }
+          },
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "${DS_PROMETHEUS}"
+              },
+              "editorMode": "builder",
+              "expr": "metrics_EvictedFileCount_Value",
+              "instant": false,
+              "range": true,
+              "refId": "A"
+            }
+          ],
+          "title": "EvictedFileCount",
+          "type": "timeseries"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "${DS_PROMETHEUS}"
+          },
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisCenteredZero": false,
+                "axisColorMode": "text",
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 0,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": false,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5203,7 +5576,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 241
+            "y": 31
           },
           "id": 165,
           "options": {
@@ -5279,7 +5652,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5295,7 +5669,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 241
+            "y": 31
           },
           "id": 166,
           "options": {
@@ -5371,7 +5745,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5387,7 +5762,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 249
+            "y": 39
           },
           "id": 167,
           "options": {
@@ -5463,7 +5838,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5479,7 +5855,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 249
+            "y": 39
           },
           "id": 168,
           "options": {
@@ -5555,7 +5931,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5571,7 +5948,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 257
+            "y": 47
           },
           "id": 169,
           "options": {
@@ -5663,7 +6040,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 257
+            "y": 47
           },
           "id": 170,
           "options": {
@@ -5754,7 +6131,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 265
+            "y": 55
           },
           "id": 108,
           "options": {
@@ -5846,7 +6223,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 265
+            "y": 55
           },
           "id": 104,
           "options": {
@@ -5937,7 +6314,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 273
+            "y": 63
           },
           "id": 106,
           "options": {
@@ -6043,7 +6420,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 282
+            "y": 230
           },
           "id": 44,
           "options": {
@@ -6133,7 +6510,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 282
+            "y": 230
           },
           "id": 46,
           "options": {
@@ -6222,7 +6599,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 290
+            "y": 238
           },
           "id": 48,
           "options": {
@@ -6312,7 +6689,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 290
+            "y": 238
           },
           "id": 180,
           "options": {
@@ -6403,7 +6780,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 298
+            "y": 246
           },
           "id": 88,
           "options": {
@@ -6493,7 +6870,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 298
+            "y": 246
           },
           "id": 135,
           "options": {
@@ -6600,7 +6977,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 307
+            "y": 255
           },
           "id": 159,
           "options": {
@@ -6693,7 +7070,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 307
+            "y": 255
           },
           "id": 160,
           "options": {
@@ -6786,7 +7163,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 315
+            "y": 263
           },
           "id": 161,
           "options": {
@@ -6892,7 +7269,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 324
+            "y": 272
           },
           "id": 139,
           "options": {
@@ -6984,7 +7361,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 324
+            "y": 272
           },
           "id": 141,
           "options": {
@@ -7076,7 +7453,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 332
+            "y": 281
           },
           "id": 142,
           "options": {
@@ -7168,7 +7545,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 332
+            "y": 281
           },
           "id": 143,
           "options": {
@@ -7260,7 +7637,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 340
+            "y": 290
           },
           "id": 144,
           "options": {
@@ -7352,7 +7729,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 340
+            "y": 290
           },
           "id": 145,
           "options": {
@@ -7444,7 +7821,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 348
+            "y": 299
           },
           "id": 146,
           "options": {
@@ -7536,7 +7913,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 348
+            "y": 299
           },
           "id": 147,
           "options": {
@@ -7628,7 +8005,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 356
+            "y": 308
           },
           "id": 148,
           "options": {
@@ -7720,7 +8097,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 356
+            "y": 308
           },
           "id": 149,
           "options": {
@@ -7812,7 +8189,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 364
+            "y": 317
           },
           "id": 150,
           "options": {
@@ -7904,7 +8281,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 364
+            "y": 317
           },
           "id": 151,
           "options": {
@@ -7995,7 +8372,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 372
+            "y": 326
           },
           "id": 153,
           "options": {
@@ -8086,7 +8463,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 372
+            "y": 326
           },
           "id": 154,
           "options": {
@@ -8177,7 +8554,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 380
+            "y": 334
           },
           "id": 155,
           "options": {
@@ -8282,7 +8659,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 389
+            "y": 343
           },
           "id": 112,
           "options": {
@@ -8373,7 +8750,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 389
+            "y": 343
           },
           "id": 116,
           "options": {
@@ -8479,7 +8856,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 398
+            "y": 352
           },
           "id": 125,
           "options": {
@@ -8572,7 +8949,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 398
+            "y": 352
           },
           "id": 126,
           "options": {
@@ -8665,7 +9042,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 406
+            "y": 360
           },
           "id": 163,
           "options": {
@@ -8758,7 +9135,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 406
+            "y": 360
           },
           "id": 162,
           "options": {
@@ -8851,7 +9228,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 414
+            "y": 368
           },
           "id": 127,
           "options": {
@@ -8957,7 +9334,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 423
+            "y": 377
           },
           "id": 174,
           "options": {
@@ -9050,7 +9427,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 423
+            "y": 377
           },
           "id": 176,
           "options": {
@@ -9142,7 +9519,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 431
+            "y": 386
           },
           "id": 175,
           "options": {
@@ -9235,7 +9612,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 431
+            "y": 386
           },
           "id": 177,
           "options": {
@@ -9305,7 +9682,7 @@
     ]
   },
   "time": {
-    "from": "now-5m",
+    "from": "now-30m",
     "to": "now"
   },
   "timepicker": {},
@@ -9314,4 +9691,4 @@
   "uid": "U_qgru_7z",
   "version": 1,
   "weekStart": ""
-}
+}
\ No newline at end of file
diff --git a/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java b/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java
index 99b131a48..6b4337929 100644
--- a/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java
+++ b/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java
@@ -444,7 +444,10 @@ public abstract class CelebornInputStream extends InputStream {
       switch (storageInfo.getType()) {
         case HDD:
         case SSD:
-          if (enabledReadLocalShuffle && location.getHost().equals(localHostAddress)) {
+        case MEMORY:
+          if (enabledReadLocalShuffle
+              && location.getHost().equals(localHostAddress)
+              && storageInfo.getType() != StorageInfo.Type.MEMORY) {
             logger.debug("Read local shuffle file {}", localHostAddress);
             containLocalRead = true;
             return new LocalPartitionReader(
diff --git a/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java b/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java
index 38306f6c2..712bd82e4 100644
--- a/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java
+++ b/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java
@@ -167,7 +167,8 @@ public class DfsPartitionReader implements PartitionReader {
                   startMapIndex,
                   endMapIndex,
                   shuffleChunkSize,
-                  ShuffleBlockInfoUtils.parseShuffleBlockInfosFromByteBuffer(indexBuffer)));
+                  ShuffleBlockInfoUtils.parseShuffleBlockInfosFromByteBuffer(indexBuffer),
+                  false));
     }
     return offsets;
   }
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/DiskFileInfo.java b/common/src/main/java/org/apache/celeborn/common/meta/DiskFileInfo.java
index ab10df2ec..ce99192c6 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/DiskFileInfo.java
+++ b/common/src/main/java/org/apache/celeborn/common/meta/DiskFileInfo.java
@@ -20,14 +20,14 @@ package org.apache.celeborn.common.meta;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
+import com.google.common.annotations.VisibleForTesting;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.identity.UserIdentifier;
 import org.apache.celeborn.common.protocol.StorageInfo;
 import org.apache.celeborn.common.util.Utils;
@@ -36,11 +36,9 @@ import org.apache.celeborn.common.util.Utils;
  * This class will describe shuffle files located on local disks, HDFS and other remote storage.
  * */
 public class DiskFileInfo extends FileInfo {
-  private static Logger logger = LoggerFactory.getLogger(DiskFileInfo.class);
-  private final Set<Long> streams = ConcurrentHashMap.newKeySet();
-  private String filePath;
-  private StorageInfo.Type storageType;
-  private volatile long bytesFlushed;
+  private static final Logger logger = LoggerFactory.getLogger(DiskFileInfo.class);
+  private final String filePath;
+  private final StorageInfo.Type storageType;
 
   public DiskFileInfo(
       UserIdentifier userIdentifier,
@@ -66,11 +64,12 @@ public class DiskFileInfo extends FileInfo {
     this.bytesFlushed = bytesFlushed;
   }
 
-  public DiskFileInfo(File file, UserIdentifier userIdentifier) {
+  @VisibleForTesting
+  public DiskFileInfo(File file, UserIdentifier userIdentifier, CelebornConf conf) {
     this(
         userIdentifier,
         true,
-        new ReduceFileMeta(new ArrayList(Arrays.asList(0L))),
+        new ReduceFileMeta(new ArrayList<>(Arrays.asList(0L)), conf.shuffleChunkSize()),
         file.getAbsolutePath(),
         StorageInfo.Type.HDD);
   }
@@ -81,42 +80,6 @@ public class DiskFileInfo extends FileInfo {
     this.storageType = StorageInfo.Type.HDD;
   }
 
-  public boolean addStream(long streamId) {
-    ReduceFileMeta reduceFileMeta = (ReduceFileMeta) fileMeta;
-    synchronized (reduceFileMeta.getSorted()) {
-      if (reduceFileMeta.getSorted().get()) {
-        return false;
-      } else {
-        streams.add(streamId);
-        return true;
-      }
-    }
-  }
-
-  public void closeStream(long streamId) {
-    ReduceFileMeta reduceFileMeta = (ReduceFileMeta) fileMeta;
-    synchronized (reduceFileMeta.getSorted()) {
-      streams.remove(streamId);
-    }
-  }
-
-  public boolean isStreamsEmpty() {
-    ReduceFileMeta reduceFileMeta = (ReduceFileMeta) fileMeta;
-    synchronized (reduceFileMeta.getSorted()) {
-      return streams.isEmpty();
-    }
-  }
-
-  @Override
-  public long getFileLength() {
-    return bytesFlushed;
-  }
-
-  public long updateBytesFlushed(long numBytes) {
-    bytesFlushed += numBytes;
-    return bytesFlushed;
-  }
-
   public File getFile() {
     return new File(filePath);
   }
@@ -185,10 +148,6 @@ public class DiskFileInfo extends FileInfo {
     ((MapFileMeta) fileMeta).setMountPoint(mountPoint);
   }
 
-  public long getBytesFlushed() {
-    return bytesFlushed;
-  }
-
   public boolean isHdfs() {
     return Utils.isHdfsPath(filePath);
   }
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/FileInfo.java b/common/src/main/java/org/apache/celeborn/common/meta/FileInfo.java
index 34ba7cb13..c21c19847 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/FileInfo.java
+++ b/common/src/main/java/org/apache/celeborn/common/meta/FileInfo.java
@@ -17,6 +17,9 @@
 
 package org.apache.celeborn.common.meta;
 
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.celeborn.common.identity.UserIdentifier;
 
 public abstract class FileInfo {
@@ -25,22 +28,40 @@ public abstract class FileInfo {
   // now it's just used for mappartition to compatible with old client which can't support split
   private boolean partitionSplitEnabled;
   protected FileMeta fileMeta;
+  protected final Set<Long> streams = ConcurrentHashMap.newKeySet();
+  protected volatile long bytesFlushed;
+  private boolean isReduceFileMeta;
 
   public FileInfo(UserIdentifier userIdentifier, boolean partitionSplitEnabled, FileMeta fileMeta) {
     this.userIdentifier = userIdentifier;
     this.partitionSplitEnabled = partitionSplitEnabled;
     this.fileMeta = fileMeta;
+    this.isReduceFileMeta = fileMeta instanceof ReduceFileMeta;
   }
 
   public void replaceFileMeta(FileMeta meta) {
     this.fileMeta = meta;
+    this.isReduceFileMeta = meta instanceof ReduceFileMeta;
   }
 
   public FileMeta getFileMeta() {
     return fileMeta;
   }
 
-  public abstract long getFileLength();
+  public ReduceFileMeta getReduceFileMeta() {
+    return (ReduceFileMeta) fileMeta;
+  }
+
+  public long getFileLength() {
+    return bytesFlushed;
+  }
+
+  public void updateBytesFlushed(long bytes) {
+    bytesFlushed += bytes;
+    if (isReduceFileMeta) {
+      getReduceFileMeta().updateChunkOffset(bytesFlushed, false);
+    }
+  }
 
   public UserIdentifier getUserIdentifier() {
     return userIdentifier;
@@ -53,4 +74,36 @@ public abstract class FileInfo {
   public void setPartitionSplitEnabled(boolean partitionSplitEnabled) {
     this.partitionSplitEnabled = partitionSplitEnabled;
   }
+
+  public boolean addStream(long streamId) {
+    if (!isReduceFileMeta) {
+      throw new IllegalStateException("In addStream, filemeta cannot be MapFileMeta");
+    }
+    synchronized (getReduceFileMeta().getSorted()) {
+      if (getReduceFileMeta().getSorted().get()) {
+        return false;
+      } else {
+        streams.add(streamId);
+        return true;
+      }
+    }
+  }
+
+  public void closeStream(long streamId) {
+    if (!isReduceFileMeta) {
+      throw new IllegalStateException("In closeStream, filemeta cannot be MapFileMeta");
+    }
+    synchronized (getReduceFileMeta().getSorted()) {
+      streams.remove(streamId);
+    }
+  }
+
+  public boolean isStreamsEmpty() {
+    if (!isReduceFileMeta) {
+      throw new IllegalStateException("In isStreamsEmpty, filemeta cannot be MapFileMeta");
+    }
+    synchronized (getReduceFileMeta().getSorted()) {
+      return streams.isEmpty();
+    }
+  }
 }
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java b/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
index e58263bdf..f75826b84 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
+++ b/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
@@ -17,19 +17,36 @@
 
 package org.apache.celeborn.common.meta;
 
+import java.util.*;
+
 import io.netty.buffer.CompositeByteBuf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.identity.UserIdentifier;
+import org.apache.celeborn.common.util.ShuffleBlockInfoUtils.ShuffleBlockInfo;
 
 public class MemoryFileInfo extends FileInfo {
+  Logger logger = LoggerFactory.getLogger(MemoryFileInfo.class);
   private CompositeByteBuf buffer;
-  private long length;
+  private CompositeByteBuf sortedBuffer;
+  private Map<Integer, List<ShuffleBlockInfo>> sortedIndexes;
 
   public MemoryFileInfo(
       UserIdentifier userIdentifier, boolean partitionSplitEnabled, FileMeta fileMeta) {
     super(userIdentifier, partitionSplitEnabled, fileMeta);
   }
 
+  // This constructor is only used in partition sorter for temp new memory file
+  public MemoryFileInfo(
+      UserIdentifier userIdentifier,
+      boolean partitionSplitEnabled,
+      FileMeta fileMeta,
+      CompositeByteBuf buffer) {
+    super(userIdentifier, partitionSplitEnabled, fileMeta);
+    this.buffer = buffer;
+  }
+
   public CompositeByteBuf getBuffer() {
     return buffer;
   }
@@ -38,12 +55,29 @@ public class MemoryFileInfo extends FileInfo {
     this.buffer = buffer;
   }
 
-  public void setBufferSize(int bufferSize) {
-    this.length = bufferSize;
+  public CompositeByteBuf getSortedBuffer() {
+    return sortedBuffer;
+  }
+
+  public void setSortedBuffer(CompositeByteBuf sortedBuffer) {
+    this.sortedBuffer = sortedBuffer;
+  }
+
+  public Map<Integer, List<ShuffleBlockInfo>> getSortedIndexes() {
+    return sortedIndexes;
+  }
+
+  public void setSortedIndexes(Map<Integer, List<ShuffleBlockInfo>> sortedIndexes) {
+    this.sortedIndexes = sortedIndexes;
   }
 
-  @Override
-  public long getFileLength() {
-    return length;
+  public int releaseMemoryBuffers() {
+    int bufferSize = 0;
+    if (buffer != null) {
+      bufferSize = buffer.writerIndex();
+      buffer.release();
+    }
+    logger.info("Memory File Info {} expire, removed {}", this, bufferSize);
+    return bufferSize;
   }
 }
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/ReduceFileMeta.java b/common/src/main/java/org/apache/celeborn/common/meta/ReduceFileMeta.java
index ab782cd9c..abc449881 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/ReduceFileMeta.java
+++ b/common/src/main/java/org/apache/celeborn/common/meta/ReduceFileMeta.java
@@ -22,12 +22,25 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class ReduceFileMeta implements FileMeta {
-  private final List<Long> chunkOffsets;
   private final AtomicBoolean sorted = new AtomicBoolean(false);
+  private final List<Long> chunkOffsets;
+  private long chunkSize;
+  private long nextBoundary;
 
-  public ReduceFileMeta() {
+  public ReduceFileMeta(long chunkSize) {
     this.chunkOffsets = new ArrayList<>();
     chunkOffsets.add(0L);
+    this.chunkSize = chunkSize;
+    nextBoundary = chunkSize;
+  }
+
+  public ReduceFileMeta(List<Long> chunkOffsets, long chunkSize) {
+    this.chunkOffsets = chunkOffsets;
+    nextBoundary = chunkSize;
+    if (!chunkOffsets.isEmpty()) {
+      nextBoundary += chunkOffsets.get(chunkOffsets.size() - 1);
+    }
+    this.chunkSize = chunkSize;
   }
 
   public ReduceFileMeta(List<Long> chunkOffsets) {
@@ -39,7 +52,16 @@ public class ReduceFileMeta implements FileMeta {
   }
 
   public synchronized void addChunkOffset(long offset) {
-    chunkOffsets.add(offset);
+    nextBoundary = offset + chunkSize;
+    if (chunkOffsets.isEmpty() || chunkOffsets.get(chunkOffsets.size() - 1) != offset) {
+      chunkOffsets.add(offset);
+    }
+  }
+
+  public void updateChunkOffset(long bytesFlushed, boolean force) {
+    if (bytesFlushed >= nextBoundary || force) {
+      addChunkOffset(bytesFlushed);
+    }
   }
 
   public synchronized long getLastChunkOffset() {
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/FileManagedBuffers.java b/common/src/main/java/org/apache/celeborn/common/network/buffer/ChunkBuffers.java
similarity index 61%
rename from common/src/main/java/org/apache/celeborn/common/meta/FileManagedBuffers.java
rename to common/src/main/java/org/apache/celeborn/common/network/buffer/ChunkBuffers.java
index 52c91ee4f..f4049953b 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/FileManagedBuffers.java
+++ b/common/src/main/java/org/apache/celeborn/common/network/buffer/ChunkBuffers.java
@@ -15,25 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.common.meta;
+package org.apache.celeborn.common.network.buffer;
 
-import java.io.File;
 import java.util.List;
 
-import org.apache.celeborn.common.network.buffer.FileSegmentManagedBuffer;
-import org.apache.celeborn.common.network.buffer.ManagedBuffer;
-import org.apache.celeborn.common.network.util.TransportConf;
+import scala.Tuple2;
 
-public class FileManagedBuffers {
-  private final File file;
-  private final long[] offsets;
-  private final int numChunks;
+import com.google.common.base.Preconditions;
 
-  private final TransportConf conf;
+import org.apache.celeborn.common.meta.ReduceFileMeta;
 
-  public FileManagedBuffers(DiskFileInfo fileInfo, TransportConf conf) {
-    file = fileInfo.getFile();
-    ReduceFileMeta reduceFileMeta = (ReduceFileMeta) fileInfo.getFileMeta();
+public abstract class ChunkBuffers {
+  protected long[] offsets;
+  protected int numChunks;
+
+  public int numChunks() {
+    return numChunks;
+  }
+
+  public ChunkBuffers(ReduceFileMeta reduceFileMeta) {
     numChunks = reduceFileMeta.getNumChunks();
     if (numChunks > 0) {
       offsets = new long[numChunks + 1];
@@ -44,19 +44,15 @@ public class FileManagedBuffers {
     } else {
       offsets = new long[] {0};
     }
-    this.conf = conf;
   }
 
-  public int numChunks() {
-    return numChunks;
-  }
-
-  public ManagedBuffer chunk(int chunkIndex, int offset, int len) {
-    // offset of the beginning of the chunk in the file
+  public Tuple2<Long, Long> getChunkOffsetLength(int chunkIndex, int offset, int len) {
     final long chunkOffset = offsets[chunkIndex];
     final long chunkLength = offsets[chunkIndex + 1] - chunkOffset;
-    assert offset < chunkLength;
+    Preconditions.checkArgument(offset < chunkLength);
     long length = Math.min(chunkLength - offset, len);
-    return new FileSegmentManagedBuffer(conf, file, chunkOffset + offset, length);
+    return new Tuple2<>(chunkOffset + offset, length);
   }
+
+  public abstract ManagedBuffer chunk(int chunkIndex, int offset, int len);
 }
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java b/common/src/main/java/org/apache/celeborn/common/network/buffer/FileChunkBuffers.java
similarity index 53%
copy from common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
copy to common/src/main/java/org/apache/celeborn/common/network/buffer/FileChunkBuffers.java
index e58263bdf..ed4969d3a 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
+++ b/common/src/main/java/org/apache/celeborn/common/network/buffer/FileChunkBuffers.java
@@ -15,35 +15,28 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.common.meta;
+package org.apache.celeborn.common.network.buffer;
 
-import io.netty.buffer.CompositeByteBuf;
+import java.io.File;
 
-import org.apache.celeborn.common.identity.UserIdentifier;
+import scala.Tuple2;
 
-public class MemoryFileInfo extends FileInfo {
-  private CompositeByteBuf buffer;
-  private long length;
+import org.apache.celeborn.common.meta.DiskFileInfo;
+import org.apache.celeborn.common.network.util.TransportConf;
 
-  public MemoryFileInfo(
-      UserIdentifier userIdentifier, boolean partitionSplitEnabled, FileMeta fileMeta) {
-    super(userIdentifier, partitionSplitEnabled, fileMeta);
-  }
-
-  public CompositeByteBuf getBuffer() {
-    return buffer;
-  }
-
-  public void setBuffer(CompositeByteBuf buffer) {
-    this.buffer = buffer;
-  }
+public class FileChunkBuffers extends ChunkBuffers {
+  private final File file;
+  private final TransportConf conf;
 
-  public void setBufferSize(int bufferSize) {
-    this.length = bufferSize;
+  public FileChunkBuffers(DiskFileInfo fileInfo, TransportConf conf) {
+    super(fileInfo.getReduceFileMeta());
+    file = fileInfo.getFile();
+    this.conf = conf;
   }
 
   @Override
-  public long getFileLength() {
-    return length;
+  public ManagedBuffer chunk(int chunkIndex, int offset, int len) {
+    Tuple2<Long, Long> offsetLen = getChunkOffsetLength(chunkIndex, offset, len);
+    return new FileSegmentManagedBuffer(conf, file, offsetLen._1, offsetLen._2);
   }
 }
diff --git a/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java b/common/src/main/java/org/apache/celeborn/common/network/buffer/MemoryChunkBuffers.java
similarity index 55%
copy from common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
copy to common/src/main/java/org/apache/celeborn/common/network/buffer/MemoryChunkBuffers.java
index e58263bdf..ec2d7286f 100644
--- a/common/src/main/java/org/apache/celeborn/common/meta/MemoryFileInfo.java
+++ b/common/src/main/java/org/apache/celeborn/common/network/buffer/MemoryChunkBuffers.java
@@ -15,35 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.common.meta;
+package org.apache.celeborn.common.network.buffer;
 
-import io.netty.buffer.CompositeByteBuf;
-
-import org.apache.celeborn.common.identity.UserIdentifier;
+import scala.Tuple2;
 
-public class MemoryFileInfo extends FileInfo {
-  private CompositeByteBuf buffer;
-  private long length;
-
-  public MemoryFileInfo(
-      UserIdentifier userIdentifier, boolean partitionSplitEnabled, FileMeta fileMeta) {
-    super(userIdentifier, partitionSplitEnabled, fileMeta);
-  }
+import io.netty.buffer.CompositeByteBuf;
 
-  public CompositeByteBuf getBuffer() {
-    return buffer;
-  }
+import org.apache.celeborn.common.meta.MemoryFileInfo;
+import org.apache.celeborn.common.meta.ReduceFileMeta;
 
-  public void setBuffer(CompositeByteBuf buffer) {
-    this.buffer = buffer;
-  }
+public class MemoryChunkBuffers extends ChunkBuffers {
+  private final CompositeByteBuf buffer;
 
-  public void setBufferSize(int bufferSize) {
-    this.length = bufferSize;
+  public MemoryChunkBuffers(MemoryFileInfo memoryFileInfo) {
+    super((ReduceFileMeta) memoryFileInfo.getFileMeta());
+    buffer = memoryFileInfo.getBuffer();
   }
 
   @Override
-  public long getFileLength() {
-    return length;
+  public ManagedBuffer chunk(int chunkIndex, int offset, int len) {
+    Tuple2<Long, Long> offsetLen = getChunkOffsetLength(chunkIndex, offset, len);
+    return new NettyManagedBuffer(
+        buffer.retainedSlice(offsetLen._1.intValue(), offsetLen._2.intValue()));
   }
 }
diff --git a/common/src/main/java/org/apache/celeborn/common/protocol/StorageInfo.java b/common/src/main/java/org/apache/celeborn/common/protocol/StorageInfo.java
index 5b59a10ed..d92909128 100644
--- a/common/src/main/java/org/apache/celeborn/common/protocol/StorageInfo.java
+++ b/common/src/main/java/org/apache/celeborn/common/protocol/StorageInfo.java
@@ -38,8 +38,8 @@ public class StorageInfo implements Serializable {
     }
   }
 
-  public static Map<Integer, Type> typesMap = new HashMap<>();
-  public static Set<String> typeNames = new HashSet<>();
+  public static final Map<Integer, Type> typesMap = new HashMap<>();
+  public static final Set<String> typeNames = new HashSet<>();
 
   static {
     for (Type type : Type.values()) {
@@ -129,6 +129,11 @@ public class StorageInfo implements Serializable {
         + '}';
   }
 
+  public boolean memoryAvailable() {
+    return availableStorageTypes == ALL_TYPES_AVAILABLE_MASK
+        || (availableStorageTypes & MEMORY_MASK) > 0;
+  }
+
   public static boolean localDiskAvailable(int availableStorageTypes) {
     return availableStorageTypes == ALL_TYPES_AVAILABLE_MASK
         || (availableStorageTypes & LOCAL_DISK_MASK) > 0;
@@ -181,7 +186,7 @@ public class StorageInfo implements Serializable {
     return Objects.hash(type, mountPoint, finalResult, filePath, availableStorageTypes);
   }
 
-  public static final boolean validate(String typeStr) {
+  public static boolean validate(String typeStr) {
     return typeNames.contains(typeStr);
   }
 
diff --git a/common/src/main/java/org/apache/celeborn/common/util/ShuffleBlockInfoUtils.java b/common/src/main/java/org/apache/celeborn/common/util/ShuffleBlockInfoUtils.java
index 193b8d3bc..a7f25cc48 100644
--- a/common/src/main/java/org/apache/celeborn/common/util/ShuffleBlockInfoUtils.java
+++ b/common/src/main/java/org/apache/celeborn/common/util/ShuffleBlockInfoUtils.java
@@ -23,6 +23,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.CompositeByteBuf;
+
 public class ShuffleBlockInfoUtils {
 
   public static class ShuffleBlockInfo {
@@ -34,7 +37,8 @@ public class ShuffleBlockInfoUtils {
       int startMapIndex,
       int endMapIndex,
       long fetchChunkSize,
-      Map<Integer, List<ShuffleBlockInfo>> indexMap) {
+      Map<Integer, List<ShuffleBlockInfo>> indexMap,
+      boolean isInMemory) {
     List<Long> sortedChunkOffset = new ArrayList<>();
     ShuffleBlockInfo lastBlock = null;
     int maxMapIndex = endMapIndex;
@@ -43,24 +47,49 @@ public class ShuffleBlockInfoUtils {
       maxMapIndex = indexMap.keySet().stream().max(Integer::compareTo).get() + 1;
     }
 
-    for (int i = startMapIndex; i < maxMapIndex; i++) {
-      List<ShuffleBlockInfo> blockInfos = indexMap.get(i);
-      if (blockInfos != null) {
-        for (ShuffleBlockInfo info : blockInfos) {
-          if (sortedChunkOffset.size() == 0) {
-            sortedChunkOffset.add(info.offset);
+    if (isInMemory) {
+      long currentChunkOffset = 0;
+      long lastChunkOffset = 0;
+      // This sorted chunk offsets are used for fetch handler.
+      // Sorted byte buf is a new composite byte buf containing the required data.
+      // It will not reuse the old buffer of memory file, so the offset starts from 0.
+      sortedChunkOffset.add(0l);
+      for (int i = startMapIndex; i < maxMapIndex; i++) {
+        List<ShuffleBlockInfo> blockInfos = indexMap.get(i);
+        if (blockInfos != null) {
+          for (ShuffleBlockInfo info : blockInfos) {
+            currentChunkOffset += info.length;
+            if (currentChunkOffset - lastChunkOffset > fetchChunkSize) {
+              lastChunkOffset = currentChunkOffset;
+              sortedChunkOffset.add(currentChunkOffset);
+            }
           }
-          if (info.offset - sortedChunkOffset.get(sortedChunkOffset.size() - 1) >= fetchChunkSize) {
-            sortedChunkOffset.add(info.offset);
+        }
+      }
+      if (lastChunkOffset != currentChunkOffset) {
+        sortedChunkOffset.add(currentChunkOffset);
+      }
+    } else {
+      for (int i = startMapIndex; i < maxMapIndex; i++) {
+        List<ShuffleBlockInfo> blockInfos = indexMap.get(i);
+        if (blockInfos != null) {
+          for (ShuffleBlockInfo info : blockInfos) {
+            if (sortedChunkOffset.size() == 0) {
+              sortedChunkOffset.add(info.offset);
+            }
+            if (info.offset - sortedChunkOffset.get(sortedChunkOffset.size() - 1)
+                >= fetchChunkSize) {
+              sortedChunkOffset.add(info.offset);
+            }
+            lastBlock = info;
           }
-          lastBlock = info;
         }
       }
-    }
-    if (lastBlock != null) {
-      long endChunkOffset = lastBlock.length + lastBlock.offset;
-      if (!sortedChunkOffset.contains(endChunkOffset)) {
-        sortedChunkOffset.add(endChunkOffset);
+      if (lastBlock != null) {
+        long endChunkOffset = lastBlock.length + lastBlock.offset;
+        if (!sortedChunkOffset.contains(endChunkOffset)) {
+          sortedChunkOffset.add(endChunkOffset);
+        }
       }
     }
     return sortedChunkOffset;
@@ -90,4 +119,23 @@ public class ShuffleBlockInfoUtils {
     }
     return indexMap;
   }
+
+  public static void sliceSortedBufferByMapRange(
+      int startMapIndex,
+      int endMapIndex,
+      Map<Integer, List<ShuffleBlockInfo>> indexMap,
+      CompositeByteBuf sortedByteBuf,
+      CompositeByteBuf targetByteBuf) {
+    for (int i = startMapIndex; i < endMapIndex; i++) {
+      List<ShuffleBlockInfo> blockInfos = indexMap.get(i);
+      if (blockInfos != null) {
+        for (ShuffleBlockInfo blockInfo : blockInfos) {
+          ByteBuf slice = sortedByteBuf.slice((int) blockInfo.offset, (int) blockInfo.length);
+          // Do not retain this buffer because this buffer
+          // will be released when the fileinfo is released
+          targetByteBuf.addComponent(slice);
+        }
+      }
+    }
+  }
 }
diff --git a/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala b/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
index 13eaed0b9..62cb30b4d 100644
--- a/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
+++ b/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
@@ -636,7 +636,7 @@ class CelebornConf(loadDefaults: Boolean) extends Cloneable with Logging with Se
   def masterSlotAssignPolicy: SlotsAssignPolicy =
     SlotsAssignPolicy.valueOf(get(MASTER_SLOT_ASSIGN_POLICY))
   def availableStorageTypes: Int = {
-    val types = get(ACTIVE_STORAGE_TYPES).split(",").map(StorageInfo.Type.valueOf(_)).toList
+    val types = get(ACTIVE_STORAGE_TYPES).split(",").map(StorageInfo.Type.valueOf).toList
     StorageInfo.getAvailableTypes(types.asJava)
   }
   def hasHDFSStorage: Boolean =
@@ -1200,8 +1200,10 @@ class CelebornConf(loadDefaults: Boolean) extends Cloneable with Logging with Se
     get(WORKER_DIRECT_MEMORY_TRIM_CHANNEL_WAIT_INTERVAL)
   def workerDirectMemoryTrimFlushWaitInterval: Long =
     get(WORKER_DIRECT_MEMORY_TRIM_FLUSH_WAIT_INTERVAL)
-  def workerDirectMemoryRatioForShuffleStorage: Double =
-    get(WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE)
+  def workerDirectMemoryRatioForMemoryFilesStorage: Double =
+    get(WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE)
+  def workerMemoryFileStorageMaxFileSize: Long =
+    get(WORKER_MEMORY_FILE_STORAGE_MAX_FILE_SIZE)
 
   // //////////////////////////////////////////////////////
   //                  Rate Limit controller              //
@@ -2583,6 +2585,7 @@ object CelebornConf extends Logging {
       .doc("Max chunk size of reducer's merged shuffle data. For example, if a reducer's " +
         "shuffle data is 128M and the data will need 16 fetch chunk requests to fetch.")
       .bytesConf(ByteUnit.BYTE)
+      .checkValue(v => v < Integer.MAX_VALUE, "Chunk size can not be larger than 2GB")
       .createWithDefaultString("8m")
 
   val CLIENT_FETCH_DFS_READ_CHUNK_SIZE: ConfigEntry[Long] =
@@ -3192,13 +3195,14 @@ object CelebornConf extends Logging {
       .doubleConf
       .createWithDefault(0.1)
 
-  val WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE: ConfigEntry[Double] =
-    buildConf("celeborn.worker.directMemoryRatioForMemoryShuffleStorage")
+  val WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE: ConfigEntry[Double] =
+    buildConf("celeborn.worker.directMemoryRatioForMemoryFileStorage")
       .categories("worker")
-      .doc("Max ratio of direct memory to store shuffle data")
-      .version("0.2.0")
+      .doc("Max ratio of direct memory to store shuffle data. " +
+        "This feature is experimental and disabled by default.")
+      .version("0.5.0")
       .doubleConf
-      .createWithDefault(0.0)
+      .createWithDefault(0)
 
   val WORKER_DIRECT_MEMORY_RATIO_PAUSE_RECEIVE: ConfigEntry[Double] =
     buildConf("celeborn.worker.directMemoryRatioToPauseReceive")
@@ -3225,6 +3229,15 @@ object CelebornConf extends Logging {
       .doubleConf
       .createWithDefault(0.7)
 
+  val WORKER_MEMORY_FILE_STORAGE_MAX_FILE_SIZE: ConfigEntry[Long] =
+    buildConf("celeborn.worker.memoryFileStorage.maxFileSize")
+      .categories("worker")
+      .doc("Max size for a memory storage file. It must be less than 2GB.")
+      .version("0.5.0")
+      .bytesConf(ByteUnit.BYTE)
+      .checkValue(v => v < Int.MaxValue, "A single memory storage file can not be larger than 2GB")
+      .createWithDefaultString("8MB")
+
   val WORKER_CONGESTION_CONTROL_ENABLED: ConfigEntry[Boolean] =
     buildConf("celeborn.worker.congestionControl.enabled")
       .categories("worker")
@@ -4793,7 +4806,7 @@ object CelebornConf extends Logging {
         "Enabled storages. Available options: MEMORY,HDD,SSD,HDFS. Note: HDD and SSD would be treated as identical.")
       .stringConf
       .transform(_.toUpperCase(Locale.ROOT))
-      .checkValue(p => p.split(",").map(StorageInfo.validate(_)).reduce(_ && _), "")
+      .checkValue(p => p.split(",").map(StorageInfo.validate).reduce(_ && _), "")
       .createWithDefault("HDD")
 
   val READ_LOCAL_SHUFFLE_FILE: ConfigEntry[Boolean] =
diff --git a/common/src/test/scala/org/apache/celeborn/common/network/CelebornRackResolverSuite.scala b/common/src/test/scala/org/apache/celeborn/common/network/CelebornRackResolverSuite.scala
index 4b46b2602..e7a0f8e40 100644
--- a/common/src/test/scala/org/apache/celeborn/common/network/CelebornRackResolverSuite.scala
+++ b/common/src/test/scala/org/apache/celeborn/common/network/CelebornRackResolverSuite.scala
@@ -135,7 +135,6 @@ class CelebornRackResolverSuite extends AnyFunSuite {
          |$hostName2 /default/rack2
          |$hostName3 /default/rack1
          |""".stripMargin)
-
     Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS)
     assertEquals("/default/rack1", resolver.resolve(hostName3).getNetworkLocation)
     assertEquals(false, resolver.isOnSameRack(hostName1, hostName2))
diff --git a/common/src/test/scala/org/apache/celeborn/common/util/PbSerDeUtilsTest.scala b/common/src/test/scala/org/apache/celeborn/common/util/PbSerDeUtilsTest.scala
index 840b9896d..a9ad63e27 100644
--- a/common/src/test/scala/org/apache/celeborn/common/util/PbSerDeUtilsTest.scala
+++ b/common/src/test/scala/org/apache/celeborn/common/util/PbSerDeUtilsTest.scala
@@ -66,13 +66,13 @@ class PbSerDeUtilsTest extends CelebornFunSuite {
   val fileInfo1 = new DiskFileInfo(
     userIdentifier1,
     true,
-    new ReduceFileMeta(chunkOffsets1),
+    new ReduceFileMeta(chunkOffsets1, 123),
     file1.getAbsolutePath,
     3000L)
   val fileInfo2 = new DiskFileInfo(
     userIdentifier2,
     true,
-    new ReduceFileMeta(chunkOffsets2),
+    new ReduceFileMeta(chunkOffsets2, 123),
     file2.getAbsolutePath,
     6000L)
   val fileInfoMap = JavaUtils.newConcurrentHashMap[String, DiskFileInfo]()
diff --git a/docs/configuration/worker.md b/docs/configuration/worker.md
index 93cbae431..42d4ec6ed 100644
--- a/docs/configuration/worker.md
+++ b/docs/configuration/worker.md
@@ -58,7 +58,7 @@ license: |
 | celeborn.worker.congestionControl.user.inactive.interval | 10min | false | How long will consider this user is inactive if it doesn't send data | 0.3.0 |  | 
 | celeborn.worker.decommission.checkInterval | 30s | false | The wait interval of checking whether all the shuffle expired during worker decommission | 0.4.0 |  | 
 | celeborn.worker.decommission.forceExitTimeout | 6h | false | The wait time of waiting for all the shuffle expire during worker decommission. | 0.4.0 |  | 
-| celeborn.worker.directMemoryRatioForMemoryShuffleStorage | 0.0 | false | Max ratio of direct memory to store shuffle data | 0.2.0 |  | 
+| celeborn.worker.directMemoryRatioForMemoryFileStorage | 0.0 | false | Max ratio of direct memory to store shuffle data. This feature is experimental and disabled by default. | 0.5.0 |  | 
 | celeborn.worker.directMemoryRatioForReadBuffer | 0.1 | false | Max ratio of direct memory for read buffer | 0.2.0 |  | 
 | celeborn.worker.directMemoryRatioToPauseReceive | 0.85 | false | If direct memory usage reaches this limit, the worker will stop to receive data from Celeborn shuffle clients. | 0.2.0 |  | 
 | celeborn.worker.directMemoryRatioToPauseReplicate | 0.95 | false | If direct memory usage reaches this limit, the worker will stop to receive replication data from other workers. This value should be higher than celeborn.worker.directMemoryRatioToPauseReceive. | 0.2.0 |  | 
@@ -101,6 +101,7 @@ license: |
 | celeborn.worker.jvmQuake.exitCode | 502 | false | The exit code of system kill for the maximum GC 'deficit' during worker jvm quake. | 0.4.0 |  | 
 | celeborn.worker.jvmQuake.kill.threshold | 60s | false | The threshold of system kill for the maximum GC 'deficit' which can be accumulated before jvmquake takes action. | 0.4.0 |  | 
 | celeborn.worker.jvmQuake.runtimeWeight | 5.0 | false | The factor by which to multiply running JVM time, when weighing it against GCing time. 'Deficit' is accumulated as `gc_time - runtime * runtime_weight`, and is compared against threshold to determine whether to take action. | 0.4.0 |  | 
+| celeborn.worker.memoryFileStorage.maxFileSize | 8MB | false | Max size for a memory storage file. It must be less than 2GB. | 0.5.0 |  | 
 | celeborn.worker.monitor.disk.check.interval | 30s | false | Intervals between device monitor to check disk. | 0.3.0 | celeborn.worker.monitor.disk.checkInterval | 
 | celeborn.worker.monitor.disk.check.timeout | 30s | false | Timeout time for worker check device status. | 0.3.0 | celeborn.worker.disk.check.timeout | 
 | celeborn.worker.monitor.disk.checklist | readwrite,diskusage | false | Monitor type for disk, available items are: iohang, readwrite and diskusage. | 0.2.0 |  | 
diff --git a/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemorySkewJoinSuite.scala b/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemorySkewJoinSuite.scala
new file mode 100644
index 000000000..d8ae35144
--- /dev/null
+++ b/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemorySkewJoinSuite.scala
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.tests.spark.memory
+
+import scala.util.Random
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.internal.SQLConf
+import org.scalatest.BeforeAndAfterEach
+import org.scalatest.funsuite.AnyFunSuite
+
+import org.apache.celeborn.client.ShuffleClient
+import org.apache.celeborn.common.CelebornConf
+import org.apache.celeborn.common.protocol.CompressionCodec
+
+class MemorySkewJoinSuite extends AnyFunSuite
+  with MemorySparkTestBase
+  with BeforeAndAfterEach {
+
+  override def beforeEach(): Unit = {
+    ShuffleClient.reset()
+  }
+
+  override def afterEach(): Unit = {
+    System.gc()
+  }
+
+  private def enableCeleborn(conf: SparkConf) = {
+    conf.set("spark.shuffle.manager", "org.apache.spark.shuffle.celeborn.SparkShuffleManager")
+      .set(s"spark.${CelebornConf.MASTER_ENDPOINTS.key}", masterInfo._1.rpcEnv.address.toString)
+      .set(s"spark.${CelebornConf.SHUFFLE_PARTITION_SPLIT_THRESHOLD.key}", "10MB")
+  }
+
+  CompressionCodec.values.foreach { codec =>
+    test(s"celeborn spark integration test - skew join - $codec") {
+      val sparkConf = new SparkConf().setAppName("celeborn-demo")
+        .setMaster("local[2]")
+        .set(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key, "true")
+        .set("spark.sql.adaptive.skewJoin.enabled", "true")
+        .set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes", "16MB")
+        .set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "12MB")
+        .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
+        .set("spark.sql.adaptive.autoBroadcastJoinThreshold", "-1")
+        .set(s"spark.celeborn.storage.availableTypes", "HDD,MEMORY")
+        .set(SQLConf.PARQUET_COMPRESSION.key, "gzip")
+        .set(s"spark.${CelebornConf.SHUFFLE_COMPRESSION_CODEC.key}", codec.name)
+        .set(s"spark.${CelebornConf.SHUFFLE_RANGE_READ_FILTER_ENABLED.key}", "true")
+
+      enableCeleborn(sparkConf)
+
+      val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate()
+      if (sparkSession.version.startsWith("3")) {
+        import sparkSession.implicits._
+        val df = sparkSession.sparkContext.parallelize(1 to 120000, 8)
+          .map(i => {
+            val random = new Random()
+            val oriKey = random.nextInt(64)
+            val key = if (oriKey < 32) 1 else oriKey
+            val fas = random.nextInt(1200000)
+            val fa = Range(fas, fas + 100).mkString(",")
+            val fbs = random.nextInt(1200000)
+            val fb = Range(fbs, fbs + 100).mkString(",")
+            val fcs = random.nextInt(1200000)
+            val fc = Range(fcs, fcs + 100).mkString(",")
+            val fds = random.nextInt(1200000)
+            val fd = Range(fds, fds + 100).mkString(",")
+
+            (key, fa, fb, fc, fd)
+          })
+          .toDF("fa", "f1", "f2", "f3", "f4")
+        df.createOrReplaceTempView("view1")
+        val df2 = sparkSession.sparkContext.parallelize(1 to 8, 8)
+          .map(i => {
+            val random = new Random()
+            val oriKey = random.nextInt(64)
+            val key = if (oriKey < 32) 1 else oriKey
+            val fas = random.nextInt(1200000)
+            val fa = Range(fas, fas + 100).mkString(",")
+            val fbs = random.nextInt(1200000)
+            val fb = Range(fbs, fbs + 100).mkString(",")
+            val fcs = random.nextInt(1200000)
+            val fc = Range(fcs, fcs + 100).mkString(",")
+            val fds = random.nextInt(1200000)
+            val fd = Range(fds, fds + 100).mkString(",")
+            (key, fa, fb, fc, fd)
+          })
+          .toDF("fb", "f6", "f7", "f8", "f9")
+        df2.createOrReplaceTempView("view2")
+        sparkSession.sql("drop table if exists fres")
+        sparkSession.sql("create table fres using parquet as select * from view1 a inner join view2 b on a.fa=b.fb where a.fa=1 ")
+        sparkSession.sql("drop table fres")
+        sparkSession.stop()
+      }
+    }
+  }
+
+}
diff --git a/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemorySparkTestBase.scala b/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemorySparkTestBase.scala
new file mode 100644
index 000000000..d38d84561
--- /dev/null
+++ b/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemorySparkTestBase.scala
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.tests.spark.memory
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.internal.SQLConf
+import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
+import org.scalatest.funsuite.AnyFunSuite
+
+import org.apache.celeborn.common.CelebornConf._
+import org.apache.celeborn.common.internal.Logging
+import org.apache.celeborn.common.protocol.ShuffleMode
+import org.apache.celeborn.service.deploy.MiniClusterFeature
+import org.apache.celeborn.tests.spark.SparkTestBase
+
+trait MemorySparkTestBase extends AnyFunSuite
+  with Logging with MiniClusterFeature with BeforeAndAfterAll with BeforeAndAfterEach
+  with SparkTestBase {
+
+  override def beforeAll(): Unit = {
+    logInfo("test initialized , setup Celeborn mini cluster")
+    val workerConfs = Map("celeborn.worker.directMemoryRatioForMemoryFileStorage" -> "0.2")
+    setupMiniClusterWithRandomPorts(workerConf = workerConfs, workerNum = 5)
+  }
+
+  override def afterAll(): Unit = {
+    logInfo("all test complete , stop Celeborn mini cluster")
+    shutdownMiniCluster()
+  }
+
+  override def updateSparkConf(sparkConf: SparkConf, mode: ShuffleMode): SparkConf = {
+    sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
+    sparkConf.set(
+      "spark.shuffle.manager",
+      "org.apache.spark.shuffle.celeborn.SparkShuffleManager")
+    sparkConf.set("spark.shuffle.useOldFetchProtocol", "true")
+    sparkConf.set(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key, "false")
+    sparkConf.set("spark.shuffle.service.enabled", "false")
+    sparkConf.set("spark.sql.adaptive.skewJoin.enabled", "false")
+    sparkConf.set("spark.sql.adaptive.localShuffleReader.enabled", "false")
+    sparkConf.set(s"spark.${MASTER_ENDPOINTS.key}", masterInfo._1.rpcEnv.address.toString)
+    sparkConf.set(s"spark.${SPARK_SHUFFLE_WRITER_MODE.key}", mode.toString)
+    sparkConf.set(s"spark.celeborn.storage.availableTypes", "HDD,MEMORY")
+
+    sparkConf
+  }
+}
diff --git a/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemoryStorageReusedExchangeSuite.scala b/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemoryStorageReusedExchangeSuite.scala
new file mode 100644
index 000000000..5304eaede
--- /dev/null
+++ b/tests/spark-it/src/test/scala/org/apache/celeborn/tests/spark/memory/MemoryStorageReusedExchangeSuite.scala
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.tests.spark.memory
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.functions._
+import org.scalatest.BeforeAndAfterEach
+import org.scalatest.funsuite.AnyFunSuite
+
+import org.apache.celeborn.client.ShuffleClient
+import org.apache.celeborn.common.CelebornConf
+
+class MemoryStorageReusedExchangeSuite extends AnyFunSuite
+  with MemorySparkTestBase
+  with BeforeAndAfterEach {
+
+  override def beforeEach(): Unit = {
+    ShuffleClient.reset()
+  }
+
+  test("[CELEBORN-980] Asynchronously delete original files to fix ReusedExchange bug") {
+    testReusedExchange(false)
+  }
+
+  def testReusedExchange(readLocalShuffle: Boolean): Unit = {
+    val sparkConf = new SparkConf().setAppName("celeborn-test").setMaster("local[2]")
+      .set("spark.shuffle.manager", "org.apache.spark.shuffle.celeborn.SparkShuffleManager")
+      .set(s"spark.${CelebornConf.MASTER_ENDPOINTS.key}", masterInfo._1.rpcEnv.address.toString)
+      .set(s"spark.${CelebornConf.READ_LOCAL_SHUFFLE_FILE.key}", readLocalShuffle.toString)
+      .set("spark.sql.autoBroadcastJoinThreshold", "-1")
+      .set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes", "100")
+      .set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "100")
+      .set(s"spark.celeborn.storage.availableTypes", "HDD,MEMORY")
+    val spark = SparkSession.builder()
+      .config(sparkConf)
+      .getOrCreate()
+    spark.range(0, 1000, 1, 10)
+      .selectExpr("id as k1", "id as v1")
+      .createOrReplaceTempView("ta")
+    spark.range(0, 1000, 1, 10)
+      .selectExpr("id % 1 as k21", "id % 1 as k22", "id as v2")
+      .createOrReplaceTempView("tb")
+    spark.range(140)
+      .select(
+        col("id").cast("long").as("k3"),
+        concat(col("id").cast("string"), lit("a")).as("v3"))
+      .createOrReplaceTempView("tc")
+
+    spark.sql(
+      """
+        |SELECT *
+        |FROM ta
+        |LEFT JOIN tb ON ta.k1 = tb.k21
+        |LEFT JOIN tc ON tb.k22 = tc.k3
+        |""".stripMargin)
+      .createOrReplaceTempView("v1")
+
+    spark.sql(
+      """
+        |SELECT * FROM v1 WHERE v3 IS NOT NULL
+        |UNION
+        |SELECT * FROM v1
+        |""".stripMargin)
+      .collect()
+    spark.stop
+  }
+}
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/memory/MemoryManager.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/memory/MemoryManager.java
index c58ccc55e..ae25060c3 100644
--- a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/memory/MemoryManager.java
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/memory/MemoryManager.java
@@ -17,8 +17,8 @@
 
 package org.apache.celeborn.service.deploy.worker.memory;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.io.IOException;
+import java.util.*;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -29,6 +29,7 @@ import java.util.concurrent.atomic.LongAdder;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
 import io.netty.util.internal.PlatformDependent;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,14 +40,16 @@ import org.apache.celeborn.common.util.ThreadUtils;
 import org.apache.celeborn.common.util.Utils;
 import org.apache.celeborn.reflect.DynMethods;
 import org.apache.celeborn.service.deploy.worker.storage.CreditStreamManager;
+import org.apache.celeborn.service.deploy.worker.storage.PartitionDataWriter;
+import org.apache.celeborn.service.deploy.worker.storage.StorageManager;
 
 public class MemoryManager {
   private static final Logger logger = LoggerFactory.getLogger(MemoryManager.class);
   private static volatile MemoryManager _INSTANCE = null;
-  @VisibleForTesting public long maxDirectorMemory = 0;
+  @VisibleForTesting public long maxDirectMemory;
   private final long pausePushDataThreshold;
   private final long pauseReplicateThreshold;
-  private final long resumeThreshold;
+  private final double resumeRatio;
   private final long maxSortMemory;
   private final int forceAppendPauseSpentTimeThreshold;
   private final List<MemoryPressureListener> memoryPressureListeners = new ArrayList<>();
@@ -75,8 +78,8 @@ public class MemoryManager {
   private volatile boolean isPaused = false;
   // For credit stream
   private final AtomicLong readBufferCounter = new AtomicLong(0);
-  private long readBufferThreshold = 0;
-  private long readBufferTarget = 0;
+  private long readBufferThreshold;
+  private long readBufferTarget;
   private ReadBufferDispatcher readBufferDispatcher;
   private List<ReadBufferTargetChangeListener> readBufferTargetChangeListeners;
   private long lastNotifiedTarget = 0;
@@ -85,11 +88,18 @@ public class MemoryManager {
           "worker-memory-manager-read-buffer-target-updater");
   private CreditStreamManager creditStreamManager = null;
 
-  private long memoryShuffleStorageThreshold = 0;
+  private long memoryFileStorageThreshold;
+  private final LongAdder memoryFileStorageCounter = new LongAdder();
+  private final StorageManager storageManager;
 
+  @VisibleForTesting
   public static MemoryManager initialize(CelebornConf conf) {
+    return initialize(conf, null);
+  }
+
+  public static MemoryManager initialize(CelebornConf conf, StorageManager storageManager) {
     if (_INSTANCE == null) {
-      _INSTANCE = new MemoryManager(conf);
+      _INSTANCE = new MemoryManager(conf, storageManager);
     }
     return _INSTANCE;
   }
@@ -104,28 +114,27 @@ public class MemoryManager {
     return _INSTANCE;
   }
 
-  private MemoryManager(CelebornConf conf) {
+  private MemoryManager(CelebornConf conf, StorageManager storageManager) {
     double pausePushDataRatio = conf.workerDirectMemoryRatioToPauseReceive();
     double pauseReplicateRatio = conf.workerDirectMemoryRatioToPauseReplicate();
-    double resumeRatio = conf.workerDirectMemoryRatioToResume();
+    this.resumeRatio = conf.workerDirectMemoryRatioToResume();
     double maxSortMemRatio = conf.workerPartitionSorterDirectMemoryRatioThreshold();
     double readBufferRatio = conf.workerDirectMemoryRatioForReadBuffer();
-    double shuffleStorageRatio = conf.workerDirectMemoryRatioForShuffleStorage();
+    double memoryFileStorageRatio = conf.workerDirectMemoryRatioForMemoryFilesStorage();
     long checkInterval = conf.workerDirectMemoryPressureCheckIntervalMs();
     long reportInterval = conf.workerDirectMemoryReportIntervalSecond();
     double readBufferTargetRatio = conf.readBufferTargetRatio();
     long readBufferTargetUpdateInterval = conf.readBufferTargetUpdateInterval();
     long readBufferTargetNotifyThreshold = conf.readBufferTargetNotifyThreshold();
     forceAppendPauseSpentTimeThreshold = conf.metricsWorkerForceAppendPauseSpentTimeThreshold();
-
-    maxDirectorMemory =
+    maxDirectMemory =
         DynMethods.builder("maxDirectMemory")
             .impl("jdk.internal.misc.VM") // for Java 10 and above
             .impl("sun.misc.VM") // for Java 9 and previous
             .buildStatic()
             .<Long>invoke();
 
-    Preconditions.checkArgument(maxDirectorMemory > 0);
+    Preconditions.checkArgument(maxDirectMemory > 0);
     Preconditions.checkArgument(
         pauseReplicateRatio > pausePushDataRatio,
         String.format(
@@ -135,15 +144,16 @@ public class MemoryManager {
             CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_RECEIVE().key(),
             pausePushDataRatio));
     Preconditions.checkArgument(pausePushDataRatio > resumeRatio);
-    Preconditions.checkArgument(resumeRatio > (readBufferRatio + shuffleStorageRatio));
+    if (memoryFileStorageRatio > 0) {
+      Preconditions.checkArgument(resumeRatio > (readBufferRatio + memoryFileStorageRatio));
+    }
 
-    maxSortMemory = ((long) (maxDirectorMemory * maxSortMemRatio));
-    pausePushDataThreshold = (long) (maxDirectorMemory * pausePushDataRatio);
-    pauseReplicateThreshold = (long) (maxDirectorMemory * pauseReplicateRatio);
-    resumeThreshold = (long) (maxDirectorMemory * resumeRatio);
-    readBufferThreshold = (long) (maxDirectorMemory * readBufferRatio);
+    maxSortMemory = ((long) (maxDirectMemory * maxSortMemRatio));
+    pausePushDataThreshold = (long) (maxDirectMemory * pausePushDataRatio);
+    pauseReplicateThreshold = (long) (maxDirectMemory * pauseReplicateRatio);
+    readBufferThreshold = (long) (maxDirectMemory * readBufferRatio);
     readBufferTarget = (long) (readBufferThreshold * readBufferTargetRatio);
-    memoryShuffleStorageThreshold = (long) (maxDirectorMemory * shuffleStorageRatio);
+    memoryFileStorageThreshold = (long) (maxDirectMemory * memoryFileStorageRatio);
 
     checkService.scheduleWithFixedDelay(
         () -> {
@@ -160,12 +170,17 @@ public class MemoryManager {
     reportService.scheduleWithFixedDelay(
         () ->
             logger.info(
-                "Direct memory usage: {}/{}, disk buffer size: {}, sort memory size: {}, read buffer size: {}",
+                "Direct memory usage: {}/{}, "
+                    + "disk buffer size: {}, "
+                    + "sort memory size: {}, "
+                    + "read buffer size: {}, "
+                    + "memory file storage size : {}",
                 Utils.bytesToString(getNettyUsedDirectMemory()),
-                Utils.bytesToString(maxDirectorMemory),
+                Utils.bytesToString(maxDirectMemory),
                 Utils.bytesToString(diskBufferCounter.get()),
                 Utils.bytesToString(sortMemoryCounter.get()),
-                Utils.bytesToString(readBufferCounter.get())),
+                Utils.bytesToString(readBufferCounter.get()),
+                Utils.bytesToString(memoryFileStorageCounter.sum())),
         reportInterval,
         reportInterval,
         TimeUnit.SECONDS);
@@ -208,19 +223,64 @@ public class MemoryManager {
           TimeUnit.MILLISECONDS);
     }
 
+    this.storageManager = storageManager;
+    if (memoryFileStorageThreshold > 0
+        && storageManager != null
+        && storageManager.localOrHdfsStorageAvailable()) {
+      ScheduledExecutorService memoryFileStorageService =
+          ThreadUtils.newDaemonSingleThreadScheduledExecutor("memory-file-storage-checker");
+      memoryFileStorageService.scheduleWithFixedDelay(
+          () -> {
+            try {
+              if ((memoryFileStorageCounter.sum() >= 0.5 * memoryFileStorageThreshold)
+                  && currentServingState() != ServingState.NONE_PAUSED) {
+                List<PartitionDataWriter> memoryWriters =
+                    new ArrayList<>(storageManager.memoryWriters().values());
+                if (memoryWriters.isEmpty()) {
+                  return;
+                }
+                logger.info("Start evicting {} memory file infos", memoryWriters.size());
+                // always evict the largest memory file info first
+                memoryWriters.sort(
+                    Comparator.comparingLong(o -> o.getMemoryFileInfo().getFileLength()));
+                Collections.reverse(memoryWriters);
+                try {
+                  for (PartitionDataWriter writer : memoryWriters) {
+                    // this branch means that there is no memory pressure
+                    if ((memoryFileStorageCounter.sum() < 0.5 * memoryFileStorageThreshold)
+                        || currentServingState() == ServingState.NONE_PAUSED) {
+                      break;
+                    }
+                    logger.debug("Evict writer {}", writer);
+                    writer.evict(true);
+                  }
+                } catch (IOException e) {
+                  logger.warn("Partition data writer evict failed", e);
+                }
+              }
+            } catch (Exception e) {
+              logger.error("Evict thread encounter error", e);
+            }
+          },
+          checkInterval,
+          checkInterval,
+          TimeUnit.MILLISECONDS);
+    }
+
     logger.info(
         "Memory tracker initialized with: "
             + "max direct memory: {}, pause push memory: {}, "
-            + "pause replication memory: {}, resume memory: {}, "
+            + "pause replication memory: {},  "
             + "read buffer memory limit: {} target: {}, "
-            + "memory shuffle storage limit: {}",
-        Utils.bytesToString(maxDirectorMemory),
+            + "memory shuffle storage limit: {},"
+            + "resume memory ratio: {},",
+        Utils.bytesToString(maxDirectMemory),
         Utils.bytesToString(pausePushDataThreshold),
         Utils.bytesToString(pauseReplicateThreshold),
-        Utils.bytesToString(resumeThreshold),
         Utils.bytesToString(readBufferThreshold),
         Utils.bytesToString(readBufferTarget),
-        Utils.bytesToString(memoryShuffleStorageThreshold));
+        Utils.bytesToString(memoryFileStorageThreshold),
+        resumeRatio);
   }
 
   public ServingState currentServingState() {
@@ -237,7 +297,7 @@ public class MemoryManager {
       return ServingState.PUSH_PAUSED;
     }
     // trigger resume
-    if (memoryUsage < resumeThreshold) {
+    if (memoryUsage / (double) (maxDirectMemory) < resumeRatio) {
       isPaused = false;
       return ServingState.NONE_PAUSED;
     }
@@ -449,6 +509,26 @@ public class MemoryManager {
     this.creditStreamManager = creditStreamManager;
   }
 
+  public double workerMemoryUsageRatio() {
+    return getMemoryUsage() / (double) (maxDirectMemory);
+  }
+
+  public long getMemoryFileStorageCounter() {
+    return memoryFileStorageCounter.sum();
+  }
+
+  public boolean memoryFileStorageAvailable() {
+    return memoryFileStorageCounter.sum() < memoryFileStorageThreshold;
+  }
+
+  public void increaseMemoryFileStorage(int bytes) {
+    memoryFileStorageCounter.add(bytes);
+  }
+
+  public void releaseMemoryFileStorage(int bytes) {
+    memoryFileStorageCounter.add(-1 * bytes);
+  }
+
   public void close() {
     checkService.shutdown();
     reportService.shutdown();
@@ -459,6 +539,10 @@ public class MemoryManager {
     readBufferDispatcher.close();
   }
 
+  public PooledByteBufAllocator getStoragePooledByteBufAllocator() {
+    return storageManager.storageBufferAllocator();
+  }
+
   @VisibleForTesting
   public static void reset() {
     _INSTANCE = null;
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManager.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManager.java
index 006ecbe44..7ea390168 100644
--- a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManager.java
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManager.java
@@ -28,8 +28,8 @@ import com.google.common.annotations.VisibleForTesting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.celeborn.common.meta.FileManagedBuffers;
 import org.apache.celeborn.common.meta.TimeWindow;
+import org.apache.celeborn.common.network.buffer.ChunkBuffers;
 import org.apache.celeborn.common.network.buffer.ManagedBuffer;
 import org.apache.celeborn.common.util.JavaUtils;
 
@@ -47,20 +47,16 @@ public class ChunkStreamManager {
   protected final ConcurrentHashMap<String, Set<Long>> shuffleStreamIds;
 
   /** State of a single stream. */
-  protected static class StreamState {
-    final FileManagedBuffers buffers;
-    final String shuffleKey;
-    final String fileName;
-    final TimeWindow fetchTimeMetric;
-
+  public static class StreamState {
+    public final ChunkBuffers buffers;
+    public final String shuffleKey;
+    public final String fileName;
+    public final TimeWindow fetchTimeMetric;
     // Used to keep track of the number of chunks being transferred and not finished yet.
     volatile long chunksBeingTransferred = 0L;
 
     StreamState(
-        String shuffleKey,
-        FileManagedBuffers buffers,
-        String fileName,
-        TimeWindow fetchTimeMetric) {
+        String shuffleKey, ChunkBuffers buffers, String fileName, TimeWindow fetchTimeMetric) {
       this.buffers = buffers;
       this.shuffleKey = shuffleKey;
       this.fileName = fileName;
@@ -87,7 +83,7 @@ public class ChunkStreamManager {
           String.format("Requested chunk index beyond end %s", chunkIndex));
     }
 
-    FileManagedBuffers buffers = state.buffers;
+    ChunkBuffers buffers = state.buffers;
     return buffers.chunk(chunkIndex, offset, len);
   }
 
@@ -146,7 +142,7 @@ public class ChunkStreamManager {
    * stream is not properly closed, it will eventually be cleaned up by `cleanupExpiredShuffleKey`.
    */
   public long registerStream(
-      String shuffleKey, FileManagedBuffers buffers, String fileName, TimeWindow fetchTimeMetric) {
+      String shuffleKey, ChunkBuffers buffers, String fileName, TimeWindow fetchTimeMetric) {
     long myStreamId = nextStreamId.getAndIncrement();
     return registerStream(myStreamId, shuffleKey, buffers, fileName, fetchTimeMetric);
   }
@@ -154,10 +150,11 @@ public class ChunkStreamManager {
   public long registerStream(
       long streamId,
       String shuffleKey,
-      FileManagedBuffers buffers,
+      ChunkBuffers buffers,
       String fileName,
       TimeWindow fetchTimeMetric) {
-    streams.put(streamId, new StreamState(shuffleKey, buffers, fileName, fetchTimeMetric));
+    StreamState streamState = new StreamState(shuffleKey, buffers, fileName, fetchTimeMetric);
+    streams.put(streamId, streamState);
     shuffleStreamIds.compute(
         shuffleKey,
         (key, value) -> {
@@ -193,6 +190,10 @@ public class ChunkStreamManager {
         streams.size());
   }
 
+  public StreamState getStreamState(long streamId) {
+    return streams.get(streamId);
+  }
+
   public Tuple2<String, String> getShuffleKeyAndFileName(long streamId) {
     StreamState state = streams.get(streamId);
     return new Tuple2<>(state.shuffleKey, state.fileName);
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriter.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriter.java
index 2ce0cbbc1..4eab8f0b1 100644
--- a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriter.java
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriter.java
@@ -23,6 +23,7 @@ import java.nio.ByteOrder;
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
 
+import com.google.common.base.Preconditions;
 import io.netty.buffer.ByteBuf;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.Path;
@@ -30,11 +31,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
-import org.apache.celeborn.common.meta.DiskFileInfo;
 import org.apache.celeborn.common.meta.MapFileMeta;
 import org.apache.celeborn.common.metrics.source.AbstractSource;
-import org.apache.celeborn.common.protocol.PartitionSplitMode;
-import org.apache.celeborn.common.protocol.PartitionType;
 import org.apache.celeborn.common.util.FileChannelUtils;
 import org.apache.celeborn.common.util.Utils;
 
@@ -57,28 +55,14 @@ public final class MapPartitionDataWriter extends PartitionDataWriter {
 
   public MapPartitionDataWriter(
       StorageManager storageManager,
-      DiskFileInfo diskFileInfo,
-      Flusher flusher,
       AbstractSource workerSource,
       CelebornConf conf,
       DeviceMonitor deviceMonitor,
-      long splitThreshold,
-      PartitionSplitMode splitMode,
-      boolean rangeReadFilter,
-      String shuffleKey)
+      PartitionDataWriterContext writerContext)
       throws IOException {
-    super(
-        storageManager,
-        diskFileInfo,
-        flusher,
-        workerSource,
-        conf,
-        deviceMonitor,
-        splitThreshold,
-        splitMode,
-        PartitionType.MAP,
-        rangeReadFilter,
-        shuffleKey);
+    super(storageManager, workerSource, conf, deviceMonitor, writerContext, false);
+
+    Preconditions.checkState(diskFileInfo != null);
     if (!diskFileInfo.isHdfs()) {
       indexChannel = FileChannelUtils.createWritableFileChannel(diskFileInfo.getIndexPath());
     } else {
@@ -134,9 +118,7 @@ public final class MapPartitionDataWriter extends PartitionDataWriter {
   @Override
   public synchronized long close() throws IOException {
     return super.close(
-        () -> {
-          flushIndex();
-        },
+        this::flushIndex,
         () -> {
           if (diskFileInfo.isHdfs()) {
             if (StorageManager.hadoopFs().exists(diskFileInfo.getHdfsPeerWriterSuccessPath())) {
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriter.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriter.java
index 2058dd649..a2f00b0a8 100644
--- a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriter.java
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriter.java
@@ -19,26 +19,33 @@ package org.apache.celeborn.service.deploy.worker.storage;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import javax.annotation.concurrent.GuardedBy;
+import scala.Tuple4;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
 import org.roaringbitmap.RoaringBitmap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.exception.AlreadyClosedException;
+import org.apache.celeborn.common.exception.CelebornIOException;
 import org.apache.celeborn.common.meta.DiskFileInfo;
 import org.apache.celeborn.common.meta.DiskStatus;
+import org.apache.celeborn.common.meta.FileInfo;
+import org.apache.celeborn.common.meta.MemoryFileInfo;
 import org.apache.celeborn.common.metrics.source.AbstractSource;
 import org.apache.celeborn.common.protocol.PartitionSplitMode;
-import org.apache.celeborn.common.protocol.PartitionType;
 import org.apache.celeborn.common.protocol.StorageInfo;
 import org.apache.celeborn.common.unsafe.Platform;
 import org.apache.celeborn.common.util.FileChannelUtils;
@@ -54,69 +61,106 @@ public abstract class PartitionDataWriter implements DeviceObserver {
   private static final Logger logger = LoggerFactory.getLogger(PartitionDataWriter.class);
   private static final long WAIT_INTERVAL_MS = 5;
 
-  protected final DiskFileInfo diskFileInfo;
+  // After commit file, there will be only 1 fileinfo left.
+  protected DiskFileInfo diskFileInfo = null;
+  protected MemoryFileInfo memoryFileInfo = null;
   private FileChannel channel;
   private volatile boolean closed;
   private volatile boolean destroyed;
 
   protected final AtomicInteger numPendingWrites = new AtomicInteger();
 
-  public final Flusher flusher;
-  private final int flushWorkerIndex;
+  public Flusher flusher;
+  private int flushWorkerIndex;
 
-  @GuardedBy("flushLock")
-  private CompositeByteBuf flushBuffer;
+  protected CompositeByteBuf flushBuffer;
 
-  private final Object flushLock = new Object();
+  protected final Object flushLock = new Object();
   private final long writerCloseTimeoutMs;
 
-  protected final long flusherBufferSize;
+  protected long flusherBufferSize;
 
   protected final DeviceMonitor deviceMonitor;
   protected final AbstractSource source; // metrics
 
-  private long splitThreshold = 0;
+  private final long splitThreshold;
   private final PartitionSplitMode splitMode;
-  private final PartitionType partitionType;
   private final boolean rangeReadFilter;
   protected boolean deleted = false;
   private RoaringBitmap mapIdBitMap = null;
   protected final FlushNotifier notifier = new FlushNotifier();
   // It's only needed when graceful shutdown is enabled
-  private String shuffleKey;
-  private final StorageManager storageManager;
+  private final String shuffleKey;
+  protected final StorageManager storageManager;
   private final boolean workerGracefulShutdown;
+  protected final long memoryFileStorageMaxFileSize;
+  protected AtomicBoolean isMemoryShuffleFile = new AtomicBoolean();
+  protected final String filename;
+  protected PooledByteBufAllocator pooledByteBufAllocator;
+  private final PartitionDataWriterContext writerContext;
+  private final long localFlusherBufferSize;
+  private final long hdfsFlusherBufferSize;
+  private Exception exception = null;
+  private boolean metricsCollectCriticalEnabled;
 
   public PartitionDataWriter(
       StorageManager storageManager,
-      DiskFileInfo diskFileInfo,
-      Flusher flusher,
       AbstractSource workerSource,
       CelebornConf conf,
       DeviceMonitor deviceMonitor,
-      long splitThreshold,
-      PartitionSplitMode splitMode,
-      PartitionType partitionType,
-      boolean rangeReadFilter,
-      String shuffleKey)
+      PartitionDataWriterContext writerContext,
+      boolean supportInMemory)
       throws IOException {
     this.storageManager = storageManager;
-    this.diskFileInfo = diskFileInfo;
-    this.flusher = flusher;
-    this.flushWorkerIndex = flusher.getWorkerIndex();
     this.writerCloseTimeoutMs = conf.workerWriterCloseTimeoutMs();
     this.workerGracefulShutdown = conf.workerGracefulShutdown();
-    this.splitThreshold = splitThreshold;
+    this.splitThreshold = writerContext.getSplitThreshold();
     this.deviceMonitor = deviceMonitor;
-    this.splitMode = splitMode;
-    this.partitionType = partitionType;
-    this.rangeReadFilter = rangeReadFilter;
-    this.shuffleKey = shuffleKey;
+    this.splitMode = writerContext.getPartitionSplitMode();
+    this.rangeReadFilter = writerContext.isRangeReadFilter();
+    this.shuffleKey = writerContext.getShuffleKey();
+    this.memoryFileStorageMaxFileSize = conf.workerMemoryFileStorageMaxFileSize();
+    this.filename = writerContext.getPartitionLocation().getFileName();
+    this.writerContext = writerContext;
+    this.localFlusherBufferSize = conf.workerFlusherBufferSize();
+    this.hdfsFlusherBufferSize = conf.workerHdfsFlusherBufferSize();
+    this.metricsCollectCriticalEnabled = conf.metricsCollectCriticalEnabled();
+
+    Tuple4<MemoryFileInfo, Flusher, DiskFileInfo, File> createFileResult =
+        storageManager.createFile(writerContext, supportInMemory);
+
+    // Reduce partition data writers support memory storage now
+    if (supportInMemory && createFileResult._1() != null) {
+      this.memoryFileInfo = createFileResult._1();
+      this.pooledByteBufAllocator = storageManager.storageBufferAllocator();
+      this.isMemoryShuffleFile.set(true);
+      storageManager.registerMemoryPartitionWriter(this, createFileResult._1());
+    } else if (createFileResult._2() != null) {
+      this.diskFileInfo = createFileResult._3();
+      this.flusher = createFileResult._2();
+      File workingDir = createFileResult._4();
+      this.isMemoryShuffleFile.set(false);
+      initFileChannelsForDiskFile();
+      storageManager.registerDiskFilePartitionWriter(this, workingDir, diskFileInfo);
+    } else {
+      throw new CelebornIOException(
+          "Create file failed for location:" + writerContext.getPartitionLocation().toString());
+    }
+
+    source = workerSource;
+    logger.debug("FileWriter {} split threshold {} mode {}", this, splitThreshold, splitMode);
+    if (rangeReadFilter) {
+      this.mapIdBitMap = new RoaringBitmap();
+    }
+    takeBuffer();
+  }
+
+  public void initFileChannelsForDiskFile() throws IOException {
     if (!this.diskFileInfo.isHdfs()) {
-      this.flusherBufferSize = conf.workerFlusherBufferSize();
+      this.flusherBufferSize = localFlusherBufferSize;
       channel = FileChannelUtils.createWritableFileChannel(this.diskFileInfo.getFilePath());
     } else {
-      this.flusherBufferSize = conf.workerHdfsFlusherBufferSize();
+      this.flusherBufferSize = hdfsFlusherBufferSize;
       // We open the stream and close immediately because HDFS output stream will
       // create a DataStreamer that is a thread.
       // If we reuse HDFS output stream, we will exhaust the memory soon.
@@ -132,12 +176,6 @@ public abstract class PartitionDataWriter implements DeviceObserver {
         StorageManager.hadoopFs().create(this.diskFileInfo.getHdfsPath(), true).close();
       }
     }
-    source = workerSource;
-    logger.debug("FileWriter {} split threshold {} mode {}", this, splitThreshold, splitMode);
-    if (rangeReadFilter) {
-      this.mapIdBitMap = new RoaringBitmap();
-    }
-    takeBuffer();
   }
 
   public DiskFileInfo getDiskFileInfo() {
@@ -156,23 +194,56 @@ public abstract class PartitionDataWriter implements DeviceObserver {
     numPendingWrites.decrementAndGet();
   }
 
-  protected void flush(boolean finalFlush) throws IOException {
-    synchronized (flushLock) {
-      // flushBuffer == null here means writer already closed
-      if (flushBuffer != null) {
-        int numBytes = flushBuffer.readableBytes();
-        if (numBytes != 0) {
-          notifier.checkException();
+  @VisibleForTesting
+  public void flush(boolean finalFlush, boolean fromEvict) throws IOException {
+    // flushBuffer == null here means this writer is already closed
+    if (flushBuffer != null) {
+      int numBytes = flushBuffer.readableBytes();
+      if (numBytes != 0) {
+        notifier.checkException();
+        FlushTask task = null;
+        if (fromEvict) {
           notifier.numPendingFlushes.incrementAndGet();
-          FlushTask task = null;
+          // duplicate buffer before its released
+          ByteBuf dupBuf = flushBuffer.retainedDuplicate();
+          // flush task will release the buffer of memory shuffle file
           if (channel != null) {
-            task = new LocalFlushTask(flushBuffer, channel, notifier);
+            task = new LocalFlushTask(flushBuffer, channel, notifier, false);
           } else if (diskFileInfo.isHdfs()) {
-            task = new HdfsFlushTask(flushBuffer, diskFileInfo.getHdfsPath(), notifier);
+            task = new HdfsFlushTask(flushBuffer, diskFileInfo.getHdfsPath(), notifier, false);
+          }
+          MemoryManager.instance().releaseMemoryFileStorage(numBytes);
+          MemoryManager.instance().incrementDiskBuffer(numBytes);
+          // read flush buffer to generate correct chunk offsets
+          // data header layout (mapId, attemptId, nextBatchId, length)
+          ByteBuffer headerBuf = ByteBuffer.allocate(16);
+          while (dupBuf.isReadable()) {
+            headerBuf.rewind();
+            dupBuf.readBytes(headerBuf);
+            byte[] batchHeader = headerBuf.array();
+            int compressedSize = Platform.getInt(batchHeader, Platform.BYTE_ARRAY_OFFSET + 12);
+            dupBuf.skipBytes(compressedSize);
+            diskFileInfo.updateBytesFlushed(compressedSize + 16);
           }
+          dupBuf.release();
+        } else {
+          if (!isMemoryShuffleFile.get()) {
+            notifier.numPendingFlushes.incrementAndGet();
+            if (channel != null) {
+              task = new LocalFlushTask(flushBuffer, channel, notifier, true);
+            } else if (diskFileInfo.isHdfs()) {
+              task = new HdfsFlushTask(flushBuffer, diskFileInfo.getHdfsPath(), notifier, true);
+            }
+          }
+        }
+        // task won't be null in real workloads
+        // task will be null in UT to check chunk size and offset
+        if (task != null) {
           addTask(task);
           flushBuffer = null;
-          diskFileInfo.updateBytesFlushed(numBytes);
+          if (!fromEvict) {
+            diskFileInfo.updateBytesFlushed(numBytes);
+          }
           if (!finalFlush) {
             takeBuffer();
           }
@@ -181,10 +252,20 @@ public abstract class PartitionDataWriter implements DeviceObserver {
     }
   }
 
+  public boolean needHardSplitForMemoryShuffleStorage() {
+    if (!isMemoryShuffleFile.get()) {
+      return false;
+    } else {
+      return !storageManager.localOrHdfsStorageAvailable()
+          && (memoryFileInfo.getFileLength() > memoryFileStorageMaxFileSize
+              || !MemoryManager.instance().memoryFileStorageAvailable());
+    }
+  }
+
   /** assume data size is less than chunk capacity */
   public void write(ByteBuf data) throws IOException {
     if (closed) {
-      String msg = "FileWriter has already closed!, fileName " + diskFileInfo.getFilePath();
+      String msg = getFileAlreadyClosedMsg();
       logger.warn(msg);
       throw new AlreadyClosedException(msg);
     }
@@ -203,49 +284,95 @@ public abstract class PartitionDataWriter implements DeviceObserver {
     }
 
     final int numBytes = data.readableBytes();
-    MemoryManager.instance().incrementDiskBuffer(numBytes);
-
-    Optional.ofNullable(CongestionController.instance())
-        .ifPresent(
-            congestionController ->
-                congestionController.produceBytes(diskFileInfo.getUserIdentifier(), numBytes));
+    if (isMemoryShuffleFile.get()) {
+      MemoryManager.instance().increaseMemoryFileStorage(numBytes);
+    } else {
+      MemoryManager.instance().incrementDiskBuffer(numBytes);
+      Optional.ofNullable(CongestionController.instance())
+          .ifPresent(
+              congestionController ->
+                  congestionController.produceBytes(diskFileInfo.getUserIdentifier(), numBytes));
+    }
 
     synchronized (flushLock) {
       if (closed) {
-        String msg = "FileWriter has already closed!, fileName " + diskFileInfo.getFilePath();
+        String msg = getFileAlreadyClosedMsg();
         logger.warn(msg);
         throw new AlreadyClosedException(msg);
       }
       if (rangeReadFilter) {
         mapIdBitMap.add(mapId);
       }
-      if (flushBuffer.readableBytes() != 0
-          && flushBuffer.readableBytes() + numBytes >= flusherBufferSize) {
-        flush(false);
+      int flushBufferReadableBytes = flushBuffer.readableBytes();
+      if (!isMemoryShuffleFile.get()) {
+        if (flushBufferReadableBytes != 0
+            && flushBufferReadableBytes + numBytes >= flusherBufferSize) {
+          flush(false, false);
+        }
+      } else {
+        if (flushBufferReadableBytes > memoryFileStorageMaxFileSize
+            && storageManager.localOrHdfsStorageAvailable()) {
+          logger.debug(
+              "{} Evict, memory buffer is  {}",
+              writerContext.getPartitionLocation().getFileName(),
+              flushBufferReadableBytes);
+          evict(false);
+        }
       }
 
       data.retain();
       flushBuffer.addComponent(true, data);
+      if (isMemoryShuffleFile.get()) {
+        memoryFileInfo.updateBytesFlushed(numBytes);
+      }
     }
 
     numPendingWrites.decrementAndGet();
   }
 
+  public void evictInternal() throws IOException {
+    if (exception != null) {
+      return;
+    }
+    Tuple4<MemoryFileInfo, Flusher, DiskFileInfo, File> createFileResult =
+        storageManager.createFile(writerContext, false);
+    if (createFileResult._4() != null) {
+      this.diskFileInfo = createFileResult._3();
+      this.flusher = createFileResult._2();
+
+      isMemoryShuffleFile.set(false);
+      initFileChannelsForDiskFile();
+      flush(closed, true);
+
+      logger.debug("evict {} {}", shuffleKey, filename);
+      storageManager.unregisterMemoryPartitionWriterAndFileInfo(
+          memoryFileInfo, shuffleKey, filename);
+      storageManager.evictedFileCount().incrementAndGet();
+      memoryFileInfo = null;
+    } else {
+      exception = new CelebornIOException("PartitionDataWriter create disk-related file failed");
+      throw (CelebornIOException) exception;
+    }
+  }
+
   public RoaringBitmap getMapIdBitMap() {
     return mapIdBitMap;
   }
 
   public StorageInfo getStorageInfo() {
-    if (flusher instanceof LocalFlusher) {
-      LocalFlusher localFlusher = (LocalFlusher) flusher;
-      // do not write file path to reduce rpc size
-      return new StorageInfo(localFlusher.diskType(), true, "");
-    } else {
-      if (deleted) {
-        return null;
+    if (diskFileInfo != null) {
+      if (diskFileInfo.isHdfs()) {
+        if (deleted) {
+          return null;
+        } else {
+          return new StorageInfo(StorageInfo.Type.HDFS, true, diskFileInfo.getFilePath());
+        }
       } else {
-        return new StorageInfo(StorageInfo.Type.HDFS, true, diskFileInfo.getFilePath());
+        return new StorageInfo(((LocalFlusher) flusher).diskType(), true, "");
       }
+    } else {
+      Preconditions.checkArgument(memoryFileInfo != null);
+      return new StorageInfo(StorageInfo.Type.MEMORY, true, "");
     }
   }
 
@@ -266,7 +393,7 @@ public abstract class PartitionDataWriter implements DeviceObserver {
       RunnableWithIOException finalClose)
       throws IOException {
     if (closed) {
-      String msg = "FileWriter has already closed! fileName " + diskFileInfo.getFilePath();
+      String msg = getFileAlreadyClosedMsg();
       logger.error(msg);
       throw new AlreadyClosedException(msg);
     }
@@ -276,15 +403,18 @@ public abstract class PartitionDataWriter implements DeviceObserver {
       closed = true;
 
       synchronized (flushLock) {
-        if (flushBuffer.readableBytes() > 0) {
-          flush(true);
+        if (!isMemoryShuffleFile.get()) {
+          // memory shuffle file doesn't need final flush
+          if (flushBuffer != null && flushBuffer.readableBytes() > 0) {
+            flush(true, false);
+          }
         }
       }
 
       tryClose.run();
       waitOnNoPending(notifier.numPendingFlushes);
     } finally {
-      returnBuffer();
+      returnBuffer(false);
       try {
         if (channel != null) {
           channel.close();
@@ -297,15 +427,53 @@ public abstract class PartitionDataWriter implements DeviceObserver {
       finalClose.run();
 
       // unregister from DeviceMonitor
-      if (!diskFileInfo.isHdfs()) {
+      if (diskFileInfo != null && !diskFileInfo.isHdfs()) {
         logger.debug("file info {} unregister from device monitor", diskFileInfo);
         deviceMonitor.unregisterFileWriter(this);
       }
     }
     if (workerGracefulShutdown) {
-      storageManager.notifyFileInfoCommitted(shuffleKey, getFile().getName(), diskFileInfo);
+      if (diskFileInfo != null) {
+        storageManager.notifyFileInfoCommitted(shuffleKey, getFile().getName(), diskFileInfo);
+      }
+    }
+    if (diskFileInfo != null) {
+      return diskFileInfo.getFileLength();
+    } else {
+      return memoryFileInfo.getFileLength();
+    }
+  }
+
+  private String getFileAlreadyClosedMsg() {
+    String msg = "PartitionDataWriter has already closed! ";
+    if (isMemoryShuffleFile.get()) {
+      msg += "In memory file name:" + filename;
+    } else {
+      msg += "Disk file name:" + diskFileInfo.getFilePath();
+    }
+    return msg;
+  }
+
+  public void evict(boolean checkClose) throws IOException {
+    // this lock is used to make sure that
+    // memory manager won't evict with writer thread concurrently
+    synchronized (flushLock) {
+      if (checkClose) {
+        // close and evict might be invoked concurrently
+        // do not evict committed files from memory manager
+        // evict memory file info if worker is shutdown gracefully
+        if (isClosed()) {
+          return;
+        }
+      }
+      if (memoryFileInfo != null) {
+        evictInternal();
+        if (isClosed()) {
+          waitOnNoPending(notifier.numPendingFlushes);
+          storageManager.notifyFileInfoCommitted(shuffleKey, getFile().getName(), diskFileInfo);
+        }
+      }
     }
-    return diskFileInfo.getFileLength();
   }
 
   public synchronized void destroy(IOException ioException) {
@@ -314,7 +482,7 @@ public abstract class PartitionDataWriter implements DeviceObserver {
       if (!notifier.hasException()) {
         notifier.setException(ioException);
       }
-      returnBuffer();
+      returnBuffer(true);
       try {
         if (channel != null) {
           channel.close();
@@ -329,15 +497,25 @@ public abstract class PartitionDataWriter implements DeviceObserver {
 
     if (!destroyed) {
       destroyed = true;
-      diskFileInfo.deleteAllFiles(StorageManager.hadoopFs());
+      if (diskFileInfo != null) {
+        diskFileInfo.deleteAllFiles(StorageManager.hadoopFs());
 
-      // unregister from DeviceMonitor
-      if (!diskFileInfo.isHdfs()) {
-        deviceMonitor.unregisterFileWriter(this);
+        // unregister from DeviceMonitor
+        if (!diskFileInfo.isHdfs()) {
+          deviceMonitor.unregisterFileWriter(this);
+        }
       }
     }
   }
 
+  protected FileInfo getCurrentFileInfo() {
+    if (!isMemoryShuffleFile.get()) {
+      return diskFileInfo;
+    } else {
+      return memoryFileInfo;
+    }
+  }
+
   public IOException getException() {
     if (notifier.hasException()) {
       return notifier.exception.get();
@@ -368,18 +546,26 @@ public abstract class PartitionDataWriter implements DeviceObserver {
   }
 
   protected void takeBuffer() {
-    if (source.metricsCollectCriticalEnabled()) {
-      String metricsName = WorkerSource.TAKE_BUFFER_TIME();
-      String fileAbsPath = diskFileInfo.getFilePath();
+    String metricsName = null;
+    String fileAbsPath = null;
+    if (metricsCollectCriticalEnabled) {
+      metricsName = WorkerSource.TAKE_BUFFER_TIME();
+      fileAbsPath = diskFileInfo.getFilePath();
       source.startTimer(metricsName, fileAbsPath);
-      synchronized (flushLock) {
+    }
+
+    synchronized (flushLock) {
+      if (diskFileInfo != null) {
         flushBuffer = flusher.takeBuffer();
+      } else {
+        if (flushBuffer == null) {
+          flushBuffer = pooledByteBufAllocator.compositeBuffer(Integer.MAX_VALUE);
+        }
       }
+    }
+
+    if (metricsCollectCriticalEnabled) {
       source.stopTimer(metricsName, fileAbsPath);
-    } else {
-      synchronized (flushLock) {
-        flushBuffer = flusher.takeBuffer();
-      }
     }
   }
 
@@ -391,11 +577,18 @@ public abstract class PartitionDataWriter implements DeviceObserver {
     }
   }
 
-  protected void returnBuffer() {
+  protected void returnBuffer(boolean destroy) {
     synchronized (flushLock) {
       if (flushBuffer != null) {
-        flusher.returnBuffer(flushBuffer);
-        flushBuffer = null;
+        if (flusher != null) {
+          flusher.returnBuffer(flushBuffer, true);
+          flushBuffer = null;
+        } else {
+          if (destroy) {
+            flushBuffer.removeComponents(0, flushBuffer.numComponents());
+            flushBuffer.release();
+          }
+        }
       }
     }
   }
@@ -415,12 +608,13 @@ public abstract class PartitionDataWriter implements DeviceObserver {
 
   @Override
   public String toString() {
-    return diskFileInfo.getFilePath();
+    return shuffleKey + "-" + filename + " partition-writer";
   }
 
   public void flushOnMemoryPressure() throws IOException {
     synchronized (flushLock) {
-      flush(false);
+      // this won't happen if this writer is in memory
+      flush(false, false);
     }
   }
 
@@ -456,7 +650,7 @@ public abstract class PartitionDataWriter implements DeviceObserver {
   @Override
   public void notifyNonCriticalError(String mountPoint, DiskStatus diskStatus) {}
 
-  public PartitionType getPartitionType() {
-    return partitionType;
+  public MemoryFileInfo getMemoryFileInfo() {
+    return memoryFileInfo;
   }
 }
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterContext.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterContext.java
new file mode 100644
index 000000000..b73b901ad
--- /dev/null
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterContext.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.service.deploy.worker.storage;
+
+import org.apache.celeborn.common.identity.UserIdentifier;
+import org.apache.celeborn.common.protocol.PartitionLocation;
+import org.apache.celeborn.common.protocol.PartitionSplitMode;
+import org.apache.celeborn.common.protocol.PartitionType;
+import org.apache.celeborn.common.util.Utils;
+
+public class PartitionDataWriterContext {
+  private final long splitThreshold;
+  private final PartitionSplitMode partitionSplitMode;
+  private final boolean rangeReadFilter;
+  private final PartitionLocation partitionLocation;
+  private final String appId;
+  private final int shuffleId;
+  private final UserIdentifier userIdentifier;
+  private final boolean partitionSplitEnabled;
+  private final String shuffleKey;
+  private final PartitionType partitionType;
+
+  public PartitionDataWriterContext(
+      long splitThreshold,
+      PartitionSplitMode partitionSplitMode,
+      boolean rangeReadFilter,
+      PartitionLocation partitionLocation,
+      String appId,
+      int shuffleId,
+      UserIdentifier userIdentifier,
+      PartitionType partitionType,
+      boolean partitionSplitEnabled) {
+    this.splitThreshold = splitThreshold;
+    this.partitionSplitMode = partitionSplitMode;
+    this.rangeReadFilter = rangeReadFilter;
+    this.partitionLocation = partitionLocation;
+    this.appId = appId;
+    this.shuffleId = shuffleId;
+    this.userIdentifier = userIdentifier;
+    this.partitionSplitEnabled = partitionSplitEnabled;
+    this.partitionType = partitionType;
+    this.shuffleKey = Utils.makeShuffleKey(appId, shuffleId);
+  }
+
+  public long getSplitThreshold() {
+    return splitThreshold;
+  }
+
+  public PartitionSplitMode getPartitionSplitMode() {
+    return partitionSplitMode;
+  }
+
+  public boolean isRangeReadFilter() {
+    return rangeReadFilter;
+  }
+
+  public PartitionLocation getPartitionLocation() {
+    return partitionLocation;
+  }
+
+  public String getAppId() {
+    return appId;
+  }
+
+  public int getShuffleId() {
+    return shuffleId;
+  }
+
+  public UserIdentifier getUserIdentifier() {
+    return userIdentifier;
+  }
+
+  public boolean isPartitionSplitEnabled() {
+    return partitionSplitEnabled;
+  }
+
+  public String getShuffleKey() {
+    return shuffleKey;
+  }
+
+  public PartitionType getPartitionType() {
+    return partitionType;
+  }
+}
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorter.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorter.java
index 0025e6482..481ea6c96 100644
--- a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorter.java
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorter.java
@@ -40,6 +40,8 @@ import java.util.concurrent.locks.ReentrantLock;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.CompositeByteBuf;
 import org.apache.commons.io.IOUtils;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.fs.FSDataOutputStream;
@@ -49,9 +51,7 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.identity.UserIdentifier;
-import org.apache.celeborn.common.meta.DiskFileInfo;
-import org.apache.celeborn.common.meta.FileInfo;
-import org.apache.celeborn.common.meta.ReduceFileMeta;
+import org.apache.celeborn.common.meta.*;
 import org.apache.celeborn.common.metrics.source.AbstractSource;
 import org.apache.celeborn.common.unsafe.Platform;
 import org.apache.celeborn.common.util.*;
@@ -88,7 +88,6 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
   protected final long shuffleChunkSize;
   protected final boolean prefetchEnabled;
   protected final long reservedMemoryPerPartition;
-  private final boolean gracefulShutdown;
   private final long partitionSorterShutdownAwaitTime;
   private DB sortedFilesDb;
 
@@ -96,7 +95,6 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
 
   private final ExecutorService fileSorterExecutors;
   private final ExecutorService fileSorterSchedulerThread;
-  private final long indexCacheMaxWeight;
 
   public PartitionFilesSorter(
       MemoryManager memoryManager, CelebornConf conf, AbstractSource source) {
@@ -106,10 +104,11 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
     this.reservedMemoryPerPartition = conf.workerPartitionSorterReservedMemoryPerPartition();
     this.partitionSorterShutdownAwaitTime =
         conf.workerGracefulShutdownPartitionSorterCloseAwaitTimeMs();
-    this.indexCacheMaxWeight = conf.workerPartitionSorterIndexCacheMaxWeight();
+    long indexCacheMaxWeight = conf.workerPartitionSorterIndexCacheMaxWeight();
     this.source = source;
     this.cleaner = new PartitionFilesCleaner(this);
-    this.gracefulShutdown = conf.workerGracefulShutdown();
+    boolean gracefulShutdown = conf.workerGracefulShutdown();
+    // Assume a chunk won't be larger than 2GB
     // ShuffleClient can fetch shuffle data from a restarted worker only
     // when the worker's fetching port is stable and enables graceful shutdown.
     if (gracefulShutdown) {
@@ -161,8 +160,6 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
                   () -> {
                     try {
                       task.sort();
-                    } catch (InterruptedException e) {
-                      logger.warn("File sorter thread was interrupted.");
                     } finally {
                       if (task.isPrefetch) {
                         memoryManager.releaseSortMemory(reservedMemoryPerPartition);
@@ -194,74 +191,166 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
   // 2. If the FileSorter task is already in the sorting queue but the sorted file has not been
   //    generated, it awaits until a timeout occurs (default 220 seconds).
   // 3. If the sorted file is generated, it returns the sorted FileInfo.
-  public DiskFileInfo getSortedFileInfo(
-      String shuffleKey, String fileName, DiskFileInfo fileInfo, int startMapIndex, int endMapIndex)
+  // This method will generate temporary file info for this shuffle read
+  public FileInfo getSortedFileInfo(
+      String shuffleKey, String fileName, FileInfo fileInfo, int startMapIndex, int endMapIndex)
       throws IOException {
-    String fileId = shuffleKey + "-" + fileName;
-    UserIdentifier userIdentifier = fileInfo.getUserIdentifier();
-    Set<String> sorted =
-        sortedShuffleFiles.computeIfAbsent(shuffleKey, v -> ConcurrentHashMap.newKeySet());
-    Set<String> sorting =
-        sortingShuffleFiles.computeIfAbsent(shuffleKey, v -> ConcurrentHashMap.newKeySet());
-
-    String sortedFilePath = Utils.getSortedFilePath(fileInfo.getFilePath());
-    String indexFilePath = Utils.getIndexFilePath(fileInfo.getFilePath());
-    boolean fileSorting = true;
-    synchronized (sorting) {
-      if (sorted.contains(fileId)) {
-        fileSorting = false;
-      } else if (!sorting.contains(fileId)) {
-        try {
-          FileSorter fileSorter = new FileSorter(fileInfo, fileId, shuffleKey);
-          sorting.add(fileId);
-          logger.debug(
-              "Adding sorter to sort queue shuffle key {}, file name {}", shuffleKey, fileName);
-          shuffleSortTaskDeque.put(fileSorter);
-        } catch (InterruptedException e) {
-          logger.error("Sorter scheduler thread is interrupted means worker is shutting down.", e);
-          throw new IOException(
-              "Sort scheduler thread is interrupted means worker is shutting down.", e);
-        } catch (IOException e) {
-          logger.error("File sorter access HDFS failed.", e);
-          throw new IOException("File sorter access HDFS failed.", e);
+    if (fileInfo instanceof MemoryFileInfo) {
+      MemoryFileInfo memoryFileInfo = ((MemoryFileInfo) fileInfo);
+      Map<Integer, List<ShuffleBlockInfo>> indexesMap;
+      sortMemoryShuffleFile(memoryFileInfo);
+      indexesMap = memoryFileInfo.getSortedIndexes();
+
+      ReduceFileMeta reduceFileMeta =
+          new ReduceFileMeta(
+              ShuffleBlockInfoUtils.getChunkOffsetsFromShuffleBlockInfos(
+                  startMapIndex, endMapIndex, shuffleChunkSize, indexesMap, true),
+              shuffleChunkSize);
+      CompositeByteBuf targetBuffer =
+          MemoryManager.instance()
+              .getStoragePooledByteBufAllocator()
+              .compositeBuffer(Integer.MAX_VALUE);
+      ShuffleBlockInfoUtils.sliceSortedBufferByMapRange(
+          startMapIndex, endMapIndex, indexesMap, memoryFileInfo.getSortedBuffer(), targetBuffer);
+      return new MemoryFileInfo(
+          memoryFileInfo.getUserIdentifier(),
+          memoryFileInfo.isPartitionSplitEnabled(),
+          reduceFileMeta,
+          targetBuffer);
+    } else {
+      DiskFileInfo diskFileInfo = ((DiskFileInfo) fileInfo);
+      String fileId = shuffleKey + "-" + fileName;
+      UserIdentifier userIdentifier = diskFileInfo.getUserIdentifier();
+      Set<String> sorted =
+          sortedShuffleFiles.computeIfAbsent(shuffleKey, v -> ConcurrentHashMap.newKeySet());
+      Set<String> sorting =
+          sortingShuffleFiles.computeIfAbsent(shuffleKey, v -> ConcurrentHashMap.newKeySet());
+
+      String sortedFilePath = Utils.getSortedFilePath(diskFileInfo.getFilePath());
+      String indexFilePath = Utils.getIndexFilePath(diskFileInfo.getFilePath());
+      boolean fileSorting = true;
+      synchronized (sorting) {
+        if (sorted.contains(fileId)) {
+          fileSorting = false;
+        } else if (!sorting.contains(fileId)) {
+          try {
+            FileSorter fileSorter = new FileSorter(diskFileInfo, fileId, shuffleKey);
+            sorting.add(fileId);
+            logger.debug(
+                "Adding sorter to sort queue shuffle key {}, file name {}", shuffleKey, fileName);
+            shuffleSortTaskDeque.put(fileSorter);
+          } catch (InterruptedException e) {
+            logger.error(
+                "Sorter scheduler thread is interrupted means worker is shutting down.", e);
+            throw new IOException(
+                "Sort scheduler thread is interrupted means worker is shutting down.", e);
+          } catch (IOException e) {
+            logger.error("File sorter access HDFS failed.", e);
+            throw new IOException("File sorter access HDFS failed.", e);
+          }
         }
       }
-    }
 
-    if (fileSorting) {
-      long sortStartTime = System.currentTimeMillis();
-      while (!sorted.contains(fileId)) {
-        if (sorting.contains(fileId)) {
-          try {
-            Thread.sleep(50);
-            if (System.currentTimeMillis() - sortStartTime > sortTimeout) {
-              logger.error("Sorting file {} timeout after {}ms", fileId, sortTimeout);
+      if (fileSorting) {
+        long sortStartTime = System.currentTimeMillis();
+        while (!sorted.contains(fileId)) {
+          if (sorting.contains(fileId)) {
+            try {
+              Thread.sleep(50);
+              if (System.currentTimeMillis() - sortStartTime > sortTimeout) {
+                logger.error("Sorting file {} timeout after {}ms", fileId, sortTimeout);
+                throw new IOException(
+                    "Sort file " + diskFileInfo.getFilePath() + " timeout after " + sortTimeout);
+              }
+            } catch (InterruptedException e) {
+              logger.error(
+                  "Sorter scheduler thread is interrupted means worker is shutting down.", e);
               throw new IOException(
-                  "Sort file " + fileInfo.getFilePath() + " timeout after " + sortTimeout);
+                  "Sorter scheduler thread is interrupted means worker is shutting down.", e);
             }
-          } catch (InterruptedException e) {
-            logger.error(
-                "Sorter scheduler thread is interrupted means worker is shutting down.", e);
+          } else {
+            logger.debug(
+                "Sorting shuffle file for {} {} failed.", shuffleKey, diskFileInfo.getFilePath());
             throw new IOException(
-                "Sorter scheduler thread is interrupted means worker is shutting down.", e);
+                "Sorting shuffle file for "
+                    + shuffleKey
+                    + " "
+                    + diskFileInfo.getFilePath()
+                    + " failed.");
           }
-        } else {
-          logger.debug(
-              "Sorting shuffle file for {} {} failed.", shuffleKey, fileInfo.getFilePath());
-          throw new IOException(
-              "Sorting shuffle file for " + shuffleKey + " " + fileInfo.getFilePath() + " failed.");
         }
       }
+
+      return resolve(
+          shuffleKey,
+          fileId,
+          userIdentifier,
+          sortedFilePath,
+          indexFilePath,
+          startMapIndex,
+          endMapIndex);
     }
+  }
 
-    return resolve(
-        shuffleKey,
-        fileId,
-        userIdentifier,
-        sortedFilePath,
-        indexFilePath,
-        startMapIndex,
-        endMapIndex);
+  public static void sortMemoryShuffleFile(MemoryFileInfo memoryFileInfo) {
+    ReduceFileMeta reduceFileMeta = ((ReduceFileMeta) memoryFileInfo.getFileMeta());
+    synchronized (reduceFileMeta.getSorted()) {
+      if (!reduceFileMeta.getSorted().get()) {
+        CompositeByteBuf originBuffer = memoryFileInfo.getBuffer();
+        Map<Integer, List<ShuffleBlockInfo>> blocksMap = new TreeMap<>();
+        int originReaderIndex = originBuffer.readerIndex();
+        int originWriterIndex = originBuffer.writerIndex();
+        int bufLength = originBuffer.readableBytes();
+        int index = 0;
+        ByteBuffer headerBuf = ByteBuffer.allocate(16);
+
+        while (index != bufLength) {
+          headerBuf.rewind();
+          originBuffer.readerIndex(index);
+          originBuffer.readBytes(headerBuf);
+          byte[] batchHeader = headerBuf.array();
+          int mapId = Platform.getInt(batchHeader, Platform.BYTE_ARRAY_OFFSET);
+          int compressedSize = Platform.getInt(batchHeader, Platform.BYTE_ARRAY_OFFSET + 12);
+          ShuffleBlockInfo shuffleBlockInfo = new ShuffleBlockInfo();
+          shuffleBlockInfo.offset = index;
+          shuffleBlockInfo.length = 16 + compressedSize;
+          List<ShuffleBlockInfo> singleMapIdShuffleBlockList =
+              blocksMap.computeIfAbsent(mapId, v -> new ArrayList<>());
+          singleMapIdShuffleBlockList.add(shuffleBlockInfo);
+          index += 16 + compressedSize;
+        }
+        originBuffer.setIndex(originReaderIndex, originWriterIndex);
+
+        // sorted buffer should not consolidate
+        // because this will affect origin buffer's reference count
+        CompositeByteBuf sortedBuffer =
+            MemoryManager.instance()
+                .getStoragePooledByteBufAllocator()
+                .compositeBuffer(Integer.MAX_VALUE - 1);
+        Map<Integer, List<ShuffleBlockInfo>> sortedBlocks = new TreeMap<>();
+        int sortedBufferIndex = 0;
+        for (Map.Entry<Integer, List<ShuffleBlockInfo>> entry : blocksMap.entrySet()) {
+          int mapId = entry.getKey();
+          List<ShuffleBlockInfo> blockInfos = entry.getValue();
+          List<ShuffleBlockInfo> sortedMapBlocks = new ArrayList<>(blockInfos.size());
+          sortedBlocks.put(mapId, sortedMapBlocks);
+          for (ShuffleBlockInfo blockInfo : blockInfos) {
+            int offset = (int) blockInfo.offset;
+            int length = (int) blockInfo.length;
+            ByteBuf slice = originBuffer.slice(offset, length);
+            sortedBuffer.addComponent(true, slice);
+            ShuffleBlockInfo shuffleBlockInfo = new ShuffleBlockInfo();
+            shuffleBlockInfo.offset = sortedBufferIndex;
+            shuffleBlockInfo.length = blockInfo.length;
+            sortedBufferIndex += (int) blockInfo.length;
+            sortedMapBlocks.add(shuffleBlockInfo);
+          }
+        }
+        memoryFileInfo.setSortedBuffer(sortedBuffer);
+        memoryFileInfo.setSortedIndexes(sortedBlocks);
+        reduceFileMeta.setSorted();
+      }
+    }
   }
 
   public void cleanup(HashSet<String> expiredShuffleKeys) {
@@ -301,7 +390,7 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
         }
       }
       long end = System.currentTimeMillis();
-      logger.info("Await partition sorter executor complete cost " + (end - start) + "ms");
+      logger.info("Await partition sorter executor complete cost {}ms", end - start);
     } else {
       fileSorterSchedulerThread.shutdownNow();
       fileSorterExecutors.shutdownNow();
@@ -355,8 +444,8 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
   }
 
   @VisibleForTesting
-  public Set<String> initSortedShuffleFiles(String shuffleKey) {
-    return sortedShuffleFiles.computeIfAbsent(shuffleKey, v -> ConcurrentHashMap.newKeySet());
+  public void initSortedShuffleFiles(String shuffleKey) {
+    sortedShuffleFiles.computeIfAbsent(shuffleKey, v -> ConcurrentHashMap.newKeySet());
   }
 
   @VisibleForTesting
@@ -494,7 +583,7 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
                 FileChannel indexChannel = null;
                 FSDataInputStream hdfsIndexStream = null;
                 boolean isHdfs = Utils.isHdfsPath(indexFilePath);
-                int indexSize = 0;
+                int indexSize;
                 try {
                   if (isHdfs) {
                     hdfsIndexStream = StorageManager.hadoopFs().open(new Path(indexFilePath));
@@ -536,7 +625,8 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
     ReduceFileMeta reduceFileMeta =
         new ReduceFileMeta(
             ShuffleBlockInfoUtils.getChunkOffsetsFromShuffleBlockInfos(
-                startMapIndex, endMapIndex, shuffleChunkSize, indexMap));
+                startMapIndex, endMapIndex, shuffleChunkSize, indexMap, false),
+            shuffleChunkSize);
     return new DiskFileInfo(userIdentifier, reduceFileMeta, sortedFilePath);
   }
 
@@ -585,7 +675,7 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
       }
     }
 
-    public void sort() throws InterruptedException {
+    public void sort() {
       source.startTimer(WorkerSource.SORT_TIME(), fileId);
 
       try {
@@ -640,7 +730,7 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
 
         writeIndex(sortedBlockInfoMap, indexFilePath, isHdfs);
         updateSortedShuffleFiles(shuffleKey, fileId, originFileLen);
-        ((ReduceFileMeta) originFileInfo.getFileMeta()).setSorted();
+        originFileInfo.getReduceFileMeta().setSorted();
         cleaner.add(this);
         logger.debug("sort complete for {} {}", shuffleKey, originFilePath);
       } catch (Exception e) {
@@ -699,7 +789,7 @@ public class PartitionFilesSorter extends ShuffleRecoverHelper {
     }
 
     public void deleteOriginFiles() throws IOException {
-      boolean deleteSuccess = false;
+      boolean deleteSuccess;
       if (isHdfs) {
         deleteSuccess = StorageManager.hadoopFs().delete(new Path(originFilePath), false);
       } else {
@@ -785,7 +875,7 @@ class PartitionFilesCleaner {
                 while (it.hasNext()) {
                   PartitionFilesSorter.FileSorter sorter = it.next();
                   try {
-                    if (((DiskFileInfo) sorter.getOriginFileInfo()).isStreamsEmpty()) {
+                    if (sorter.getOriginFileInfo().isStreamsEmpty()) {
                       logger.debug(
                           "Deleting the original files for shuffle key {}: {}",
                           sorter.getShuffleKey(),
diff --git a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriter.java b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriter.java
index 04d4d46d3..20910ab88 100644
--- a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriter.java
+++ b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriter.java
@@ -24,11 +24,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
-import org.apache.celeborn.common.meta.DiskFileInfo;
-import org.apache.celeborn.common.meta.ReduceFileMeta;
+import org.apache.celeborn.common.meta.FileInfo;
 import org.apache.celeborn.common.metrics.source.AbstractSource;
-import org.apache.celeborn.common.protocol.PartitionSplitMode;
-import org.apache.celeborn.common.protocol.PartitionType;
 
 /*
  * reduce partition file writer, it will create chunk index
@@ -36,49 +33,27 @@ import org.apache.celeborn.common.protocol.PartitionType;
 public final class ReducePartitionDataWriter extends PartitionDataWriter {
   private static final Logger logger = LoggerFactory.getLogger(ReducePartitionDataWriter.class);
 
-  private long nextBoundary;
-  private final long shuffleChunkSize;
-
   public ReducePartitionDataWriter(
       StorageManager storageManager,
-      DiskFileInfo diskFileInfo,
-      Flusher flusher,
       AbstractSource workerSource,
       CelebornConf conf,
       DeviceMonitor deviceMonitor,
-      long splitThreshold,
-      PartitionSplitMode splitMode,
-      boolean rangeReadFilter,
-      String shuffleKey)
+      PartitionDataWriterContext writerContext)
       throws IOException {
-    super(
-        storageManager,
-        diskFileInfo,
-        flusher,
-        workerSource,
-        conf,
-        deviceMonitor,
-        splitThreshold,
-        splitMode,
-        PartitionType.REDUCE,
-        rangeReadFilter,
-        shuffleKey);
-    this.shuffleChunkSize = conf.shuffleChunkSize();
-    this.nextBoundary = this.shuffleChunkSize;
+    super(storageManager, workerSource, conf, deviceMonitor, writerContext, true);
   }
 
-  @Override
-  protected void flush(boolean finalFlush) throws IOException {
-    super.flush(finalFlush);
-    maybeSetChunkOffsets(finalFlush);
+  private void updateLastChunkOffset() {
+    FileInfo fileInfo = getCurrentFileInfo();
+    fileInfo.getReduceFileMeta().updateChunkOffset(fileInfo.getFileLength(), true);
   }
 
-  private void maybeSetChunkOffsets(boolean forceSet) {
-    long bytesFlushed = diskFileInfo.getFileLength();
-    if (bytesFlushed >= nextBoundary || forceSet) {
-      ((ReduceFileMeta) diskFileInfo.getFileMeta()).addChunkOffset(bytesFlushed);
-      nextBoundary = bytesFlushed + shuffleChunkSize;
-    }
+  @Override
+  public void flush(boolean finalFlush, boolean fromEvict) throws IOException {
+    super.flush(finalFlush, fromEvict);
+    getCurrentFileInfo()
+        .getReduceFileMeta()
+        .updateChunkOffset(getCurrentFileInfo().getFileLength(), finalFlush);
   }
 
   private boolean isChunkOffsetValid() {
@@ -90,36 +65,57 @@ public final class ReducePartitionDataWriter extends PartitionDataWriter {
     // but its size is smaller than the nextBoundary, then the
     // chunk offset will not be set after flushing. we should
     // set it during FileWriter close.
-    return ((ReduceFileMeta) diskFileInfo.getFileMeta()).getLastChunkOffset()
-        == diskFileInfo.getFileLength();
+    if (diskFileInfo != null) {
+      return (diskFileInfo.getReduceFileMeta()).getLastChunkOffset()
+          == diskFileInfo.getFileLength();
+    }
+    if (memoryFileInfo != null) {
+      return (memoryFileInfo.getReduceFileMeta()).getLastChunkOffset()
+          == memoryFileInfo.getFileLength();
+    }
+    // this should not happen
+    return false;
   }
 
   @Override
   public synchronized long close() throws IOException {
-    return super.close(
-        () -> {
-          if (!isChunkOffsetValid()) {
-            maybeSetChunkOffsets(true);
-          }
-        },
-        () -> {
-          if (diskFileInfo.isHdfs()) {
-            if (StorageManager.hadoopFs().exists(diskFileInfo.getHdfsPeerWriterSuccessPath())) {
-              StorageManager.hadoopFs().delete(diskFileInfo.getHdfsPath(), false);
-              deleted = true;
-            } else {
-              StorageManager.hadoopFs().create(diskFileInfo.getHdfsWriterSuccessPath()).close();
-              FSDataOutputStream indexOutputStream =
-                  StorageManager.hadoopFs().create(diskFileInfo.getHdfsIndexPath());
-              indexOutputStream.writeInt(
-                  ((ReduceFileMeta) diskFileInfo.getFileMeta()).getChunkOffsets().size());
-              for (Long offset : ((ReduceFileMeta) diskFileInfo.getFileMeta()).getChunkOffsets()) {
-                indexOutputStream.writeLong(offset);
+    long streamId =
+        super.close(
+            () -> {
+              if (!isChunkOffsetValid()) {
+                updateLastChunkOffset();
+              }
+            },
+            () -> {
+              if (diskFileInfo != null) {
+                if (diskFileInfo.isHdfs()) {
+                  if (StorageManager.hadoopFs()
+                      .exists(diskFileInfo.getHdfsPeerWriterSuccessPath())) {
+                    StorageManager.hadoopFs().delete(diskFileInfo.getHdfsPath(), false);
+                    deleted = true;
+                  } else {
+                    StorageManager.hadoopFs()
+                        .create(diskFileInfo.getHdfsWriterSuccessPath())
+                        .close();
+                    FSDataOutputStream indexOutputStream =
+                        StorageManager.hadoopFs().create(diskFileInfo.getHdfsIndexPath());
+                    indexOutputStream.writeInt(
+                        (diskFileInfo.getReduceFileMeta()).getChunkOffsets().size());
+                    for (Long offset : (diskFileInfo.getReduceFileMeta()).getChunkOffsets()) {
+                      indexOutputStream.writeLong(offset);
+                    }
+                    indexOutputStream.close();
+                  }
+                }
+              } else {
+                synchronized (flushLock) {
+                  // merge and free small components
+                  flushBuffer.consolidate();
+                  memoryFileInfo.setBuffer(flushBuffer);
+                }
               }
-              indexOutputStream.close();
-            }
-          }
-        },
-        () -> {});
+            },
+            () -> {});
+    return streamId;
   }
 }
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Controller.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Controller.scala
index 7024ca889..6d9a44df1 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Controller.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Controller.scala
@@ -31,7 +31,7 @@ import org.roaringbitmap.RoaringBitmap
 import org.apache.celeborn.common.CelebornConf
 import org.apache.celeborn.common.identity.UserIdentifier
 import org.apache.celeborn.common.internal.Logging
-import org.apache.celeborn.common.meta.{WorkerInfo, WorkerPartitionLocationInfo}
+import org.apache.celeborn.common.meta.{ReduceFileMeta, WorkerInfo, WorkerPartitionLocationInfo}
 import org.apache.celeborn.common.metrics.MetricsSystem
 import org.apache.celeborn.common.protocol.{PartitionLocation, PartitionSplitMode, PartitionType, StorageInfo}
 import org.apache.celeborn.common.protocol.message.ControlMessages._
@@ -200,8 +200,8 @@ private[deploy] class Controller(
       logWarning(s"[handleReserveSlots] $msg, will destroy writers.")
       primaryLocs.asScala.foreach { partitionLocation =>
         val fileWriter = partitionLocation.asInstanceOf[WorkingPartition].getFileWriter
-        fileWriter.destroy(new IOException(s"Destroy FileWriter ${fileWriter} caused by " +
-          s"reserving slots failed for ${shuffleKey}."))
+        fileWriter.destroy(new IOException(s"Destroy FileWriter $fileWriter caused by " +
+          s"reserving slots failed for $shuffleKey."))
       }
       context.reply(ReserveSlotsResponse(StatusCode.RESERVE_SLOTS_FAILED, msg))
       return
@@ -240,13 +240,13 @@ private[deploy] class Controller(
       logWarning(s"[handleReserveSlots] $msg, destroy writers.")
       primaryLocs.asScala.foreach { partitionLocation =>
         val fileWriter = partitionLocation.asInstanceOf[WorkingPartition].getFileWriter
-        fileWriter.destroy(new IOException(s"Destroy FileWriter ${fileWriter} caused by " +
-          s"reserving slots failed for ${shuffleKey}."))
+        fileWriter.destroy(new IOException(s"Destroy FileWriter $fileWriter caused by " +
+          s"reserving slots failed for $shuffleKey."))
       }
       replicaLocs.asScala.foreach { partitionLocation =>
         val fileWriter = partitionLocation.asInstanceOf[WorkingPartition].getFileWriter
-        fileWriter.destroy(new IOException(s"Destroy FileWriter ${fileWriter} caused by " +
-          s"reserving slots failed for ${shuffleKey}."))
+        fileWriter.destroy(new IOException(s"Destroy FileWriter $fileWriter caused by " +
+          s"reserving slots failed for $shuffleKey."))
       }
       context.reply(ReserveSlotsResponse(StatusCode.RESERVE_SLOTS_FAILED, msg))
       return
@@ -346,14 +346,16 @@ private[deploy] class Controller(
   private def waitMapPartitionRegionFinished(
       fileWriter: PartitionDataWriter,
       waitTimeout: Long): Unit = {
-    if (fileWriter.isInstanceOf[MapPartitionDataWriter]) {
-      if (fileWriter.asInstanceOf[MapPartitionDataWriter].checkPartitionRegionFinished(
-          waitTimeout)) {
-        logDebug(s"CommitFile succeed to waitMapPartitionRegionFinished ${fileWriter.getFile.getAbsolutePath}")
-      } else {
-        logWarning(
-          s"CommitFile failed to waitMapPartitionRegionFinished ${fileWriter.getFile.getAbsolutePath}")
-      }
+    fileWriter match {
+      case writer: MapPartitionDataWriter =>
+        if (writer.checkPartitionRegionFinished(
+            waitTimeout)) {
+          logDebug(s"CommitFile succeed to waitMapPartitionRegionFinished ${fileWriter.getFile.getAbsolutePath}")
+        } else {
+          logWarning(
+            s"CommitFile failed to waitMapPartitionRegionFinished ${fileWriter.getFile.getAbsolutePath}")
+        }
+      case _ =>
     }
   }
 
@@ -424,11 +426,11 @@ private[deploy] class Controller(
 
     commitInfo.synchronized {
       if (commitInfo.status == CommitInfo.COMMIT_FINISHED) {
-        logInfo(s"${shuffleKey} CommitFinished, just return the response")
+        logInfo(s"$shuffleKey CommitFinished, just return the response")
         context.reply(commitInfo.response)
         return
       } else if (commitInfo.status == CommitInfo.COMMIT_INPROCESS) {
-        logInfo(s"${shuffleKey} CommitFiles inprogress, wait for finish")
+        logInfo(s"$shuffleKey CommitFiles inprogress, wait for finish")
         commitThreadPool.submit(new Runnable {
           override def run(): Unit = {
             waitForCommitFinish()
@@ -436,7 +438,7 @@ private[deploy] class Controller(
         })
         return
       } else {
-        logInfo(s"Start commitFiles for ${shuffleKey}")
+        logInfo(s"Start commitFiles for $shuffleKey")
         commitInfo.status = CommitInfo.COMMIT_INPROCESS
         workerSource.startTimer(WorkerSource.COMMIT_FILES_TIME, shuffleKey)
       }
@@ -529,10 +531,10 @@ private[deploy] class Controller(
           logInfo(
             s"CommitFiles for $shuffleKey success with " +
               s"${committedPrimaryIds.size()} committed primary partitions, " +
-              s"${emptyFilePrimaryIds.size()} empty primary partitions, " +
+              s"${emptyFilePrimaryIds.size()} empty primary partitions ${emptyFilePrimaryIds.asScala.mkString(",")}, " +
               s"${failedPrimaryIds.size()} failed primary partitions, " +
               s"${committedReplicaIds.size()} committed replica partitions, " +
-              s"${emptyFileReplicaIds.size()} empty replica partitions, " +
+              s"${emptyFileReplicaIds.size()} empty replica partitions ${emptyFileReplicaIds.asScala.mkString(",")} , " +
               s"${failedReplicaIds.size()} failed replica partitions.")
           CommitFilesResponse(
             StatusCode.SUCCESS,
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
index e9b1a6607..934a4e967 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
@@ -34,15 +34,15 @@ import org.apache.celeborn.common.CelebornConf
 import org.apache.celeborn.common.CelebornConf.MAX_CHUNKS_BEING_TRANSFERRED
 import org.apache.celeborn.common.exception.CelebornIOException
 import org.apache.celeborn.common.internal.Logging
-import org.apache.celeborn.common.meta.{DiskFileInfo, FileManagedBuffers, MapFileMeta, ReduceFileMeta}
-import org.apache.celeborn.common.network.buffer.NioManagedBuffer
+import org.apache.celeborn.common.meta.{DiskFileInfo, FileInfo, MapFileMeta, MemoryFileInfo, ReduceFileMeta}
+import org.apache.celeborn.common.network.buffer.{FileChunkBuffers, MemoryChunkBuffers, NioManagedBuffer}
 import org.apache.celeborn.common.network.client.{RpcResponseCallback, TransportClient}
 import org.apache.celeborn.common.network.protocol._
 import org.apache.celeborn.common.network.server.BaseMessageHandler
 import org.apache.celeborn.common.network.util.{NettyUtils, TransportConf}
 import org.apache.celeborn.common.protocol.{MessageType, PbBufferStreamEnd, PbChunkFetchRequest, PbOpenStream, PbOpenStreamList, PbOpenStreamListResponse, PbReadAddCredit, PbStreamHandler, PbStreamHandlerOpt, StreamType}
 import org.apache.celeborn.common.protocol.message.StatusCode
-import org.apache.celeborn.common.util.{ExceptionUtils, ThreadUtils, Utils}
+import org.apache.celeborn.common.util.{ExceptionUtils, Utils}
 import org.apache.celeborn.service.deploy.worker.storage.{ChunkStreamManager, CreditStreamManager, PartitionFilesSorter, StorageManager}
 
 class FetchHandler(
@@ -81,11 +81,11 @@ class FetchHandler(
     this.registered = Some(worker.registered)
   }
 
-  def getRawDiskFileInfo(
+  def getRawFileInfo(
       shuffleKey: String,
-      fileName: String): DiskFileInfo = {
+      fileName: String): FileInfo = {
     // find FileWriter responsible for the data
-    val fileInfo = storageManager.getDiskFileInfo(shuffleKey, fileName)
+    val fileInfo = storageManager.getFileInfo(shuffleKey, fileName)
     if (fileInfo == null) {
       val errMsg = s"Could not find file $fileName for $shuffleKey."
       logWarning(errMsg)
@@ -240,14 +240,14 @@ class FetchHandler(
         s"$endIndex get file name $fileName from client channel " +
         s"${NettyUtils.getRemoteAddress(client.getChannel)}")
 
-      var fileInfo = getRawDiskFileInfo(shuffleKey, fileName)
+      var fileInfo = getRawFileInfo(shuffleKey, fileName)
       val streamId = chunkStreamManager.nextStreamId()
       // we must get sorted fileInfo for the following cases.
       // 1. when the current request is a non-range openStream, but the original unsorted file
       //    has been deleted by another range's openStream request.
       // 2. when the current request is a range openStream request.
-      if ((endIndex != Int.MaxValue) || (endIndex == Int.MaxValue && !fileInfo.addStream(
-          streamId))) {
+      if ((endIndex != Int.MaxValue) || (endIndex == Int.MaxValue
+          && !fileInfo.addStream(streamId))) {
         fileInfo = partitionsSorter.getSortedFileInfo(
           shuffleKey,
           fileName,
@@ -255,9 +255,9 @@ class FetchHandler(
           startIndex,
           endIndex)
       }
-      val meta = fileInfo.getFileMeta.asInstanceOf[ReduceFileMeta]
+      val meta = fileInfo.getReduceFileMeta
       val streamHandler =
-        if (readLocalShuffle) {
+        if (readLocalShuffle && !fileInfo.isInstanceOf[MemoryFileInfo]) {
           chunkStreamManager.registerStream(
             streamId,
             shuffleKey,
@@ -266,31 +266,45 @@ class FetchHandler(
             streamId,
             meta.getNumChunks,
             meta.getChunkOffsets,
-            fileInfo.getFilePath)
-        } else if (fileInfo.isHdfs) {
-          chunkStreamManager.registerStream(
-            streamId,
-            shuffleKey,
-            fileName)
-          makeStreamHandler(streamId, numChunks = 0)
-        } else {
-          chunkStreamManager.registerStream(
-            streamId,
-            shuffleKey,
-            new FileManagedBuffers(fileInfo, transportConf),
-            fileName,
-            storageManager.getFetchTimeMetric(fileInfo.getFile))
-          if (meta.getNumChunks == 0)
-            logDebug(s"StreamId $streamId, fileName $fileName, mapRange " +
-              s"[$startIndex-$endIndex] is empty. Received from client channel " +
-              s"${NettyUtils.getRemoteAddress(client.getChannel)}")
-          else logDebug(
-            s"StreamId $streamId, fileName $fileName, numChunks ${meta.getNumChunks}, " +
-              s"mapRange [$startIndex-$endIndex]. Received from client channel " +
-              s"${NettyUtils.getRemoteAddress(client.getChannel)}")
-          makeStreamHandler(
-            streamId,
-            meta.getNumChunks)
+            fileInfo.asInstanceOf[DiskFileInfo].getFilePath)
+        } else fileInfo match {
+          case info: DiskFileInfo if info.isHdfs =>
+            chunkStreamManager.registerStream(
+              streamId,
+              shuffleKey,
+              fileName)
+            makeStreamHandler(streamId, numChunks = 0)
+          case _ =>
+            val managedBuffer = fileInfo match {
+              case df: DiskFileInfo =>
+                new FileChunkBuffers(df, transportConf)
+              case mf: MemoryFileInfo =>
+                new MemoryChunkBuffers(mf)
+            }
+            val fetchTimeMetric =
+              fileInfo match {
+                case info: DiskFileInfo =>
+                  storageManager.getFetchTimeMetric(info.getFile)
+                case _ =>
+                  null
+              }
+            chunkStreamManager.registerStream(
+              streamId,
+              shuffleKey,
+              managedBuffer,
+              fileName,
+              fetchTimeMetric)
+            if (meta.getNumChunks == 0)
+              logDebug(s"StreamId $streamId, fileName $fileName, mapRange " +
+                s"[$startIndex-$endIndex] is empty. Received from client channel " +
+                s"${NettyUtils.getRemoteAddress(client.getChannel)}")
+            else logDebug(
+              s"StreamId $streamId, fileName $fileName, numChunks ${meta.getNumChunks}, " +
+                s"mapRange [$startIndex-$endIndex]. Received from client channel " +
+                s"${NettyUtils.getRemoteAddress(client.getChannel)}")
+            makeStreamHandler(
+              streamId,
+              meta.getNumChunks)
         }
       workerSource.incCounter(WorkerSource.OPEN_STREAM_SUCCESS_COUNT)
       PbStreamHandlerOpt.newBuilder().setStreamHandler(streamHandler)
@@ -323,7 +337,7 @@ class FetchHandler(
     workerSource.recordAppActiveConnection(client, shuffleKey)
     workerSource.startTimer(WorkerSource.OPEN_STREAM_TIME, shuffleKey)
     try {
-      val fileInfo = getRawDiskFileInfo(shuffleKey, fileName)
+      val fileInfo = getRawFileInfo(shuffleKey, fileName)
       fileInfo.getFileMeta match {
         case _: ReduceFileMeta =>
           val pbStreamHandlerOpt =
@@ -358,7 +372,7 @@ class FetchHandler(
             initialCredit,
             startIndex,
             endIndex,
-            fileInfo)
+            fileInfo.asInstanceOf[DiskFileInfo])
       }
       workerSource.incCounter(WorkerSource.OPEN_STREAM_SUCCESS_COUNT)
     } catch {
@@ -440,10 +454,10 @@ class FetchHandler(
       streamType: StreamType): Unit = {
     streamType match {
       case StreamType.ChunkStream =>
-        val (shuffleKey, fileName) = chunkStreamManager.getShuffleKeyAndFileName(streamId)
+        val streamState = chunkStreamManager.getStreamState(streamId)
+        val (shuffleKey, fileName) = (streamState.shuffleKey, streamState.fileName)
         workerSource.recordAppActiveConnection(client, shuffleKey)
-        getRawDiskFileInfo(shuffleKey, fileName).closeStream(
-          streamId)
+        getRawFileInfo(shuffleKey, fileName).closeStream(streamId)
       case StreamType.CreditStream =>
         val shuffleKey = creditStreamManager.getStreamShuffleKey(streamId)
         if (shuffleKey != null) {
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/PushDataHandler.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/PushDataHandler.scala
index d21a1fdea..0fe387f58 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/PushDataHandler.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/PushDataHandler.scala
@@ -269,7 +269,7 @@ class PushDataHandler(val workerSource: WorkerSource) extends BaseMessageHandler
         s"[handlePushData] FileWriter is already closed! File path ${fileWriter.getDiskFileInfo.getFilePath}")
       callbackWithTimer.onFailure(new CelebornIOException("File already closed!"))
       fileWriter.decrementPendingWrites()
-      return;
+      return
     }
     val writePromise = Promise[Unit]()
     // for primary, send data to replica
@@ -816,7 +816,7 @@ class PushDataHandler(val workerSource: WorkerSource) extends BaseMessageHandler
         s"[handleMapPartitionPushData] FileWriter is already closed! File path ${fileWriter.getDiskFileInfo.getFilePath}")
       callback.onFailure(new CelebornIOException("File already closed!"))
       fileWriter.decrementPendingWrites()
-      return;
+      return
     }
     val writePromise = Promise[Unit]()
     writeLocalData(Seq(fileWriter), body, shuffleKey, isPrimary, None, writePromise)
@@ -867,7 +867,7 @@ class PushDataHandler(val workerSource: WorkerSource) extends BaseMessageHandler
   }
 
   private def mapPartitionRpcRequest(rpcRequest: RpcRequest)
-      : Tuple8[GeneratedMessageV3, Message, Boolean, Type, Mode, String, String, Boolean] = {
+      : (GeneratedMessageV3, Message, Boolean, Type, Mode, String, String, Boolean) = {
     try {
       val msg = TransportMessage.fromByteBuffer(
         rpcRequest.body().nioByteBuffer()).getParsedPayload.asInstanceOf[GeneratedMessageV3]
@@ -1185,7 +1185,7 @@ class PushDataHandler(val workerSource: WorkerSource) extends BaseMessageHandler
   }
 
   private def checkDiskFull(fileWriter: PartitionDataWriter): Boolean = {
-    if (fileWriter.flusher.isInstanceOf[HdfsFlusher]) {
+    if (fileWriter.flusher == null || fileWriter.flusher.isInstanceOf[HdfsFlusher]) {
       return false
     }
     val mountPoint = fileWriter.flusher.asInstanceOf[LocalFlusher].mountPoint
@@ -1206,28 +1206,34 @@ class PushDataHandler(val workerSource: WorkerSource) extends BaseMessageHandler
          |CheckDiskFullAndSplit in
          |diskFull:$diskFull,
          |partitionSplitMinimumSize:$partitionSplitMinimumSize,
-         |splitThreshold:${fileWriter.getSplitThreshold()},
+         |splitThreshold:${fileWriter.getSplitThreshold},
          |fileLength:${fileWriter.getDiskFileInfo.getFileLength}
          |fileName:${fileWriter.getDiskFileInfo.getFilePath}
          |""".stripMargin)
-    if (workerPartitionSplitEnabled && ((diskFull && fileWriter.getDiskFileInfo.getFileLength > partitionSplitMinimumSize) ||
-        (isPrimary && fileWriter.getDiskFileInfo.getFileLength > fileWriter.getSplitThreshold()))) {
-      if (softSplit != null && fileWriter.getSplitMode == PartitionSplitMode.SOFT &&
-        (fileWriter.getDiskFileInfo.getFileLength < partitionSplitMaximumSize)) {
-        softSplit.set(true)
-      } else {
-        workerSource.incCounter(WorkerSource.WRITE_DATA_HARD_SPLIT_COUNT)
-        callback.onSuccess(ByteBuffer.wrap(Array[Byte](StatusCode.HARD_SPLIT.getValue)))
-        logTrace(
-          s"""
-             |CheckDiskFullAndSplit hardSplit
-             |diskFull:$diskFull,
-             |partitionSplitMinimumSize:$partitionSplitMinimumSize,
-             |splitThreshold:${fileWriter.getSplitThreshold()},
-             |fileLength:${fileWriter.getDiskFileInfo.getFileLength},
-             |fileName:${fileWriter.getDiskFileInfo.getFilePath}
-             |""".stripMargin)
-        return true
+    if (fileWriter.needHardSplitForMemoryShuffleStorage()) {
+      return true
+    }
+    val diskFileInfo = fileWriter.getDiskFileInfo
+    if (diskFileInfo != null) {
+      if (workerPartitionSplitEnabled && ((diskFull && diskFileInfo.getFileLength > partitionSplitMinimumSize) ||
+          (isPrimary && diskFileInfo.getFileLength > fileWriter.getSplitThreshold))) {
+        if (softSplit != null && fileWriter.getSplitMode == PartitionSplitMode.SOFT &&
+          (fileWriter.getDiskFileInfo.getFileLength < partitionSplitMaximumSize)) {
+          softSplit.set(true)
+        } else {
+          workerSource.incCounter(WorkerSource.WRITE_DATA_HARD_SPLIT_COUNT)
+          callback.onSuccess(ByteBuffer.wrap(Array[Byte](StatusCode.HARD_SPLIT.getValue)))
+          logTrace(
+            s"""
+               |CheckDiskFullAndSplit hardSplit
+               |diskFull:$diskFull,
+               |partitionSplitMinimumSize:$partitionSplitMinimumSize,
+               |splitThreshold:${fileWriter.getSplitThreshold},
+               |fileLength:${diskFileInfo.getFileLength},
+               |fileName:${diskFileInfo.getFilePath}
+               |""".stripMargin)
+          return true
+        }
       }
     }
     false
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Worker.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Worker.scala
index 07d29e465..f3039fc20 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Worker.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/Worker.scala
@@ -170,7 +170,7 @@ private[celeborn] class Worker(
 
   val storageManager = new StorageManager(conf, workerSource)
 
-  val memoryManager: MemoryManager = MemoryManager.initialize(conf)
+  val memoryManager: MemoryManager = MemoryManager.initialize(conf, storageManager)
   memoryManager.registerMemoryListener(storageManager)
 
   val partitionsSorter = new PartitionFilesSorter(memoryManager, conf, workerSource)
@@ -386,11 +386,24 @@ private[celeborn] class Worker(
   workerSource.addGauge(WorkerSource.READ_BUFFER_ALLOCATED_COUNT) { () =>
     memoryManager.getAllocatedReadBuffers
   }
+  workerSource.addGauge(WorkerSource.MEMORY_FILE_STORAGE_SIZE) { () =>
+    memoryManager.getMemoryFileStorageCounter
+  }
+  workerSource.addGauge(WorkerSource.DIRECT_MEMORY_USAGE_RATIO) { () =>
+    memoryManager.workerMemoryUsageRatio()
+  }
+  workerSource.addGauge(WorkerSource.EVICTED_FILE_COUNT) { () =>
+    storageManager.evictedFileCount.get()
+  }
+  workerSource.addGauge(WorkerSource.MEMORY_STORAGE_FILE_COUNT) { () =>
+    storageManager.memoryWriters.size()
+  }
+
   workerSource.addGauge(WorkerSource.ACTIVE_SHUFFLE_SIZE) { () =>
-    storageManager.getActiveShuffleSize()
+    storageManager.getActiveShuffleSize
   }
   workerSource.addGauge(WorkerSource.ACTIVE_SHUFFLE_FILE_COUNT) { () =>
-    storageManager.getActiveShuffleFileCount()
+    storageManager.getActiveShuffleFileCount
   }
   workerSource.addGauge(WorkerSource.PAUSE_PUSH_DATA_TIME) { () =>
     memoryManager.getPausePushDataTime
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/WorkerSource.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/WorkerSource.scala
index 6be2a9256..726f7acec 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/WorkerSource.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/WorkerSource.scala
@@ -187,6 +187,11 @@ object WorkerSource {
   val BUFFER_STREAM_READ_BUFFER = "BufferStreamReadBuffer"
   val READ_BUFFER_DISPATCHER_REQUESTS_LENGTH = "ReadBufferDispatcherRequestsLength"
   val READ_BUFFER_ALLOCATED_COUNT = "ReadBufferAllocatedCount"
+  val MEMORY_FILE_STORAGE_SIZE = "MemoryFileStorageSize"
+  val DIRECT_MEMORY_USAGE_RATIO = "DirectMemoryUsageRatio"
+  val EVICTED_FILE_COUNT = "EvictedFileCount"
+
+  val MEMORY_STORAGE_FILE_COUNT = "MemoryStorageFileCount"
 
   // credit
   val ACTIVE_CREDIT_STREAM_COUNT = "ActiveCreditStreamCount"
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/FlushTask.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/FlushTask.scala
index 6c6bc64aa..607e47ddc 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/FlushTask.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/FlushTask.scala
@@ -24,14 +24,16 @@ import org.apache.hadoop.fs.Path
 
 abstract private[worker] class FlushTask(
     val buffer: CompositeByteBuf,
-    val notifier: FlushNotifier) {
+    val notifier: FlushNotifier,
+    val keepBuffer: Boolean) {
   def flush(): Unit
 }
 
 private[worker] class LocalFlushTask(
     buffer: CompositeByteBuf,
     fileChannel: FileChannel,
-    notifier: FlushNotifier) extends FlushTask(buffer, notifier) {
+    notifier: FlushNotifier,
+    keepBuffer: Boolean) extends FlushTask(buffer, notifier, keepBuffer) {
   override def flush(): Unit = {
     val buffers = buffer.nioBuffers()
     for (buffer <- buffers) {
@@ -45,7 +47,8 @@ private[worker] class LocalFlushTask(
 private[worker] class HdfsFlushTask(
     buffer: CompositeByteBuf,
     val path: Path,
-    notifier: FlushNotifier) extends FlushTask(buffer, notifier) {
+    notifier: FlushNotifier,
+    keepBuffer: Boolean) extends FlushTask(buffer, notifier, keepBuffer) {
   override def flush(): Unit = {
     val hdfsStream = StorageManager.hadoopFs.append(path, 256 * 1024)
     hdfsStream.write(ByteBufUtil.getBytes(buffer))
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/Flusher.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/Flusher.scala
index ffee36f0c..2ac06b829 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/Flusher.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/Flusher.scala
@@ -77,17 +77,19 @@ abstract private[worker] class Flusher(
                   }
                 } catch {
                   case t: Throwable =>
-                    if (t.isInstanceOf[IOException]) {
-                      task.notifier.setException(t.asInstanceOf[IOException])
-                      processIOException(
-                        t.asInstanceOf[IOException],
-                        DiskStatus.READ_OR_WRITE_FAILURE)
+                    t match {
+                      case exception: IOException =>
+                        task.notifier.setException(exception)
+                        processIOException(
+                          exception,
+                          DiskStatus.READ_OR_WRITE_FAILURE)
+                      case _ =>
                     }
-                    logWarning(s"Flusher-${this}-thread-${index} encounter exception.", t)
+                    logWarning(s"Flusher-$this-thread-$index encounter exception.", t)
                 }
                 lastBeginFlushTime.set(index, -1)
               }
-              Utils.tryLogNonFatalError(returnBuffer(task.buffer))
+              Utils.tryLogNonFatalError(returnBuffer(task.buffer, task.keepBuffer))
               task.notifier.numPendingFlushes.decrementAndGet()
             }
           }
@@ -97,11 +99,6 @@ abstract private[worker] class Flusher(
     ThreadPoolSource.registerSource(s"$this", workers)
   }
 
-  def getWorkerIndex: Int = synchronized {
-    nextWorkerIndex = (nextWorkerIndex + 1) % threadCount
-    nextWorkerIndex
-  }
-
   def takeBuffer(): CompositeByteBuf = {
     var buffer = bufferQueue.poll()
     if (buffer == null) {
@@ -110,15 +107,19 @@ abstract private[worker] class Flusher(
     buffer
   }
 
-  def returnBuffer(buffer: CompositeByteBuf): Unit = {
-    MemoryManager.instance().releaseDiskBuffer(buffer.readableBytes())
+  def returnBuffer(buffer: CompositeByteBuf, keepBuffer: Boolean = false): Unit = {
+    val bufferSize = buffer.readableBytes()
+    MemoryManager.instance().releaseDiskBuffer(bufferSize)
     Option(CongestionController.instance())
       .foreach(
-        _.consumeBytes(buffer.readableBytes()))
+        _.consumeBytes(bufferSize))
     buffer.removeComponents(0, buffer.numComponents())
     buffer.clear()
-
-    bufferQueue.put(buffer)
+    if (keepBuffer) {
+      bufferQueue.put(buffer)
+    } else {
+      buffer.release()
+    }
   }
 
   def addTask(task: FlushTask, timeoutMs: Long, workerIndex: Int): Boolean = {
diff --git a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
index 82e0f2ef1..857939929 100644
--- a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
+++ b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
@@ -22,12 +22,13 @@ import java.nio.charset.StandardCharsets
 import java.nio.file.{FileAlreadyExistsException, Files, Paths}
 import java.util
 import java.util.concurrent.{ConcurrentHashMap, ScheduledExecutorService, ThreadPoolExecutor, TimeUnit}
-import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.{AtomicInteger, AtomicLong}
 import java.util.function.{BiConsumer, IntUnaryOperator}
 
 import scala.collection.JavaConverters._
 import scala.concurrent.duration._
 
+import com.google.common.annotations.VisibleForTesting
 import io.netty.buffer.PooledByteBufAllocator
 import org.apache.commons.io.FileUtils
 import org.apache.hadoop.fs.{FileSystem, Path}
@@ -37,22 +38,32 @@ import org.apache.celeborn.common.CelebornConf
 import org.apache.celeborn.common.exception.CelebornException
 import org.apache.celeborn.common.identity.UserIdentifier
 import org.apache.celeborn.common.internal.Logging
-import org.apache.celeborn.common.meta.{DeviceInfo, DiskFileInfo, DiskInfo, DiskStatus, MapFileMeta, ReduceFileMeta, TimeWindow}
+import org.apache.celeborn.common.meta.{DeviceInfo, DiskFileInfo, DiskInfo, DiskStatus, FileInfo, MapFileMeta, MemoryFileInfo, ReduceFileMeta, TimeWindow}
 import org.apache.celeborn.common.metrics.source.{AbstractSource, ThreadPoolSource}
 import org.apache.celeborn.common.network.util.{NettyUtils, TransportConf}
 import org.apache.celeborn.common.protocol.{PartitionLocation, PartitionSplitMode, PartitionType, StorageInfo}
 import org.apache.celeborn.common.quota.ResourceConsumption
 import org.apache.celeborn.common.util.{CelebornExitKind, CelebornHadoopUtils, JavaUtils, PbSerDeUtils, ThreadUtils, Utils}
 import org.apache.celeborn.service.deploy.worker._
+import org.apache.celeborn.service.deploy.worker.memory.MemoryManager
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager.MemoryPressureListener
 import org.apache.celeborn.service.deploy.worker.shuffledb.{DB, DBBackend, DBProvider}
 import org.apache.celeborn.service.deploy.worker.storage.StorageManager.hadoopFs
 
 final private[worker] class StorageManager(conf: CelebornConf, workerSource: AbstractSource)
   extends ShuffleRecoverHelper with DeviceObserver with Logging with MemoryPressureListener {
+  // fileInfos and partitionDataWriters are one to one mapping
   // mount point -> file writer
   val workingDirWriters =
     JavaUtils.newConcurrentHashMap[File, ConcurrentHashMap[String, PartitionDataWriter]]()
+  val hdfsWriters = JavaUtils.newConcurrentHashMap[String, PartitionDataWriter]()
+  val memoryWriters = JavaUtils.newConcurrentHashMap[MemoryFileInfo, PartitionDataWriter]()
+  // (shuffleKey->(filename->DiskFileInfo))
+  private val diskFileInfos =
+    JavaUtils.newConcurrentHashMap[String, ConcurrentHashMap[String, DiskFileInfo]]()
+  // (shuffleKey->(filename->MemoryFileInfo))
+  val memoryFileInfos =
+    JavaUtils.newConcurrentHashMap[String, ConcurrentHashMap[String, MemoryFileInfo]]()
 
   val hasHDFSStorage = conf.hasHDFSStorage
 
@@ -107,21 +118,22 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
   private val deviceMonitor =
     DeviceMonitor.createDeviceMonitor(conf, this, deviceInfos, tmpDiskInfos, workerSource)
 
-  private val byteBufAllocator: PooledByteBufAllocator =
+  val storageBufferAllocator: PooledByteBufAllocator =
     NettyUtils.getPooledByteBufAllocator(new TransportConf("StorageManager", conf), null, true)
+
   // (mountPoint -> LocalFlusher)
   private val (
     localFlushers: ConcurrentHashMap[String, LocalFlusher],
     _totalLocalFlusherThread: Int) = {
     val flushers = JavaUtils.newConcurrentHashMap[String, LocalFlusher]()
-    var totalThread = 0;
+    var totalThread = 0
     disksSnapshot().foreach { diskInfo =>
       if (!flushers.containsKey(diskInfo.mountPoint)) {
         val flusher = new LocalFlusher(
           workerSource,
           deviceMonitor,
           diskInfo.threadCount,
-          byteBufAllocator,
+          storageBufferAllocator,
           conf.workerPushMaxComponents,
           diskInfo.mountPoint,
           diskInfo.storageType,
@@ -137,10 +149,9 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
 
   val hdfsDir = conf.hdfsDir
   val hdfsPermission = new FsPermission("755")
-  val hdfsWriters = JavaUtils.newConcurrentHashMap[String, PartitionDataWriter]()
   val (hdfsFlusher, _totalHdfsFlusherThread) =
     if (hasHDFSStorage) {
-      logInfo(s"Initialize HDFS support with path ${hdfsDir}")
+      logInfo(s"Initialize HDFS support with path $hdfsDir")
       try {
         StorageManager.hadoopFs = CelebornHadoopUtils.getHadoopFS(conf)
       } catch {
@@ -152,7 +163,7 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
         Some(new HdfsFlusher(
           workerSource,
           conf.workerHdfsFlusherThreads,
-          byteBufAllocator,
+          storageBufferAllocator,
           conf.workerPushMaxComponents)),
         conf.workerHdfsFlusherThreads)
     } else {
@@ -160,6 +171,12 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
     }
 
   def totalFlusherThread: Int = _totalLocalFlusherThread + _totalHdfsFlusherThread
+  val activeTypes = conf.availableStorageTypes
+
+  def localOrHdfsStorageAvailable(): Boolean = {
+    StorageInfo.HDFSAvailable(activeTypes) || StorageInfo.localDiskAvailable(
+      activeTypes) || hdfsDir.nonEmpty || !diskInfos.isEmpty
+  }
 
   override def notifyError(mountPoint: String, diskStatus: DiskStatus): Unit = this.synchronized {
     if (diskStatus == DiskStatus.CRITICAL_ERROR) {
@@ -193,11 +210,9 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
   }
 
   // shuffleKey -> (fileName -> file info)
-  private val diskFileInfos =
-    JavaUtils.newConcurrentHashMap[String, ConcurrentHashMap[String, DiskFileInfo]]()
   private val RECOVERY_FILE_NAME_PREFIX = "recovery"
   private var RECOVERY_FILE_NAME = "recovery.ldb"
-  private var db: DB = null
+  private var db: DB = _
   private var saveCommittedFileInfosExecutor: ScheduledExecutorService = _
   private val saveCommittedFileInfoBySyncMethod =
     conf.workerGracefulShutdownSaveCommittedFileInfoSync
@@ -293,12 +308,18 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
         logDebug(s"Update FileInfos into DB: $shuffleKey -> $files")
       } catch {
         case exception: Exception =>
-          logError(s"Update FileInfos into DB: ${shuffleKey} failed.", exception)
+          logError(s"Update FileInfos into DB: $shuffleKey failed.", exception)
       }
     }
   }
 
-  private def getNextIndex() = counter.getAndUpdate(counterOperator)
+  def saveAllMemoryStorageFileInfosToDB(): Unit = {
+    for (writer <- memoryWriters.asScala) {
+      Utils.tryLogNonFatalError(writer._2.evict(false))
+    }
+  }
+
+  private def getNextIndex = counter.getAndUpdate(counterOperator)
 
   private val newMapFunc =
     new java.util.function.Function[String, ConcurrentHashMap[String, DiskFileInfo]]() {
@@ -312,12 +333,21 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
         JavaUtils.newConcurrentHashMap()
     }
 
+  private val memoryFileInfoMapFunc =
+    new java.util.function.Function[String, ConcurrentHashMap[String, MemoryFileInfo]]() {
+      override def apply(key: String): ConcurrentHashMap[String, MemoryFileInfo] =
+        JavaUtils.newConcurrentHashMap()
+    }
+
   private val workingDirWriterListFunc =
     new java.util.function.Function[File, ConcurrentHashMap[String, PartitionDataWriter]]() {
       override def apply(t: File): ConcurrentHashMap[String, PartitionDataWriter] =
         JavaUtils.newConcurrentHashMap[String, PartitionDataWriter]()
     }
 
+  @VisibleForTesting
+  val evictedFileCount = new AtomicLong
+
   @throws[IOException]
   def createPartitionDataWriter(
       appId: String,
@@ -354,40 +384,32 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
     if (healthyWorkingDirs().size <= 0 && !hasHDFSStorage) {
       throw new IOException("No available working dirs!")
     }
-    val shuffleKey = Utils.makeShuffleKey(appId, shuffleId)
-    val (flusher, diskFileInfo, workingDir) = createFile(
+    val partitionDataWriterContext = new PartitionDataWriterContext(
+      splitThreshold,
+      splitMode,
+      rangeReadFilter,
       location,
       appId,
       shuffleId,
-      location.getFileName,
       userIdentifier,
       partitionType,
       partitionSplitEnabled)
+
     val writer =
       try {
         partitionType match {
           case PartitionType.MAP => new MapPartitionDataWriter(
               this,
-              diskFileInfo,
-              flusher,
               workerSource,
               conf,
               deviceMonitor,
-              splitThreshold,
-              splitMode,
-              rangeReadFilter,
-              shuffleKey)
+              partitionDataWriterContext)
           case PartitionType.REDUCE => new ReducePartitionDataWriter(
               this,
-              diskFileInfo,
-              flusher,
               workerSource,
               conf,
               deviceMonitor,
-              splitThreshold,
-              splitMode,
-              rangeReadFilter,
-              shuffleKey)
+              partitionDataWriterContext)
           case _ => throw new UnsupportedOperationException(s"Not support $partitionType yet")
         }
       } catch {
@@ -395,24 +417,58 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
           logError("Create partition data writer failed", e)
           throw e
       }
-    if (!(writer.getDiskFileInfo.isHdfs)) {
-      deviceMonitor.registerFileWriter(writer)
-      workingDirWriters.computeIfAbsent(workingDir, workingDirWriterListFunc).put(
-        diskFileInfo.getFilePath,
-        writer)
-    } else {
-      hdfsWriters.put(diskFileInfo.getFilePath, writer)
-    }
     writer
   }
 
-  def getDiskFileInfo(shuffleKey: String, fileName: String): DiskFileInfo = {
-    val shuffleMap = diskFileInfos.get(shuffleKey)
-    if (shuffleMap ne null) {
-      shuffleMap.get(fileName)
-    } else {
-      null
+  def registerMemoryPartitionWriter(writer: PartitionDataWriter, fileInfo: MemoryFileInfo): Unit = {
+    memoryWriters.put(fileInfo, writer)
+  }
+
+  def unregisterMemoryPartitionWriterAndFileInfo(
+      fileInfo: MemoryFileInfo,
+      shuffleKey: String,
+      fileName: String): Unit = {
+    memoryWriters.remove(fileInfo)
+    val map = memoryFileInfos.get(shuffleKey)
+    if (map != null) {
+      map.remove(fileName)
+    }
+  }
+
+  def registerDiskFilePartitionWriter(
+      writer: PartitionDataWriter,
+      workingDir: File,
+      fileInfo: DiskFileInfo): Unit = {
+    if (writer.getDiskFileInfo.isHdfs) {
+      hdfsWriters.put(fileInfo.getFilePath, writer)
+      return
+    }
+    deviceMonitor.registerFileWriter(writer)
+    workingDirWriters.computeIfAbsent(workingDir, workingDirWriterListFunc).put(
+      fileInfo.getFilePath,
+      writer)
+  }
+
+  def getFileInfo(
+      shuffleKey: String,
+      fileName: String): FileInfo = {
+    val memoryShuffleMap = memoryFileInfos.get(shuffleKey)
+    if (memoryShuffleMap != null) {
+      val memoryFileInfo = memoryShuffleMap.get(fileName)
+      if (memoryFileInfo != null) {
+        return memoryFileInfo
+      }
     }
+
+    val diskShuffleMap = diskFileInfos.get(shuffleKey)
+    if (diskShuffleMap != null) {
+      val diskFileInfo = diskShuffleMap.get(fileName)
+      if (diskFileInfo != null) {
+        return diskFileInfo
+      }
+    }
+
+    null
   }
 
   def getFetchTimeMetric(file: File): TimeWindow = {
@@ -443,34 +499,40 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
   }
 
   def cleanFile(shuffleKey: String, fileName: String): Unit = {
-    val fileInfo = getDiskFileInfo(shuffleKey, fileName)
-    if (fileInfo != null) {
-      cleanFileInternal(shuffleKey, fileInfo)
-    }
+    cleanFileInternal(shuffleKey, getFileInfo(shuffleKey, fileName))
   }
 
-  def cleanFileInternal(shuffleKey: String, fileInfo: DiskFileInfo): Boolean = {
+  def cleanFileInternal(shuffleKey: String, fileInfo: FileInfo): Boolean = {
+    if (fileInfo == null) return false
     var isHdfsExpired = false
-    if (fileInfo.isHdfs) {
-      isHdfsExpired = true
-      val hdfsFileWriter = hdfsWriters.get(fileInfo.getFilePath)
-      if (hdfsFileWriter != null) {
-        hdfsFileWriter.destroy(new IOException(
-          s"Destroy FileWriter $hdfsFileWriter caused by shuffle $shuffleKey expired."))
-        hdfsWriters.remove(fileInfo.getFilePath)
-      }
-    } else {
-      val workingDir =
-        fileInfo.getFile.getParentFile.getParentFile.getParentFile
-      val writers = workingDirWriters.get(workingDir)
-      if (writers != null) {
-        val fileWriter = writers.get(fileInfo.getFilePath)
-        if (fileWriter != null) {
-          fileWriter.destroy(new IOException(
-            s"Destroy FileWriter $fileWriter caused by shuffle $shuffleKey expired."))
-          writers.remove(fileInfo.getFilePath)
+    fileInfo match {
+      case info: DiskFileInfo =>
+        if (info.isHdfs) {
+          isHdfsExpired = true
+          val hdfsFileWriter = hdfsWriters.get(info.getFilePath)
+          if (hdfsFileWriter != null) {
+            hdfsFileWriter.destroy(new IOException(
+              s"Destroy FileWriter $hdfsFileWriter caused by shuffle $shuffleKey expired."))
+            hdfsWriters.remove(info.getFilePath)
+          }
+        } else {
+          val workingDir =
+            info.getFile.getParentFile.getParentFile.getParentFile
+          val writers = workingDirWriters.get(workingDir)
+          if (writers != null) {
+            val fileWriter = writers.get(info.getFilePath)
+            if (fileWriter != null) {
+              fileWriter.destroy(new IOException(
+                s"Destroy FileWriter $fileWriter caused by shuffle $shuffleKey expired."))
+              writers.remove(info.getFilePath)
+            }
+          }
         }
-      }
+      case mInfo: MemoryFileInfo =>
+        val memoryWriter = memoryWriters.remove(mInfo)
+        memoryWriter.destroy(new IOException(
+          s"Destroy FileWriter $memoryWriter caused by shuffle $shuffleKey expired."))
+      case _ =>
     }
 
     isHdfsExpired
@@ -517,6 +579,13 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
           }
         }
       }
+      if (memoryFileInfos.containsKey(shuffleKey)) {
+        val memoryFileMaps = memoryFileInfos.remove(shuffleKey)
+        memoryFileMaps.asScala.foreach(u => {
+          cleanFileInternal(shuffleKey, u._2)
+          MemoryManager.instance().releaseMemoryFileStorage(u._2.releaseMemoryBuffers())
+        })
+      }
     }
   }
 
@@ -630,6 +699,7 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
     if (db != null) {
       if (exitKind == CelebornExitKind.WORKER_GRACEFUL_SHUTDOWN) {
         try {
+          saveAllMemoryStorageFileInfosToDB()
           saveAllCommittedFileInfosToDB()
           db.close()
         } catch {
@@ -783,15 +853,83 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
     committedFileInfos.computeIfAbsent(shuffleKey, newMapFunc).put(fileName, fileInfo)
   }
 
-  def getActiveShuffleSize(): Long = {
-    diskFileInfos.values().asScala.map(_.values().asScala.map(_.getBytesFlushed).sum).sum
+  def getActiveShuffleSize: Long = {
+    diskFileInfos.values().asScala.map(_.values().asScala.map(_.getFileLength).sum).sum
   }
 
-  def getActiveShuffleFileCount(): Long = {
+  def getActiveShuffleFileCount: Long = {
     diskFileInfos.asScala.values.map(_.size()).sum
   }
 
   def createFile(
+      partitionDataWriterContext: PartitionDataWriterContext,
+      useMemoryShuffle: Boolean): (MemoryFileInfo, Flusher, DiskFileInfo, File) = {
+    logDebug(
+      s"create file for ${partitionDataWriterContext.getShuffleKey} ${partitionDataWriterContext.getPartitionLocation.getFileName}")
+    val location = partitionDataWriterContext.getPartitionLocation
+    if (useMemoryShuffle
+      && location.getStorageInfo.memoryAvailable()
+      && MemoryManager.instance().memoryFileStorageAvailable()) {
+      logDebug(s"Create memory file for ${partitionDataWriterContext.getShuffleKey} ${partitionDataWriterContext.getPartitionLocation.getFileName}")
+      (
+        createMemoryFile(
+          partitionDataWriterContext.getAppId,
+          partitionDataWriterContext.getShuffleId,
+          location.getFileName,
+          partitionDataWriterContext.getUserIdentifier,
+          partitionDataWriterContext.getPartitionType,
+          partitionDataWriterContext.isPartitionSplitEnabled),
+        null,
+        null,
+        null)
+    } else if (location.getStorageInfo.localDiskAvailable() || location.getStorageInfo.HDFSAvailable()) {
+      logDebug(s"create non-memory file for ${partitionDataWriterContext.getShuffleKey} ${partitionDataWriterContext.getPartitionLocation.getFileName}")
+      val createDiskFileResult = createDiskFile(
+        location,
+        partitionDataWriterContext.getAppId,
+        partitionDataWriterContext.getShuffleId,
+        location.getFileName,
+        partitionDataWriterContext.getUserIdentifier,
+        partitionDataWriterContext.getPartitionType,
+        partitionDataWriterContext.isPartitionSplitEnabled)
+      (null, createDiskFileResult._1, createDiskFileResult._2, createDiskFileResult._3)
+    } else {
+      (null, null, null, null)
+    }
+  }
+
+  def createMemoryFile(
+      appId: String,
+      shuffleId: Int,
+      fileName: String,
+      userIdentifier: UserIdentifier,
+      partitionType: PartitionType,
+      partitionSplitEnabled: Boolean): MemoryFileInfo = {
+    val fileMeta = partitionType match {
+      case PartitionType.REDUCE =>
+        new ReduceFileMeta(conf.shuffleChunkSize)
+      case PartitionType.MAP =>
+        new MapFileMeta()
+      case PartitionType.MAPGROUP =>
+        throw new NotImplementedError("Map group is not implemented")
+    }
+    val shuffleKey = Utils.makeShuffleKey(appId, shuffleId)
+    val memoryFileInfo =
+      new MemoryFileInfo(
+        userIdentifier,
+        partitionSplitEnabled,
+        fileMeta)
+    logDebug(s"create memory file for ${shuffleKey} ${fileName} and put it int memoryFileInfos")
+    memoryFileInfos.computeIfAbsent(shuffleKey, memoryFileInfoMapFunc).put(
+      fileName,
+      memoryFileInfo)
+    memoryFileInfo
+  }
+
+  /**
+   * @return (Flusher,DiskFileInfo,workingDir)
+   */
+  def createDiskFile(
       location: PartitionLocation,
       appId: String,
       shuffleId: Int,
@@ -825,7 +963,7 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
         val hdfsFileInfo = new DiskFileInfo(
           userIdentifier,
           partitionSplitEnabled,
-          new ReduceFileMeta(),
+          new ReduceFileMeta(conf.shuffleChunkSize),
           hdfsFilePath,
           StorageInfo.Type.HDFS)
         diskFileInfos.computeIfAbsent(shuffleKey, diskFileInfoMapFunc).put(
@@ -833,7 +971,7 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
           hdfsFileInfo)
         return (hdfsFlusher.get, hdfsFileInfo, null)
       } else if (dirs.nonEmpty && location.getStorageInfo.localDiskAvailable()) {
-        val dir = dirs(getNextIndex() % dirs.size)
+        val dir = dirs(getNextIndex % dirs.size)
         val mountPoint = DeviceInfo.getMountPoint(dir.getAbsolutePath, mountPoints)
         val shuffleDir = new File(dir, s"$appId/$shuffleId")
         shuffleDir.mkdirs()
@@ -852,7 +990,7 @@ final private[worker] class StorageManager(conf: CelebornConf, workerSource: Abs
           val filePath = file.getAbsolutePath
           val fileMeta = partitionType match {
             case PartitionType.REDUCE =>
-              new ReduceFileMeta()
+              new ReduceFileMeta(conf.shuffleChunkSize)
             case PartitionType.MAP =>
               new MapFileMeta()
             case PartitionType.MAPGROUP =>
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/FetchHandlerSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/FetchHandlerSuiteJ.java
index ef4f172bf..b54a0b99a 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/FetchHandlerSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/FetchHandlerSuiteJ.java
@@ -18,7 +18,6 @@
 package org.apache.celeborn.service.deploy.worker;
 
 import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.*;
 
 import java.io.File;
@@ -47,7 +46,6 @@ import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.identity.UserIdentifier;
 import org.apache.celeborn.common.meta.DiskFileInfo;
 import org.apache.celeborn.common.meta.FileInfo;
-import org.apache.celeborn.common.meta.ReduceFileMeta;
 import org.apache.celeborn.common.network.buffer.NioManagedBuffer;
 import org.apache.celeborn.common.network.client.RpcResponseCallback;
 import org.apache.celeborn.common.network.client.TransportClient;
@@ -80,7 +78,7 @@ public class FetchHandlerSuiteJ {
 
   private static final Logger LOG = LoggerFactory.getLogger(FetchHandlerSuiteJ.class);
 
-  private static CelebornConf conf = new CelebornConf();
+  private static final CelebornConf conf = new CelebornConf();
 
   private static final Random random = new Random();
 
@@ -93,7 +91,7 @@ public class FetchHandlerSuiteJ {
     byte[] batchHeader = new byte[16];
     File shuffleFile = File.createTempFile("celeborn", UUID.randomUUID().toString());
 
-    FileInfo fileInfo = new DiskFileInfo(shuffleFile, userIdentifier);
+    DiskFileInfo fileInfo = new DiskFileInfo(shuffleFile, userIdentifier, conf);
     FileOutputStream fileOutputStream = new FileOutputStream(shuffleFile);
     FileChannel channel = fileOutputStream.getChannel();
     Map<Integer, Integer> batchIds = new HashMap<>();
@@ -132,10 +130,10 @@ public class FetchHandlerSuiteJ {
     for (long offset = conf.shuffleChunkSize();
         offset <= originFileLen;
         offset += conf.shuffleChunkSize()) {
-      ((ReduceFileMeta) fileInfo.getFileMeta()).getChunkOffsets().add(offset);
+      (fileInfo.getReduceFileMeta()).addChunkOffset(offset);
     }
     // update sorted fileInfo chunk offsets
-    ((DiskFileInfo) fileInfo).updateBytesFlushed(originFileLen);
+    fileInfo.updateBytesFlushed(originFileLen);
     return fileInfo;
   }
 
@@ -319,7 +317,7 @@ public class FetchHandlerSuiteJ {
     Mockito.doReturn(partitionFilesSorter).when(worker).partitionsSorter();
     fetchHandler0.init(worker);
     FetchHandler fetchHandler = spy(fetchHandler0);
-    Mockito.doReturn(fileInfo).when(fetchHandler).getRawDiskFileInfo(anyString(), anyString());
+    Mockito.doReturn(fileInfo).when(fetchHandler).getRawFileInfo(anyString(), anyString());
     return fetchHandler;
   }
 
@@ -443,7 +441,7 @@ public class FetchHandlerSuiteJ {
   }
 
   private void checkOriginFileBeDeleted(FileInfo fileInfo) {
-    assertTrue(!((DiskFileInfo) fileInfo).getFilePath().endsWith(".sorted"));
+    assertFalse(((DiskFileInfo) fileInfo).getFilePath().endsWith(".sorted"));
     long startTs = System.currentTimeMillis();
     boolean deleted = false;
     long timeout = 5 * 1000; // 5s
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManagerSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManagerSuiteJ.java
index 515b176d5..5a055b7b4 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManagerSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ChunkStreamManagerSuiteJ.java
@@ -24,7 +24,7 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
 
-import org.apache.celeborn.common.meta.FileManagedBuffers;
+import org.apache.celeborn.common.network.buffer.FileChunkBuffers;
 
 public class ChunkStreamManagerSuiteJ {
   @Test
@@ -32,12 +32,12 @@ public class ChunkStreamManagerSuiteJ {
     ChunkStreamManager manager = new ChunkStreamManager();
 
     @SuppressWarnings("unchecked")
-    FileManagedBuffers buffers = Mockito.mock(FileManagedBuffers.class);
+    FileChunkBuffers buffers = Mockito.mock(FileChunkBuffers.class);
 
     @SuppressWarnings("unchecked")
-    FileManagedBuffers buffers2 = Mockito.mock(FileManagedBuffers.class);
-    FileManagedBuffers buffers3 = Mockito.mock(FileManagedBuffers.class);
-    FileManagedBuffers buffers4 = Mockito.mock(FileManagedBuffers.class);
+    FileChunkBuffers buffers2 = Mockito.mock(FileChunkBuffers.class);
+    FileChunkBuffers buffers3 = Mockito.mock(FileChunkBuffers.class);
+    FileChunkBuffers buffers4 = Mockito.mock(FileChunkBuffers.class);
 
     manager.registerStream("shuffleKey1", buffers, "shuffleFile1", null);
     manager.registerStream("shuffleKey1", buffers2, "shuffleFile1", null);
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/CreditStreamManagerSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/CreditStreamManagerSuiteJ.java
index 0896f2462..95064b90e 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/CreditStreamManagerSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/CreditStreamManagerSuiteJ.java
@@ -46,16 +46,16 @@ public class CreditStreamManagerSuiteJ {
   private static final Logger LOG = LoggerFactory.getLogger(CreditStreamManagerSuiteJ.class);
   private static File tempDir =
       Utils.createTempDir(System.getProperty("java.io.tmpdir"), "celeborn");
+  private static CelebornConf conf = new CelebornConf();
 
   @BeforeClass
   public static void beforeAll() {
-    CelebornConf conf = new CelebornConf();
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_RECEIVE().key(), "0.8");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_REPLICATE().key(), "0.9");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_RESUME().key(), "0.5");
     conf.set(CelebornConf.WORKER_PARTITION_SORTER_DIRECT_MEMORY_RATIO_THRESHOLD().key(), "0.6");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_READ_BUFFER().key(), "0.1");
-    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE().key(), "0.1");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_CHECK_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_REPORT_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_READBUFFER_ALLOCATIONWAIT().key(), "10ms");
@@ -77,7 +77,7 @@ public class CreditStreamManagerSuiteJ {
     Channel channel = Mockito.mock(Channel.class);
     DiskFileInfo diskFileInfo =
         new DiskFileInfo(
-            createTemporaryFileWithIndexFile(), new UserIdentifier("default", "default"));
+            createTemporaryFileWithIndexFile(), new UserIdentifier("default", "default"), conf);
     MapFileMeta mapFileMeta = new MapFileMeta(1024, 10);
     diskFileInfo.replaceFileMeta(mapFileMeta);
     Consumer<Long> streamIdConsumer = streamId -> Assert.assertTrue(streamId > 0);
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterSuiteUtils.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterSuiteUtils.java
index e5926ad10..44baa691e 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterSuiteUtils.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionDataWriterSuiteUtils.java
@@ -20,15 +20,25 @@ package org.apache.celeborn.service.deploy.worker.storage;
 import java.io.File;
 import java.io.IOException;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
 
-import scala.Tuple3;
+import scala.Function0;
 import scala.Tuple4;
 
+import org.apache.hadoop.shaded.com.google.common.io.Files;
 import org.mockito.Mockito;
 
+import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.identity.UserIdentifier;
 import org.apache.celeborn.common.meta.DiskFileInfo;
 import org.apache.celeborn.common.meta.MapFileMeta;
+import org.apache.celeborn.common.meta.MemoryFileInfo;
+import org.apache.celeborn.common.meta.ReduceFileMeta;
+import org.apache.celeborn.common.metrics.source.AbstractSource;
+import org.apache.celeborn.common.network.util.NettyUtils;
+import org.apache.celeborn.common.network.util.TransportConf;
+import org.apache.celeborn.common.protocol.StorageInfo;
+import org.apache.celeborn.service.deploy.worker.WorkerSource;
 
 public class PartitionDataWriterSuiteUtils {
   public static File getTemporaryFile(File tempDir) throws IOException {
@@ -38,25 +48,100 @@ public class PartitionDataWriterSuiteUtils {
     return temporaryFile;
   }
 
-  public static Tuple4<StorageManager, Flusher, DiskFileInfo, File> prepareTestFileContext(
-      File tempDir, UserIdentifier userIdentifier, Flusher flusher, boolean reduceMeta)
+  public static StorageManager prepareDiskFileTestEnvironment(
+      File tempDir,
+      UserIdentifier userIdentifier,
+      Flusher flusher,
+      boolean reduceMeta,
+      CelebornConf conf)
       throws IOException {
     File file = getTemporaryFile(tempDir);
-    DiskFileInfo fileInfo = new DiskFileInfo(file, userIdentifier);
+    DiskFileInfo fileInfo = new DiskFileInfo(file, userIdentifier, conf);
     if (!reduceMeta) {
       fileInfo.replaceFileMeta(new MapFileMeta(32 * 1024, 10));
     }
     StorageManager storageManager = Mockito.mock(StorageManager.class);
-    Mockito.doAnswer(invocation -> new Tuple3<Flusher, DiskFileInfo, File>(flusher, fileInfo, null))
+    Mockito.doAnswer(
+            invocation ->
+                new Tuple4<MemoryFileInfo, Flusher, DiskFileInfo, File>(
+                    null, flusher, fileInfo, file))
         .when(storageManager)
-        .createFile(
-            Mockito.any(),
-            Mockito.any(),
-            Mockito.anyInt(),
-            Mockito.any(),
-            Mockito.any(),
-            Mockito.any(),
-            Mockito.anyBoolean());
-    return new Tuple4<>(storageManager, flusher, fileInfo, null);
+        .createFile(Mockito.any(), Mockito.anyBoolean());
+    return storageManager;
+  }
+
+  public static StorageManager prepareMemoryFileTestEnvironment(
+      UserIdentifier userIdentifier,
+      boolean reduceMeta,
+      StorageManager storageManager,
+      CelebornConf celebornConf) {
+    ReduceFileMeta reduceFileMeta = new ReduceFileMeta(celebornConf.shuffleChunkSize());
+    MemoryFileInfo memoryFileInfo = new MemoryFileInfo(userIdentifier, false, reduceFileMeta);
+    if (!reduceMeta) {
+      memoryFileInfo.replaceFileMeta(new MapFileMeta(32 * 1024, 10));
+    }
+
+    Mockito.doAnswer(
+            invocation ->
+                new Tuple4<MemoryFileInfo, Flusher, DiskFileInfo, File>(
+                    memoryFileInfo, null, null, null))
+        .when(storageManager)
+        .createFile(Mockito.any(), Mockito.anyBoolean());
+    return storageManager;
+  }
+
+  public static StorageManager prepareMemoryEvictEnvironment(
+      UserIdentifier userIdentifier,
+      boolean reduceMeta,
+      StorageManager storageManager,
+      CelebornConf celebornConf)
+      throws IOException {
+    ReduceFileMeta reduceFileMeta = new ReduceFileMeta(celebornConf.shuffleChunkSize());
+    MemoryFileInfo memoryFileInfo = new MemoryFileInfo(userIdentifier, false, reduceFileMeta);
+    if (!reduceMeta) {
+      memoryFileInfo.replaceFileMeta(new MapFileMeta(32 * 1024, 10));
+    }
+
+    File tempDir = Files.createTempDir();
+    tempDir.deleteOnExit();
+    File file = getTemporaryFile(tempDir);
+    DiskFileInfo fileInfo = new DiskFileInfo(file, userIdentifier, celebornConf);
+    if (!reduceMeta) {
+      fileInfo.replaceFileMeta(new MapFileMeta(32 * 1024, 10));
+    }
+
+    AbstractSource source = Mockito.mock(WorkerSource.class);
+    Mockito.doAnswer(
+            invocationOnMock -> {
+              Function0<?> function = (Function0<?>) invocationOnMock.getArguments()[2];
+              return function.apply();
+            })
+        .when(source)
+        .sample(Mockito.anyString(), Mockito.anyString(), Mockito.any(Function0.class));
+    AtomicInteger callCounter = new AtomicInteger(0);
+    LocalFlusher flusher =
+        new LocalFlusher(
+            source,
+            DeviceMonitor$.MODULE$.EmptyMonitor(),
+            1,
+            NettyUtils.getPooledByteBufAllocator(
+                new TransportConf("test", celebornConf), null, true),
+            256,
+            "disk1",
+            StorageInfo.Type.HDD,
+            null);
+    Mockito.doAnswer(
+            invocation -> {
+              if (callCounter.getAndIncrement() == 0) {
+                return new Tuple4<MemoryFileInfo, Flusher, DiskFileInfo, File>(
+                    memoryFileInfo, null, null, null);
+              } else {
+                return new Tuple4<MemoryFileInfo, Flusher, DiskFileInfo, File>(
+                    null, flusher, fileInfo, file);
+              }
+            })
+        .when(storageManager)
+        .createFile(Mockito.any(), Mockito.anyBoolean());
+    return storageManager;
   }
 }
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/SSLReducePartitionDataWriterSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/SSLReducePartitionDataWriterSuiteJ.java
index e6e37ed51..4fa2d54ee 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/SSLReducePartitionDataWriterSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/SSLReducePartitionDataWriterSuiteJ.java
@@ -24,8 +24,9 @@ import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.network.TestHelper;
 import org.apache.celeborn.common.network.ssl.SslSampleConfigs;
 import org.apache.celeborn.common.network.util.TransportConf;
+import org.apache.celeborn.service.deploy.worker.storage.local.DiskReducePartitionDataWriterSuiteJ;
 
-public class SSLReducePartitionDataWriterSuiteJ extends ReducePartitionDataWriterSuiteJ {
+public class SSLReducePartitionDataWriterSuiteJ extends DiskReducePartitionDataWriterSuiteJ {
   protected TransportConf createModuleTransportConf(String module) {
     CelebornConf conf =
         TestHelper.updateCelebornConfWithMap(
@@ -35,11 +36,11 @@ public class SSLReducePartitionDataWriterSuiteJ extends ReducePartitionDataWrite
 
   @BeforeClass
   public static void beforeAll() {
-    ReducePartitionDataWriterSuiteJ.beforeAll();
+    DiskReducePartitionDataWriterSuiteJ.beforeAll();
   }
 
   @AfterClass
   public static void afterAll() {
-    ReducePartitionDataWriterSuiteJ.afterAll();
+    DiskReducePartitionDataWriterSuiteJ.afterAll();
   }
 }
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriterSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskMapPartitionDataWriterSuiteJ.java
similarity index 85%
rename from worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriterSuiteJ.java
rename to worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskMapPartitionDataWriterSuiteJ.java
index bc7772001..f3fb4030d 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionDataWriterSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskMapPartitionDataWriterSuiteJ.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.service.deploy.worker.storage;
+package org.apache.celeborn.service.deploy.worker.storage.local;
 
 import static org.junit.Assert.assertEquals;
 
@@ -24,12 +24,10 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
-import java.util.UUID;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicLong;
 
 import scala.Function0;
-import scala.Tuple4;
 import scala.collection.mutable.ListBuffer;
 
 import io.netty.buffer.Unpooled;
@@ -42,19 +40,21 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.identity.UserIdentifier;
-import org.apache.celeborn.common.meta.DiskFileInfo;
 import org.apache.celeborn.common.network.util.NettyUtils;
 import org.apache.celeborn.common.network.util.TransportConf;
+import org.apache.celeborn.common.protocol.PartitionLocation;
 import org.apache.celeborn.common.protocol.PartitionSplitMode;
+import org.apache.celeborn.common.protocol.PartitionType;
 import org.apache.celeborn.common.protocol.StorageInfo;
 import org.apache.celeborn.common.util.JavaUtils;
 import org.apache.celeborn.common.util.Utils;
 import org.apache.celeborn.service.deploy.worker.WorkerSource;
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager;
+import org.apache.celeborn.service.deploy.worker.storage.*;
 
-public class MapPartitionDataWriterSuiteJ {
+public class DiskMapPartitionDataWriterSuiteJ {
 
-  private static final Logger LOG = LoggerFactory.getLogger(MapPartitionDataWriterSuiteJ.class);
+  private static final Logger LOG = LoggerFactory.getLogger(DiskMapPartitionDataWriterSuiteJ.class);
 
   private static final CelebornConf CONF = new CelebornConf();
   public static final Long SPLIT_THRESHOLD = 256 * 1024 * 1024L;
@@ -100,7 +100,7 @@ public class MapPartitionDataWriterSuiteJ {
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_RESUME().key(), "0.5");
     conf.set(CelebornConf.WORKER_PARTITION_SORTER_DIRECT_MEMORY_RATIO_THRESHOLD().key(), "0.6");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_READ_BUFFER().key(), "0.1");
-    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE().key(), "0.1");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_CHECK_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_REPORT_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_READBUFFER_ALLOCATIONWAIT().key(), "10ms");
@@ -121,21 +121,24 @@ public class MapPartitionDataWriterSuiteJ {
 
   @Test
   public void testMultiThreadWrite() throws IOException {
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, false);
+    PartitionLocation partitionLocation = Mockito.mock(PartitionLocation.class);
     MapPartitionDataWriter fileWriter =
         new MapPartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, false, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                partitionLocation,
+                "app1",
+                1,
+                userIdentifier,
+                PartitionType.MAP,
+                false));
     fileWriter.pushDataHandShake(2, 32 * 1024);
     fileWriter.regionStart(0, false);
     byte[] partData0 = generateData(0);
@@ -157,13 +160,6 @@ public class MapPartitionDataWriterSuiteJ {
     assertEquals(fileWriter.getFile().length(), bytesWritten);
   }
 
-  private File getTemporaryFile() throws IOException {
-    String filename = UUID.randomUUID().toString();
-    File temporaryFile = new File(tempDir, filename);
-    temporaryFile.createNewFile();
-    return temporaryFile;
-  }
-
   private byte[] generateData(int partitionId) {
     ThreadLocalRandom rand = ThreadLocalRandom.current();
     byte[] hello = "hello, world".getBytes(StandardCharsets.UTF_8);
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorterSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskPartitionFilesSorterSuiteJ.java
similarity index 89%
rename from worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorterSuiteJ.java
rename to worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskPartitionFilesSorterSuiteJ.java
index cce7e1f29..d8546ed49 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/PartitionFilesSorterSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskPartitionFilesSorterSuiteJ.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.service.deploy.worker.storage;
+package org.apache.celeborn.service.deploy.worker.storage.local;
 
 import static org.mockito.Mockito.when;
 
@@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory;
 import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.identity.UserIdentifier;
 import org.apache.celeborn.common.meta.DiskFileInfo;
+import org.apache.celeborn.common.meta.FileInfo;
 import org.apache.celeborn.common.meta.ReduceFileMeta;
 import org.apache.celeborn.common.unsafe.Platform;
 import org.apache.celeborn.common.util.CelebornExitKind;
@@ -44,18 +45,20 @@ import org.apache.celeborn.common.util.JavaUtils;
 import org.apache.celeborn.common.util.Utils;
 import org.apache.celeborn.service.deploy.worker.WorkerSource;
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager;
+import org.apache.celeborn.service.deploy.worker.storage.PartitionDataWriter;
+import org.apache.celeborn.service.deploy.worker.storage.PartitionFilesSorter;
 
-public class PartitionFilesSorterSuiteJ {
+public class DiskPartitionFilesSorterSuiteJ {
 
-  private static Logger logger = LoggerFactory.getLogger(PartitionFilesSorterSuiteJ.class);
+  private static final Logger logger =
+      LoggerFactory.getLogger(DiskPartitionFilesSorterSuiteJ.class);
 
-  private Random random = new Random();
+  private final Random random = new Random();
   private File shuffleFile;
   private DiskFileInfo fileInfo;
   private String originFileName;
-  private long originFileLen;
   private PartitionDataWriter partitionDataWriter;
-  private UserIdentifier userIdentifier = new UserIdentifier("mock-tenantId", "mock-name");
+  private final UserIdentifier userIdentifier = new UserIdentifier("mock-tenantId", "mock-name");
 
   private static final int MAX_MAP_ID = 50;
 
@@ -65,7 +68,7 @@ public class PartitionFilesSorterSuiteJ {
     shuffleFile = File.createTempFile("Celeborn", "sort-suite");
 
     originFileName = shuffleFile.getAbsolutePath();
-    fileInfo = new DiskFileInfo(shuffleFile, userIdentifier);
+    fileInfo = new DiskFileInfo(shuffleFile, userIdentifier, new CelebornConf());
     FileOutputStream fileOutputStream = new FileOutputStream(shuffleFile);
     FileChannel channel = fileOutputStream.getChannel();
     Map<Integer, Integer> batchIds = new HashMap<>();
@@ -102,8 +105,8 @@ public class PartitionFilesSorterSuiteJ {
       }
       partitionSize[mapId] = partitionSize[mapId] + batchHeader.length + mockedData.length;
     }
-    originFileLen = channel.size();
-    ((ReduceFileMeta) fileInfo.getFileMeta()).getChunkOffsets().add(originFileLen);
+    long originFileLen = channel.size();
+    fileInfo.getReduceFileMeta().getChunkOffsets().add(originFileLen);
     fileInfo.updateBytesFlushed(originFileLen);
     logger.info(shuffleFile.getAbsolutePath() + " filelen: " + Utils.bytesToString(originFileLen));
 
@@ -113,7 +116,7 @@ public class PartitionFilesSorterSuiteJ {
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_RESUME().key(), "0.5");
     conf.set(CelebornConf.WORKER_PARTITION_SORTER_DIRECT_MEMORY_RATIO_THRESHOLD().key(), "0.6");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_READ_BUFFER().key(), "0.1");
-    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE().key(), "0.1");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_CHECK_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_REPORT_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_READBUFFER_ALLOCATIONWAIT().key(), "10ms");
@@ -141,7 +144,7 @@ public class PartitionFilesSorterSuiteJ {
       conf.set(CelebornConf.SHUFFLE_CHUNK_SIZE().key(), "8m");
       PartitionFilesSorter partitionFilesSorter =
           new PartitionFilesSorter(MemoryManager.instance(), conf, new WorkerSource(conf));
-      DiskFileInfo info =
+      FileInfo info =
           partitionFilesSorter.getSortedFileInfo(
               "application-1",
               originFileName,
@@ -159,7 +162,7 @@ public class PartitionFilesSorterSuiteJ {
       long actualTotalChunkSize =
           ((ReduceFileMeta) info.getFileMeta()).getLastChunkOffset()
               - ((ReduceFileMeta) info.getFileMeta()).getChunkOffsets().get(0);
-      Assert.assertTrue(totalSizeToFetch == actualTotalChunkSize);
+      Assert.assertEquals(totalSizeToFetch, actualTotalChunkSize);
     } finally {
       clean();
     }
@@ -207,7 +210,7 @@ public class PartitionFilesSorterSuiteJ {
     Assert.assertEquals(
         partitionFilesSorter2.getSortedShuffleFiles("application-1-1").toString(),
         "[0-0-3, 0-0-2, 0-0-1]");
-    Assert.assertEquals(partitionFilesSorter2.getSortedShuffleFiles("application-2-1"), null);
+    Assert.assertNull(partitionFilesSorter2.getSortedShuffleFiles("application-2-1"));
     Assert.assertEquals(
         partitionFilesSorter2.getSortedShuffleFiles("application-3-1").toString(), "[0-0-1]");
     partitionFilesSorter2.close(CelebornExitKind.WORKER_GRACEFUL_SHUTDOWN());
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriterSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskReducePartitionDataWriterSuiteJ.java
similarity index 72%
copy from worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriterSuiteJ.java
copy to worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskReducePartitionDataWriterSuiteJ.java
index 6417e2aa0..748cd9d83 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriterSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/local/DiskReducePartitionDataWriterSuiteJ.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.service.deploy.worker.storage;
+package org.apache.celeborn.service.deploy.worker.storage.local;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -27,13 +27,7 @@ import java.io.IOException;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -43,7 +37,6 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
 import scala.Function0;
-import scala.Tuple4;
 import scala.collection.mutable.ListBuffer;
 
 import io.netty.buffer.ByteBuf;
@@ -57,9 +50,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
-import org.apache.celeborn.common.exception.CelebornException;
 import org.apache.celeborn.common.identity.UserIdentifier;
 import org.apache.celeborn.common.meta.DiskFileInfo;
+import org.apache.celeborn.common.meta.FileInfo;
 import org.apache.celeborn.common.meta.ReduceFileMeta;
 import org.apache.celeborn.common.network.TransportContext;
 import org.apache.celeborn.common.network.buffer.ManagedBuffer;
@@ -77,10 +70,12 @@ import org.apache.celeborn.common.util.Utils;
 import org.apache.celeborn.service.deploy.worker.FetchHandler;
 import org.apache.celeborn.service.deploy.worker.WorkerSource;
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager;
+import org.apache.celeborn.service.deploy.worker.storage.*;
 
-public class ReducePartitionDataWriterSuiteJ {
+public class DiskReducePartitionDataWriterSuiteJ {
 
-  private static final Logger LOG = LoggerFactory.getLogger(ReducePartitionDataWriterSuiteJ.class);
+  private static final Logger LOG =
+      LoggerFactory.getLogger(DiskReducePartitionDataWriterSuiteJ.class);
 
   private static final CelebornConf CONF = new CelebornConf();
   public static final Long SPLIT_THRESHOLD = 256 * 1024 * 1024L;
@@ -130,7 +125,7 @@ public class ReducePartitionDataWriterSuiteJ {
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_RESUME().key(), "0.5");
     conf.set(CelebornConf.WORKER_PARTITION_SORTER_DIRECT_MEMORY_RATIO_THRESHOLD().key(), "0.6");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_READ_BUFFER().key(), "0.1");
-    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE().key(), "0.1");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_CHECK_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_REPORT_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_READBUFFER_ALLOCATIONWAIT().key(), "10ms");
@@ -152,7 +147,7 @@ public class ReducePartitionDataWriterSuiteJ {
           }
 
           @Override
-          public DiskFileInfo getRawDiskFileInfo(String shuffleKey, String fileName) {
+          public FileInfo getRawFileInfo(String shuffleKey, String fileName) {
             return info;
           }
 
@@ -222,7 +217,7 @@ public class ReducePartitionDataWriterSuiteJ {
     return message.toByteBuffer();
   }
 
-  private void setUpConn(TransportClient client) throws IOException, CelebornException {
+  private void setUpConn(TransportClient client) throws IOException {
     ByteBuffer resp = client.sendRpcSync(createOpenMessage(), 10000);
     PbStreamHandler streamHandler = TransportMessage.fromByteBuffer(resp).getParsedPayload();
     streamId = streamHandler.getStreamId();
@@ -269,22 +264,25 @@ public class ReducePartitionDataWriterSuiteJ {
   @Test
   public void testMultiThreadWrite() throws IOException, ExecutionException, InterruptedException {
     final int threadsNum = 8;
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
 
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-1");
@@ -318,22 +316,24 @@ public class ReducePartitionDataWriterSuiteJ {
   public void testMultiThreadWriteDuringClose()
       throws IOException, ExecutionException, InterruptedException {
     final int threadsNum = 8;
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-1");
@@ -361,29 +361,31 @@ public class ReducePartitionDataWriterSuiteJ {
 
     assertEquals(length.get(), bytesWritten.get());
     assertEquals(partitionDataWriter.getFile().length(), bytesWritten.get());
-    assertEquals(context._3().getFileLength(), bytesWritten.get());
+    assertEquals(partitionDataWriter.getDiskFileInfo().getFileLength(), bytesWritten.get());
   }
 
   @Test
   public void testAfterStressfulWriteWillReadCorrect()
       throws IOException, ExecutionException, InterruptedException {
     final int threadsNum = Runtime.getRuntime().availableProcessors();
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-2");
@@ -432,23 +434,24 @@ public class ReducePartitionDataWriterSuiteJ {
   @Test
   public void testWriteAndChunkRead() throws Exception {
     final int threadsNum = 16;
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-    DiskFileInfo fileInfo = context._3();
-
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-2");
@@ -477,7 +480,7 @@ public class ReducePartitionDataWriterSuiteJ {
     long bytesWritten = partitionDataWriter.close();
     assertEquals(length.get(), bytesWritten);
 
-    setupChunkServer(fileInfo);
+    setupChunkServer(partitionDataWriter.getDiskFileInfo());
 
     TransportClient client =
         clientFactory.createClient(InetAddress.getLocalHost().getHostAddress(), server.getPort());
@@ -547,24 +550,28 @@ public class ReducePartitionDataWriterSuiteJ {
     conf.set(CelebornConf.WORKER_FLUSHER_BUFFER_SIZE().key(), "128B");
 
     // case 1: write 8MiB
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     partitionDataWriter.write(generateData(8 * 1024 * 1024));
     partitionDataWriter.close();
-    ReduceFileMeta reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    ReduceFileMeta reduceFileMeta =
+        (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 1);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 8 * 1024 * 1024);
     assertEquals(
@@ -572,224 +579,242 @@ public class ReducePartitionDataWriterSuiteJ {
         8 * 1024 * 1024);
 
     // case 2: write 1024B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 8; i++) {
       partitionDataWriter.write(generateData(128));
     }
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 1);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 1024);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 3: write 1023B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     partitionDataWriter.write(generateData(1020));
     partitionDataWriter.write(generateData(3));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 1);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 1023);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1023);
 
     // case 4: write 1025B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 8; i++) {
       partitionDataWriter.write(generateData(128));
     }
     partitionDataWriter.write(generateData(1));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 2);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 1025);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 5: write 2048B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 16; i++) {
       partitionDataWriter.write(generateData(128));
     }
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 2);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 2048);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 5.1: write 2048B with trim; without PR #1702 this case will fail
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 16; i++) {
       partitionDataWriter.write(generateData(128));
     }
     // mock trim
-    partitionDataWriter.flush(false);
+    partitionDataWriter.flush(false, false);
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 2);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 2048);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 6: write 2049B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 16; i++) {
       partitionDataWriter.write(generateData(128));
     }
     partitionDataWriter.write(generateData(1));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 3);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 2049);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(2) - reduceFileMeta.getChunkOffsets().get(1), 1024);
 
     // case 7: write 4097B with 3 chunks
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     partitionDataWriter.write(generateData(1024));
     for (int i = 0; i < 9; i++) {
       partitionDataWriter.write(generateData(128));
     }
     partitionDataWriter.write(generateData(1920));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 3);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 4096);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(3) - reduceFileMeta.getChunkOffsets().get(2), 2048);
 
     // case 7.2: write 4097B with 3 chunks with trim; without PR #1702 this case will fail
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareDiskFileTestEnvironment(
+                tempDir, userIdentifier, localFlusher, true, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     partitionDataWriter.write(generateData(1024));
     for (int i = 0; i < 9; i++) {
       partitionDataWriter.write(generateData(128));
-      partitionDataWriter.flush(false);
+      partitionDataWriter.flush(false, false);
     }
     partitionDataWriter.write(generateData(1920));
     // mock trim
-    partitionDataWriter.flush(false);
+    partitionDataWriter.flush(false, false);
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getDiskFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 3);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 4096);
     assertEquals(
@@ -818,9 +843,7 @@ public class ReducePartitionDataWriterSuiteJ {
 
   private ByteBuf generateData(int len) {
     byte[] data = new byte[len];
-    for (int i = 0; i < len; i++) {
-      data[i] = 'a';
-    }
+    Arrays.fill(data, (byte) 'a');
     return Unpooled.wrappedBuffer(data);
   }
 }
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/memory/MemoryPartitionFilesSorterSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/memory/MemoryPartitionFilesSorterSuiteJ.java
new file mode 100644
index 000000000..1d39839b0
--- /dev/null
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/memory/MemoryPartitionFilesSorterSuiteJ.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.service.deploy.worker.storage.memory;
+
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import org.apache.celeborn.common.CelebornConf;
+import org.apache.celeborn.common.identity.UserIdentifier;
+import org.apache.celeborn.common.meta.FileInfo;
+import org.apache.celeborn.common.meta.MemoryFileInfo;
+import org.apache.celeborn.common.meta.ReduceFileMeta;
+import org.apache.celeborn.common.metrics.source.AbstractSource;
+import org.apache.celeborn.common.network.util.NettyUtils;
+import org.apache.celeborn.common.unsafe.Platform;
+import org.apache.celeborn.service.deploy.worker.WorkerSource;
+import org.apache.celeborn.service.deploy.worker.memory.MemoryManager;
+import org.apache.celeborn.service.deploy.worker.storage.PartitionDataWriter;
+import org.apache.celeborn.service.deploy.worker.storage.PartitionFilesSorter;
+import org.apache.celeborn.service.deploy.worker.storage.StorageManager;
+
+public class MemoryPartitionFilesSorterSuiteJ {
+
+  private final Random random = new Random();
+  private MemoryFileInfo fileInfo;
+  private PartitionDataWriter partitionDataWriter;
+  private final UserIdentifier userIdentifier = new UserIdentifier("mock-tenantId", "mock-name");
+
+  private static final int MAX_MAP_ID = 50;
+
+  public long[] prepare(int mapCount) {
+    long[] partitionSize = new long[MAX_MAP_ID];
+    byte[] batchHeader = new byte[16];
+    fileInfo = new MemoryFileInfo(userIdentifier, true, new ReduceFileMeta(8 * 1024 * 1024));
+
+    AbstractSource source = Mockito.mock(AbstractSource.class);
+    PooledByteBufAllocator allocator =
+        NettyUtils.getSharedPooledByteBufAllocator(new CelebornConf(), source, false);
+    CompositeByteBuf buffer = allocator.compositeBuffer();
+    fileInfo.setBuffer(buffer);
+
+    Map<Integer, Integer> batchIds = new HashMap<>();
+
+    for (int i = 0; i < mapCount; i++) {
+      int mapId = random.nextInt(MAX_MAP_ID);
+      int currentAttemptId = 0;
+      int batchId =
+          batchIds.compute(
+              mapId,
+              (k, v) -> {
+                if (v == null) {
+                  v = 0;
+                } else {
+                  v++;
+                }
+                return v;
+              });
+      // [63.9k, 192k + 63.9k]
+      int dataSize = random.nextInt(192 * 1024) + 65525;
+      byte[] mockedData = new byte[dataSize];
+      Platform.putInt(batchHeader, Platform.BYTE_ARRAY_OFFSET, mapId);
+      Platform.putInt(batchHeader, Platform.BYTE_ARRAY_OFFSET + 4, currentAttemptId);
+      Platform.putInt(batchHeader, Platform.BYTE_ARRAY_OFFSET + 8, batchId);
+      Platform.putInt(batchHeader, Platform.BYTE_ARRAY_OFFSET + 12, dataSize);
+      ByteBuffer buf1 = ByteBuffer.wrap(batchHeader);
+      while (buf1.hasRemaining()) {
+        buffer.writeBytes(buf1);
+      }
+      random.nextBytes(mockedData);
+      ByteBuffer buf2 = ByteBuffer.wrap(mockedData);
+      while (buf2.hasRemaining()) {
+        buffer.writeBytes(buf2);
+      }
+      partitionSize[mapId] = partitionSize[mapId] + batchHeader.length + mockedData.length;
+    }
+    long originFileLen = buffer.readableBytes();
+    fileInfo.getReduceFileMeta().addChunkOffset(originFileLen);
+    fileInfo.updateBytesFlushed((int) originFileLen);
+
+    CelebornConf conf = new CelebornConf();
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_RECEIVE().key(), "0.8");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_REPLICATE().key(), "0.9");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_RESUME().key(), "0.5");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_READ_BUFFER().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_CHECK_INTERVAL().key(), "10");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_REPORT_INTERVAL().key(), "10");
+    conf.set(CelebornConf.WORKER_READBUFFER_ALLOCATIONWAIT().key(), "10ms");
+
+    StorageManager storageManager = Mockito.mock(StorageManager.class);
+    Mockito.when(storageManager.storageBufferAllocator()).thenAnswer(a -> allocator);
+    MemoryManager.initialize(conf, storageManager);
+    partitionDataWriter = Mockito.mock(PartitionDataWriter.class);
+    when(partitionDataWriter.getMemoryFileInfo()).thenAnswer(i -> fileInfo);
+
+    return partitionSize;
+  }
+
+  private void check(int mapCount, int startMapIndex, int endMapIndex) throws IOException {
+    long[] partitionSize = prepare(mapCount);
+    CelebornConf conf = new CelebornConf();
+    conf.set(CelebornConf.SHUFFLE_CHUNK_SIZE().key(), "8m");
+    PartitionFilesSorter partitionFilesSorter =
+        new PartitionFilesSorter(MemoryManager.instance(), conf, new WorkerSource(conf));
+    FileInfo info =
+        partitionFilesSorter.getSortedFileInfo(
+            "application-1",
+            "",
+            partitionDataWriter.getMemoryFileInfo(),
+            startMapIndex,
+            endMapIndex);
+    long totalSizeToFetch = 0;
+    for (int i = startMapIndex; i < endMapIndex; i++) {
+      totalSizeToFetch += partitionSize[i];
+    }
+    long numChunks = totalSizeToFetch / conf.shuffleChunkSize() + 1;
+    Assert.assertTrue(
+        0 < ((ReduceFileMeta) info.getFileMeta()).getNumChunks()
+            && ((ReduceFileMeta) info.getFileMeta()).getNumChunks() <= numChunks);
+    long actualTotalChunkSize =
+        ((ReduceFileMeta) info.getFileMeta()).getLastChunkOffset()
+            - ((ReduceFileMeta) info.getFileMeta()).getChunkOffsets().get(0);
+    Assert.assertEquals(totalSizeToFetch, actualTotalChunkSize);
+    fileInfo.getBuffer().release();
+  }
+
+  @Test
+  public void testSortMemoryShuffleFile() throws IOException {
+    int startMapIndex = random.nextInt(5);
+    int endMapIndex = startMapIndex + random.nextInt(5) + 5;
+    check(1000, startMapIndex, endMapIndex);
+  }
+}
diff --git a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriterSuiteJ.java b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/memory/MemoryReducePartitionDataWriterSuiteJ.java
similarity index 57%
rename from worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriterSuiteJ.java
rename to worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/memory/MemoryReducePartitionDataWriterSuiteJ.java
index 6417e2aa0..cf71200dc 100644
--- a/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionDataWriterSuiteJ.java
+++ b/worker/src/test/java/org/apache/celeborn/service/deploy/worker/storage/memory/MemoryReducePartitionDataWriterSuiteJ.java
@@ -15,41 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.service.deploy.worker.storage;
+package org.apache.celeborn.service.deploy.worker.storage.memory;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
+import java.util.*;
+import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicLong;
 
 import scala.Function0;
-import scala.Tuple4;
-import scala.collection.mutable.ListBuffer;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
 import io.netty.buffer.Unpooled;
-import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -57,9 +41,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.CelebornConf;
-import org.apache.celeborn.common.exception.CelebornException;
 import org.apache.celeborn.common.identity.UserIdentifier;
-import org.apache.celeborn.common.meta.DiskFileInfo;
+import org.apache.celeborn.common.meta.FileInfo;
 import org.apache.celeborn.common.meta.ReduceFileMeta;
 import org.apache.celeborn.common.network.TransportContext;
 import org.apache.celeborn.common.network.buffer.ManagedBuffer;
@@ -71,36 +54,37 @@ import org.apache.celeborn.common.network.server.TransportServer;
 import org.apache.celeborn.common.network.util.NettyUtils;
 import org.apache.celeborn.common.network.util.TransportConf;
 import org.apache.celeborn.common.protocol.*;
-import org.apache.celeborn.common.util.JavaUtils;
+import org.apache.celeborn.common.unsafe.Platform;
 import org.apache.celeborn.common.util.ThreadUtils;
-import org.apache.celeborn.common.util.Utils;
 import org.apache.celeborn.service.deploy.worker.FetchHandler;
 import org.apache.celeborn.service.deploy.worker.WorkerSource;
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager;
+import org.apache.celeborn.service.deploy.worker.storage.*;
 
-public class ReducePartitionDataWriterSuiteJ {
+public class MemoryReducePartitionDataWriterSuiteJ {
 
-  private static final Logger LOG = LoggerFactory.getLogger(ReducePartitionDataWriterSuiteJ.class);
+  private static final Logger LOG =
+      LoggerFactory.getLogger(MemoryReducePartitionDataWriterSuiteJ.class);
 
   private static final CelebornConf CONF = new CelebornConf();
   public static final Long SPLIT_THRESHOLD = 256 * 1024 * 1024L;
   public static final PartitionSplitMode splitMode = PartitionSplitMode.HARD;
 
-  private static File tempDir = null;
-  private static LocalFlusher localFlusher = null;
   private static WorkerSource source = null;
 
-  private TransportContext transportContext;
-  private TransportServer server;
-  private TransportClientFactory clientFactory;
+  private static TransportServer server;
+  private static TransportClientFactory clientFactory;
   private static long streamId;
   private static int numChunks;
   private final UserIdentifier userIdentifier = new UserIdentifier("mock-tenantId", "mock-name");
 
+  private static final TransportConf transConf = new TransportConf("shuffle", new CelebornConf());
+  private static StorageManager storageManager;
+
   @BeforeClass
   public static void beforeAll() {
-    tempDir = Utils.createTempDir(System.getProperty("java.io.tmpdir"), "celeborn");
     CONF.set(CelebornConf.SHUFFLE_CHUNK_SIZE().key(), "1k");
+    CONF.set(CelebornConf.ACTIVE_STORAGE_TYPES().key(), "MEMORY,HDD");
 
     source = Mockito.mock(WorkerSource.class);
     Mockito.doAnswer(
@@ -111,39 +95,27 @@ public class ReducePartitionDataWriterSuiteJ {
         .when(source)
         .sample(Mockito.anyString(), Mockito.anyString(), Mockito.any(Function0.class));
 
-    ListBuffer<File> dirs = new ListBuffer<>();
-    dirs.$plus$eq(tempDir);
-    localFlusher =
-        new LocalFlusher(
-            source,
-            DeviceMonitor$.MODULE$.EmptyMonitor(),
-            1,
-            NettyUtils.getPooledByteBufAllocator(new TransportConf("test", CONF), null, true),
-            256,
-            "disk1",
-            StorageInfo.Type.HDD,
-            null);
-
     CelebornConf conf = new CelebornConf();
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_RECEIVE().key(), "0.8");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_PAUSE_REPLICATE().key(), "0.9");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_RESUME().key(), "0.5");
     conf.set(CelebornConf.WORKER_PARTITION_SORTER_DIRECT_MEMORY_RATIO_THRESHOLD().key(), "0.6");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_READ_BUFFER().key(), "0.1");
-    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_SHUFFLE_STORAGE().key(), "0.1");
+    conf.set(CelebornConf.WORKER_DIRECT_MEMORY_RATIO_FOR_MEMORY_FILE_STORAGE().key(), "0.1");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_CHECK_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_DIRECT_MEMORY_REPORT_INTERVAL().key(), "10");
     conf.set(CelebornConf.WORKER_READBUFFER_ALLOCATIONWAIT().key(), "10ms");
-    MemoryManager.initialize(conf);
-  }
-
-  protected TransportConf createModuleTransportConf(String module) {
-    return new TransportConf(module, new CelebornConf());
+    PooledByteBufAllocator allocator =
+        NettyUtils.getPooledByteBufAllocator(transConf, source, false);
+    storageManager = Mockito.mock(StorageManager.class);
+    AtomicLong evictCount = new AtomicLong();
+    Mockito.when(storageManager.evictedFileCount()).thenAnswer(a -> evictCount);
+    Mockito.when(storageManager.localOrHdfsStorageAvailable()).thenAnswer(a -> true);
+    Mockito.when(storageManager.storageBufferAllocator()).thenAnswer(a -> allocator);
+    MemoryManager.initialize(conf, storageManager);
   }
 
-  public void setupChunkServer(DiskFileInfo info) throws IOException {
-    TransportConf transConf = createModuleTransportConf("shuffle");
-
+  public static void setupChunkServer(FileInfo info) throws IOException {
     FetchHandler handler =
         new FetchHandler(transConf.getCelebornConf(), transConf, mock(WorkerSource.class)) {
           @Override
@@ -152,7 +124,7 @@ public class ReducePartitionDataWriterSuiteJ {
           }
 
           @Override
-          public DiskFileInfo getRawDiskFileInfo(String shuffleKey, String fileName) {
+          public FileInfo getRawFileInfo(String shuffleKey, String fileName) {
             return info;
           }
 
@@ -171,28 +143,15 @@ public class ReducePartitionDataWriterSuiteJ {
         .when(sorter)
         .getSortedFileInfo(anyString(), anyString(), eq(info), anyInt(), anyInt());
     handler.setPartitionsSorter(sorter);
-    transportContext = new TransportContext(transConf, handler);
-    server = transportContext.createServer();
+    TransportContext context = new TransportContext(transConf, handler);
+    server = context.createServer();
 
-    clientFactory = transportContext.createClientFactory();
-  }
-
-  @AfterClass
-  public static void afterAll() {
-    if (tempDir != null) {
-      try {
-        JavaUtils.deleteRecursively(tempDir);
-        tempDir = null;
-      } catch (IOException e) {
-        LOG.error("Failed to delete temp dir.", e);
-      }
-    }
+    clientFactory = context.createClientFactory();
   }
 
-  public void closeChunkServer() {
+  public static void closeChunkServer() {
     server.close();
     clientFactory.close();
-    transportContext.close();
   }
 
   static class FetchResult {
@@ -212,7 +171,7 @@ public class ReducePartitionDataWriterSuiteJ {
         new TransportMessage(
             MessageType.OPEN_STREAM,
             PbOpenStream.newBuilder()
-                .setShuffleKey("shuffleKey-123")
+                .setShuffleKey("app-1")
                 .setFileName("location")
                 .setStartIndex(0)
                 .setEndIndex(Integer.MAX_VALUE)
@@ -222,7 +181,7 @@ public class ReducePartitionDataWriterSuiteJ {
     return message.toByteBuffer();
   }
 
-  private void setUpConn(TransportClient client) throws IOException, CelebornException {
+  private void setUpConn(TransportClient client) throws IOException {
     ByteBuffer resp = client.sendRpcSync(createOpenMessage(), 10000);
     PbStreamHandler streamHandler = TransportMessage.fromByteBuffer(resp).getParsedPayload();
     streamId = streamHandler.getStreamId();
@@ -251,6 +210,7 @@ public class ReducePartitionDataWriterSuiteJ {
           @Override
           public void onFailure(int chunkIndex, Throwable e) {
             res.failedChunks.add(chunkIndex);
+            LOG.error("Fetch chunk failed", e);
             sem.release();
           }
         };
@@ -269,22 +229,25 @@ public class ReducePartitionDataWriterSuiteJ {
   @Test
   public void testMultiThreadWrite() throws IOException, ExecutionException, InterruptedException {
     final int threadsNum = 8;
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
 
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-1");
@@ -294,7 +257,7 @@ public class ReducePartitionDataWriterSuiteJ {
       futures.add(
           es.submit(
               () -> {
-                byte[] bytes = generateData();
+                byte[] bytes = generateDataWithHeader();
                 length.addAndGet(bytes.length);
                 ByteBuf buf = Unpooled.wrappedBuffer(bytes);
                 try {
@@ -311,29 +274,31 @@ public class ReducePartitionDataWriterSuiteJ {
     long bytesWritten = partitionDataWriter.close();
 
     assertEquals(length.get(), bytesWritten);
-    assertEquals(partitionDataWriter.getFile().length(), bytesWritten);
+    assertEquals(partitionDataWriter.getMemoryFileInfo().getFileLength(), bytesWritten);
   }
 
   @Test
   public void testMultiThreadWriteDuringClose()
       throws IOException, ExecutionException, InterruptedException {
     final int threadsNum = 8;
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-1");
@@ -344,7 +309,7 @@ public class ReducePartitionDataWriterSuiteJ {
       futures.add(
           es.submit(
               () -> {
-                byte[] bytes = generateData();
+                byte[] bytes = generateDataWithHeader();
                 ByteBuf buf = Unpooled.wrappedBuffer(bytes);
                 try {
                   partitionDataWriter.write(buf);
@@ -360,30 +325,31 @@ public class ReducePartitionDataWriterSuiteJ {
     }
 
     assertEquals(length.get(), bytesWritten.get());
-    assertEquals(partitionDataWriter.getFile().length(), bytesWritten.get());
-    assertEquals(context._3().getFileLength(), bytesWritten.get());
+    assertEquals(partitionDataWriter.getMemoryFileInfo().getFileLength(), bytesWritten.get());
   }
 
   @Test
   public void testAfterStressfulWriteWillReadCorrect()
       throws IOException, ExecutionException, InterruptedException {
     final int threadsNum = Runtime.getRuntime().availableProcessors();
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryEvictEnvironment(
+                userIdentifier, true, storageManager, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-2");
@@ -393,7 +359,7 @@ public class ReducePartitionDataWriterSuiteJ {
           es.submit(
               () -> {
                 for (int j = 0; j < 100; ++j) {
-                  byte[] bytes = generateData();
+                  byte[] bytes = generateDataWithHeader();
                   length.addAndGet(bytes.length);
                   ByteBuf buf = Unpooled.wrappedBuffer(bytes);
                   try {
@@ -413,42 +379,101 @@ public class ReducePartitionDataWriterSuiteJ {
   }
 
   @Test
-  public void testHugeBufferQueueSize() throws IOException {
-    File file = getTemporaryFile();
-    ListBuffer<File> dirs = new ListBuffer<>();
-    dirs.$plus$eq(file);
-    localFlusher =
-        new LocalFlusher(
+  public void testWriteAndChunkRead() throws Exception {
+    final int threadsNum = 2;
+    PartitionDataWriter partitionDataWriter =
+        new ReducePartitionDataWriter(
+            PartitionDataWriterSuiteUtils.prepareMemoryEvictEnvironment(
+                userIdentifier, true, storageManager, CONF),
             source,
+            CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            1,
-            NettyUtils.getPooledByteBufAllocator(new TransportConf("test", CONF), null, true),
-            256,
-            "disk2",
-            StorageInfo.Type.HDD,
-            null);
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
+
+    List<Future<?>> futures = new ArrayList<>();
+    ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-2");
+    AtomicLong length = new AtomicLong(0);
+    futures.add(
+        es.submit(
+            () -> {
+              for (int j = 0; j < 10; ++j) {
+                byte[] bytes = generateDataWithHeader();
+                length.addAndGet(bytes.length);
+                ByteBuf buf = Unpooled.wrappedBuffer(bytes);
+                buf.retain();
+                try {
+                  partitionDataWriter.incrementPendingWrites();
+                  partitionDataWriter.write(buf);
+                } catch (IOException e) {
+                  LOG.error("Failed to write buffer.", e);
+                }
+                buf.release();
+              }
+            }));
+    for (Future<?> future : futures) {
+      future.get();
+    }
+
+    long bytesWritten = partitionDataWriter.close();
+    assertEquals(length.get(), bytesWritten);
+
+    setupChunkServer(partitionDataWriter.getMemoryFileInfo());
+
+    TransportClient client =
+        clientFactory.createClient(InetAddress.getLocalHost().getHostAddress(), server.getPort());
+
+    setUpConn(client);
+
+    int chunkNums = numChunks;
+    List<Integer> indices = new ArrayList<>();
+
+    for (int i = 0; i < chunkNums; i++) {
+      indices.add(i);
+    }
+
+    FetchResult result = fetchChunks(client, indices);
+
+    Thread.sleep(3000);
+
+    assertEquals(result.buffers.size(), chunkNums);
+    assertEquals(result.successChunks.size(), chunkNums);
+
+    result.releaseBuffers();
+
+    closeChunkServer();
   }
 
   @Test
-  public void testWriteAndChunkRead() throws Exception {
+  public void testEvictAndChunkRead() throws Exception {
     final int threadsNum = 16;
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> context =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-    DiskFileInfo fileInfo = context._3();
-
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            context._1(),
-            context._3(),
-            context._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryEvictEnvironment(
+                userIdentifier, true, storageManager, CONF),
             source,
             CONF,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
 
     List<Future<?>> futures = new ArrayList<>();
     ExecutorService es = ThreadUtils.newDaemonFixedThreadPool(threadsNum, "FileWriter-UT-2");
@@ -457,7 +482,7 @@ public class ReducePartitionDataWriterSuiteJ {
         es.submit(
             () -> {
               for (int j = 0; j < 1000; ++j) {
-                byte[] bytes = generateData();
+                byte[] bytes = generateDataWithHeader();
                 length.addAndGet(bytes.length);
                 ByteBuf buf = Unpooled.wrappedBuffer(bytes);
                 buf.retain();
@@ -477,7 +502,7 @@ public class ReducePartitionDataWriterSuiteJ {
     long bytesWritten = partitionDataWriter.close();
     assertEquals(length.get(), bytesWritten);
 
-    setupChunkServer(fileInfo);
+    setupChunkServer(partitionDataWriter.getDiskFileInfo());
 
     TransportClient client =
         clientFactory.createClient(InetAddress.getLocalHost().getHostAddress(), server.getPort());
@@ -547,24 +572,28 @@ public class ReducePartitionDataWriterSuiteJ {
     conf.set(CelebornConf.WORKER_FLUSHER_BUFFER_SIZE().key(), "128B");
 
     // case 1: write 8MiB
-    Tuple4<StorageManager, Flusher, DiskFileInfo, File> pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
     PartitionDataWriter partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
-    partitionDataWriter.write(generateData(8 * 1024 * 1024));
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
+    partitionDataWriter.write(generateDataWithHeader(8 * 1024 * 1024));
     partitionDataWriter.close();
-    ReduceFileMeta reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    ReduceFileMeta reduceFileMeta =
+        (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 1);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 8 * 1024 * 1024);
     assertEquals(
@@ -572,255 +601,270 @@ public class ReducePartitionDataWriterSuiteJ {
         8 * 1024 * 1024);
 
     // case 2: write 1024B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 8; i++) {
-      partitionDataWriter.write(generateData(128));
+      partitionDataWriter.write(generateDataWithHeader(128));
     }
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 1);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 1024);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 3: write 1023B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
-    partitionDataWriter.write(generateData(1020));
-    partitionDataWriter.write(generateData(3));
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
+    partitionDataWriter.write(generateDataWithHeader(1020));
+    partitionDataWriter.write(generateDataWithHeader(3));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 1);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 1023);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1023);
 
     // case 4: write 1025B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 8; i++) {
-      partitionDataWriter.write(generateData(128));
+      partitionDataWriter.write(generateDataWithHeader(128));
     }
-    partitionDataWriter.write(generateData(1));
+    partitionDataWriter.write(generateDataWithHeader(1));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 2);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 1025);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 5: write 2048B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 16; i++) {
-      partitionDataWriter.write(generateData(128));
+      partitionDataWriter.write(generateDataWithHeader(128));
     }
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 2);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 2048);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 5.1: write 2048B with trim; without PR #1702 this case will fail
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 16; i++) {
-      partitionDataWriter.write(generateData(128));
+      partitionDataWriter.write(generateDataWithHeader(128));
     }
     // mock trim
-    partitionDataWriter.flush(false);
+    partitionDataWriter.flush(false, false);
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 2);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 2048);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(1) - reduceFileMeta.getChunkOffsets().get(0), 1024);
 
     // case 6: write 2049B
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
     for (int i = 0; i < 16; i++) {
-      partitionDataWriter.write(generateData(128));
+      partitionDataWriter.write(generateDataWithHeader(128));
     }
-    partitionDataWriter.write(generateData(1));
+    partitionDataWriter.write(generateDataWithHeader(1));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 3);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 2049);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(2) - reduceFileMeta.getChunkOffsets().get(1), 1024);
 
     // case 7: write 4097B with 3 chunks
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
-    partitionDataWriter.write(generateData(1024));
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
+    partitionDataWriter.write(generateDataWithHeader(1024));
     for (int i = 0; i < 9; i++) {
-      partitionDataWriter.write(generateData(128));
+      partitionDataWriter.write(generateDataWithHeader(128));
     }
-    partitionDataWriter.write(generateData(1920));
+    partitionDataWriter.write(generateDataWithHeader(1920));
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 3);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 4096);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(3) - reduceFileMeta.getChunkOffsets().get(2), 2048);
 
     // case 7.2: write 4097B with 3 chunks with trim; without PR #1702 this case will fail
-    pctx =
-        PartitionDataWriterSuiteUtils.prepareTestFileContext(
-            tempDir, userIdentifier, localFlusher, true);
-
     partitionDataWriter =
         new ReducePartitionDataWriter(
-            pctx._1(),
-            pctx._3(),
-            pctx._2(),
+            PartitionDataWriterSuiteUtils.prepareMemoryFileTestEnvironment(
+                userIdentifier, true, storageManager, conf),
             source,
             conf,
             DeviceMonitor$.MODULE$.EmptyMonitor(),
-            SPLIT_THRESHOLD,
-            splitMode,
-            false,
-            "app1-1");
-    partitionDataWriter.write(generateData(1024));
+            new PartitionDataWriterContext(
+                SPLIT_THRESHOLD,
+                splitMode,
+                false,
+                new PartitionLocation(
+                    1, 0, "host", 1111, 1112, 1113, 1114, PartitionLocation.Mode.PRIMARY, null),
+                "app1-1",
+                1,
+                userIdentifier,
+                PartitionType.REDUCE,
+                false));
+    partitionDataWriter.write(generateDataWithHeader(1024));
     for (int i = 0; i < 9; i++) {
-      partitionDataWriter.write(generateData(128));
-      partitionDataWriter.flush(false);
+      partitionDataWriter.write(generateDataWithHeader(128));
+      partitionDataWriter.flush(false, false);
     }
-    partitionDataWriter.write(generateData(1920));
+    partitionDataWriter.write(generateDataWithHeader(1920));
     // mock trim
-    partitionDataWriter.flush(false);
+    partitionDataWriter.flush(false, false);
     partitionDataWriter.close();
-    reduceFileMeta = (ReduceFileMeta) pctx._3().getFileMeta();
+    reduceFileMeta = (ReduceFileMeta) partitionDataWriter.getMemoryFileInfo().getFileMeta();
     assertEquals(reduceFileMeta.getNumChunks(), 3);
     assertEquals(reduceFileMeta.getLastChunkOffset(), 4096);
     assertEquals(
         reduceFileMeta.getChunkOffsets().get(3) - reduceFileMeta.getChunkOffsets().get(2), 2048);
   }
 
-  private File getTemporaryFile() throws IOException {
-    String filename = UUID.randomUUID().toString();
-    File temporaryFile = new File(tempDir, filename);
-    temporaryFile.createNewFile();
-    return temporaryFile;
-  }
-
-  private byte[] generateData() {
+  private byte[] generateDataWithHeader() {
     ThreadLocalRandom rand = ThreadLocalRandom.current();
+    byte[] header = new byte[16];
+    Platform.putInt(header, Platform.BYTE_ARRAY_OFFSET, 1);
+    Platform.putInt(header, Platform.BYTE_ARRAY_OFFSET + 4, 1);
+    Platform.putInt(header, Platform.BYTE_ARRAY_OFFSET + 8, 1);
     byte[] hello = "hello, world".getBytes(StandardCharsets.UTF_8);
     int tempLen = rand.nextInt(256 * 1024) + 128 * 1024;
     int len = (int) (Math.ceil(1.0 * tempLen / hello.length) * hello.length);
+    Platform.putInt(header, Platform.BYTE_ARRAY_OFFSET + 12, len);
 
-    byte[] data = new byte[len];
+    byte[] data = new byte[16 + len];
+    System.arraycopy(header, 0, data, 0, 16);
     for (int i = 0; i < len; i += hello.length) {
-      System.arraycopy(hello, 0, data, i, hello.length);
+      System.arraycopy(hello, 0, data, 16 + i, hello.length);
     }
     return data;
   }
 
-  private ByteBuf generateData(int len) {
+  private ByteBuf generateDataWithHeader(int len) {
     byte[] data = new byte[len];
-    for (int i = 0; i < len; i++) {
-      data[i] = 'a';
-    }
+    Arrays.fill(data, (byte) 'a');
     return Unpooled.wrappedBuffer(data);
   }
 }
diff --git a/worker/src/test/scala/org/apache/celeborn/service/deploy/memory/MemoryManagerSuite.scala b/worker/src/test/scala/org/apache/celeborn/service/deploy/memory/MemoryManagerSuite.scala
index 6f91b4374..78fc43670 100644
--- a/worker/src/test/scala/org/apache/celeborn/service/deploy/memory/MemoryManagerSuite.scala
+++ b/worker/src/test/scala/org/apache/celeborn/service/deploy/memory/MemoryManagerSuite.scala
@@ -53,7 +53,7 @@ class MemoryManagerSuite extends CelebornFunSuite {
     val conf = new CelebornConf()
     try {
       val memoryManager = MemoryManager.initialize(conf)
-      val maxDirectorMemory = memoryManager.maxDirectorMemory
+      val maxDirectorMemory = memoryManager.maxDirectMemory
       val pushThreshold =
         (conf.workerDirectMemoryRatioToPauseReceive * maxDirectorMemory).longValue()
       val replicateThreshold =
@@ -90,7 +90,7 @@ class MemoryManagerSuite extends CelebornFunSuite {
   test("[CELEBORN-882] Test MemoryManager check memory thread logic") {
     val conf = new CelebornConf()
     val memoryManager = MemoryManager.initialize(conf)
-    val maxDirectorMemory = memoryManager.maxDirectorMemory
+    val maxDirectorMemory = memoryManager.maxDirectMemory
     val pushThreshold =
       (conf.workerDirectMemoryRatioToPauseReceive * maxDirectorMemory).longValue()
     val replicateThreshold =