Repository: juicedata/juicefs
Pull request #3006 (2022-11-22 07:46:55)

update en/status_check_and_maintenance.md


Patch:
diff --git a/cmd/config.go b/cmd/config.go
index 13941d09..7ec65dc0 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -258,7 +258,7 @@ func config(ctx *cli.Context) error {
 			logger.Fatalf("Format encrypt: %s", err)
 		}
 	}
-	if err = m.Init(*format, false); err == nil {
+	if err = m.Init(format, false); err == nil {
 		fmt.Println(msg.String()[:msg.Len()-1])
 	}
 	return err
diff --git a/cmd/format.go b/cmd/format.go
index 025f342e..f93d7bf5 100644
--- a/cmd/format.go
+++ b/cmd/format.go
@@ -472,7 +472,7 @@ func format(c *cli.Context) error {
 			logger.Fatalf("Format encrypt: %s", err)
 		}
 	}
-	if err = m.Init(*format, c.Bool("force")); err != nil {
+	if err = m.Init(format, c.Bool("force")); err != nil {
 		if create {
 			_ = blob.Delete("juicefs_uuid")
 		}
diff --git a/cmd/gateway.go b/cmd/gateway.go
index 52543170..83fa4c31 100644
--- a/cmd/gateway.go
+++ b/cmd/gateway.go
@@ -191,9 +191,7 @@ func initForSvc(c *cli.Context, mp string, metaUrl string) (*vfs.Config, *fs.Fil
 	}
 	registerer, registry := wrapRegister(mp, format.Name)
 
-	blob, err := NewReloadableStorage(format, func() (*meta.Format, error) {
-		return getFormat(c, metaCli)
-	})
+	blob, err := NewReloadableStorage(format, metaCli, updateFormat(c))
 	if err != nil {
 		logger.Fatalf("object storage: %s", err)
 	}
diff --git a/cmd/mount.go b/cmd/mount.go
index 60bbe816..5faeab0c 100644
--- a/cmd/mount.go
+++ b/cmd/mount.go
@@ -175,18 +175,15 @@ func wrapRegister(mp, name string) (prometheus.Registerer, *prometheus.Registry)
 	return registerer, registry
 }
 
-func getFormat(c *cli.Context, metaCli meta.Meta) (*meta.Format, error) {
-	format, err := metaCli.Load(true)
-	if err != nil {
-		return nil, fmt.Errorf("load setting: %s", err)
-	}
-	if c.IsSet("bucket") {
-		format.Bucket = c.String("bucket")
-	}
-	if c.IsSet("storage") {
-		format.Storage = c.String("storage")
+func updateFormat(c *cli.Context) func(*meta.Format) {
+	return func(format *meta.Format) {
+		if c.IsSet("bucket") {
+			format.Bucket = c.String("bucket")
+		}
+		if c.IsSet("storage") {
+			format.Storage = c.String("storage")
+		}
 	}
-	return format, nil
 }
 
 func daemonRun(c *cli.Context, addr string, vfsConf *vfs.Config, m meta.Meta) {
@@ -404,35 +401,38 @@ func initBackgroundTasks(c *cli.Context, vfsConf *vfs.Config, metaConf *meta.Con
 
 type storageHolder struct {
 	object.ObjectStorage
+	fmt meta.Format
 }
 
-func NewReloadableStorage(format *meta.Format, reload func() (*meta.Format, error)) (object.ObjectStorage, error) {
+func NewReloadableStorage(format *meta.Format, cli meta.Meta, patch func(*meta.Format)) (object.ObjectStorage, error) {
+	if patch != nil {
+		patch(format)
+	}
 	blob, err := createStorage(*format)
 	if err != nil {
 		return nil, err
 	}
-	holder := &storageHolder{blob}
-	go func() {
-		old := *format // keep a copy, so it will not refreshed
-		for {
-			time.Sleep(time.Minute)
-			new, err := reload()
+	holder := &storageHolder{
+		ObjectStorage: blob,
+		fmt:           *format, // keep a copy to find the change
+	}
+	cli.OnReload(func(new *meta.Format) {
+		if patch != nil {
+			patch(new)
+		}
+		old := &holder.fmt
+		if new.Storage != old.Storage || new.Bucket != old.Bucket || new.AccessKey != old.AccessKey || new.SecretKey != old.SecretKey || new.SessionToken != old.SessionToken {
+			logger.Infof("found new configuration: storage=%s bucket=%s ak=%s", new.Storage, new.Bucket, new.AccessKey)
+
+			newBlob, err := createStorage(*new)
 			if err != nil {
-				logger.Warnf("reload config: %s", err)
-				continue
-			}
-			if new.Storage != old.Storage || new.Bucket != old.Bucket || new.AccessKey != old.AccessKey || new.SecretKey != old.SecretKey || new.SessionToken != old.SessionToken {
-				logger.Infof("found new configuration: storage=%s bucket=%s ak=%s", new.Storage, new.Bucket, new.AccessKey)
-				newBlob, err := createStorage(*new)
-				if err != nil {
-					logger.Warnf("object storage: %s", err)
-					continue
-				}
-				holder.ObjectStorage = newBlob
-				old = *new
+				logger.Warnf("object storage: %s", err)
+				return
 			}
+			holder.ObjectStorage = newBlob
+			holder.fmt = *new
 		}
-	}()
+	})
 	return holder, nil
 }
 
@@ -548,7 +548,7 @@ func mount(c *cli.Context) error {
 	metaConf := getMetaConf(c, mp, c.Bool("read-only") || utils.StringContains(strings.Split(c.String("o"), ","), "ro"))
 	metaConf.CaseInsensi = strings.HasSuffix(mp, ":") && runtime.GOOS == "windows"
 	metaCli := meta.NewClient(addr, metaConf)
-	format, err := getFormat(c, metaCli)
+	format, err := metaCli.Load(true)
 	if err != nil {
 		return err
 	}
@@ -556,9 +556,7 @@ func mount(c *cli.Context) error {
 	// Wrap the default registry, all prometheus.MustRegister() calls should be afterwards
 	registerer, registry := wrapRegister(mp, format.Name)
 
-	blob, err := NewReloadableStorage(format, func() (*meta.Format, error) {
-		return getFormat(c, metaCli)
-	})
+	blob, err := NewReloadableStorage(format, metaCli, updateFormat(c))
 	if err != nil {
 		return fmt.Errorf("object storage: %s", err)
 	}
diff --git a/docs/en/administration/fault_diagnosis_and_analysis.md b/docs/en/administration/fault_diagnosis_and_analysis.md
index 1407612c..c55d2084 100644
--- a/docs/en/administration/fault_diagnosis_and_analysis.md
+++ b/docs/en/administration/fault_diagnosis_and_analysis.md
@@ -11,15 +11,15 @@ import TabItem from '@theme/TabItem';
 
 ## Client log
 
-JuiceFS client will output logs for troubleshooting while running. The level of logs in terms of fatality follows DEBUG < INFO < WARNING < ERROR < FATAL. Since DEBUG logs are not printed by default, you need to explicitly enable it if needed, e.g. by adding the `--debug` option when running the JuiceFS client. 
+JuiceFS client will output logs for troubleshooting while running. The level of logs in terms of fatality follows DEBUG < INFO < WARNING < ERROR < FATAL. Since DEBUG logs are not printed by default, you need to explicitly enable it if needed, e.g. by adding the `--debug` option when running the JuiceFS client.
 
 Different JuiceFS clients print logs in different ways, which are described as follows.
 
 ### Mount point
 
-When a JuiceFS file system is mounted with the [`-d` option](../reference/command_reference.md#juicefs-mount) (indicating running in the background), it will print logs to the system log file and local log file simultaneously. Depending on which user is running when mounting the file system, the paths of the local log files are slightly different. For root, the local log file locates at `/var/log/juicefs.log`, while it locates at `$HOME/.juicefs/juicefs.log` for non-root users. Please refer to [`--log` option](../reference/command_reference.md#juicefs-mount) for details.
+When a JuiceFS file system is mounted with the [`-d` option](../reference/command_reference.md#mount) (indicating running in the background), it will print logs to the system log file and local log file simultaneously. Depending on which user is running when mounting the file system, the paths of the local log files are slightly different. For root, the local log file locates at `/var/log/juicefs.log`, while it locates at `$HOME/.juicefs/juicefs.log` for non-root users. Please refer to [`--log` option](../reference/command_reference.md#mount) for details.
 
-Depending on the operating system, there are different commands to retrieve system logs or read local log files directly. 
+Depending on the operating system, there are different commands to retrieve system logs or read local log files directly.
 
 <Tabs>
   <TabItem value="local-log-file" label="Local log file">
@@ -149,7 +149,7 @@ By default, pprof listens on port numbers ranging from 6060 to 6099. That's why
 - Goroutine stack information: `http://localhost:<port>/debug/pprof/goroutine?debug=1`
 - CPU performance statistics: `http://localhost:<port>/debug/pprof/profile?seconds=30`
 - Memory allocation statistics: `http://localhost:<port>/debug/pprof/heap`
-- 
+-
 :::tip
 You can also use the debug command to automatically collect these runtime information and save it locally. By default, it is saved to the debug directory under the current directory, for example:
 ```bash
diff --git a/docs/en/administration/monitoring.md b/docs/en/administration/monitoring.md
index 00a03d55..1fdb54a2 100644
--- a/docs/en/administration/monitoring.md
+++ b/docs/en/administration/monitoring.md
@@ -76,7 +76,7 @@ There are different ways to collect monitoring metrics depending on how JuiceFS
 
 ### Mount point
 
-When the JuiceFS file system is mounted via the [`juicefs mount`](../reference/command_reference.md#juicefs-mount) command, you can collect monitoring metrics via the address `http://localhost:9567/metrics`, or you can customize it via the `--metrics` option. For example:
+When the JuiceFS file system is mounted via the [`juicefs mount`](../reference/command_reference.md#mount) command, you can collect monitoring metrics via the address `http://localhost:9567/metrics`, or you can customize it via the `--metrics` option. For example:
 
 ```shell
 juicefs mount --metrics localhost:9567 ...
diff --git a/docs/en/administration/status_check_and_maintenance.md b/docs/en/administration/status_check_and_maintenance.md
index b5fba28a..92e879fa 100644
--- a/docs/en/administration/status_check_and_maintenance.md
+++ b/docs/en/administration/status_check_and_maintenance.md
@@ -4,5 +4,228 @@ sidebar_position: 4
 ---
 # Status Check & Maintenance
 
-:::note
-Working in progress.
\ No newline at end of file
+Any kind of storage system needs to be checked and maintained regularly after it is put into use, so that potential problems can be found and fixed as early as possible to ensure reliable operation of the file system and the integrity and consistency of the stored data.
+
+JuiceFS provides a series of tools to check and maintain the file system, not only to help us understand the basic information of the file system and its operation status, but also to help us detect and fix potential problems more easily.
+
+## status
+
+`juicefs status` is used to review basic information about a JuiceFS file system and the status of all active sessions (including mounts, SDK accesses, S3 gateways, WebDAV connections).
+
+The basic information of the file system includes name, UUID, storage type,  Bucket, recycle bin status, etc.
+
+```shell
+juicefs status redis://xxx.cache.amazonaws.com:6379/1
+```
+
+```json
+{
+  "Setting": {
+    "Name": "myjfs",
+    "UUID": "6b0452fc-0502-404c-b163-c9ab577ec766",
+    "Storage": "s3",
+    "Bucket": "https://xxx.s3.amazonaws.com",
+    "AccessKey": "xxx",
+    "SecretKey": "removed",
+    "BlockSize": 4096,
+    "Compression": "none",
+    "TrashDays": 1,
+    "MetaVersion": 1
+  },
+  "Sessions": [
+    {
+      "Sid": 2,
+      "Heartbeat": "2021-08-23T16:47:59+08:00",
+      "Version": "1.0.0+2022-08-08.cf0c269",
+      "Hostname": "ubuntu-s-1vcpu-1gb-sgp1-01",
+      "MountPoint": "/home/herald/mnt",
+      "ProcessID": 2869146
+    }
+  ]
+}
+```
+
+Specifying the `Sid` of a session with the `--session, -s` option allows you to provide more information about the session.
+
+```shell
+juicefs status --session 2 redis://xxx.cache.amazonaws.com:6379/1
+```
+
+```json
+{
+  "Sid": 2,
+  "Heartbeat": "2021-08-23T16:47:59+08:00",
+  "Version": "1.0.0+2022-08-08.cf0c269",
+  "Hostname": "ubuntu-s-1vcpu-1gb-sgp1-01",
+  "MountPoint": "/home/herald/mnt",
+  "ProcessID": 2869146
+}
+```
+
+Depending on the status of the session, the message may also include.
+
+- Sustained inodes: These are files that have been deleted, but because they have been opened in this session, they will be held temporarily until the file is closed.
+- Flocks: Information about the file flock locked by this session
+- Plocks: Information about the file plock locked by this session
+
+## info
+
+`juicefs info` is used to check the metadata information of the specified file or directory, which includes the object path on the object storage for each block corresponding to that file.
+
+### Checking the metadata of a file
+
+```shell
+$ juicefs info mnt/luggage-6255515.jpg
+
+mnt/luggage-6255515.jpg :
+  inode: 36
+  files: 1
+   dirs: 0
+ length: 789.02 KiB (807955 Bytes)
+   size: 792.00 KiB (811008 Bytes)
+   path: /luggage-6255515.jpg
+objects:
++------------+------------------------------+--------+--------+--------+
+| chunkIndex |          objectName          |  size  | offset | length |
++------------+------------------------------+--------+--------+--------+
+|          0 | myjfs/chunks/0/0/80_0_807955 | 807955 |      0 | 807955 |
++------------+------------------------------+--------+--------+--------+
+```
+
+### Checking the metadata of a directory
+
+This command checks only one level of directories by default.
+
+```shell
+$ juicefs info ./mnt
+
+mnt :
+  inode: 1
+  files: 9
+   dirs: 4
+ length: 2.41 MiB (2532102 Bytes)
+   size: 2.44 MiB (2555904 Bytes)
+   path: /
+```
+
+If you want to recursively check all subdirectories, you need to specify the `--recursive, -r` option.
+
+```shell
+$ juicefs info -r ./mnt
+
+./mnt :
+  inode: 1
+  files: 33
+   dirs: 4
+ length: 80.29 MiB (84191037 Bytes)
+   size: 80.34 MiB (84242432 Bytes)
+   path: /
+```
+
+### Checking metadata with inode
+
+You can also perform reverse lookup on the file path and data block information via inode , but you need to enter the mountpoint directory.
+
+```shell
+~     $ cd mnt
+~/mnt $ juicefs info -i 36
+
+36 :
+  inode: 36
+  files: 1
+   dirs: 0
+ length: 789.02 KiB (807955 Bytes)
+   size: 792.00 KiB (811008 Bytes)
+   path: /luggage-6255515.jpg
+objects:
++------------+------------------------------+--------+--------+--------+
+| chunkIndex |          objectName          |  size  | offset | length |
++------------+------------------------------+--------+--------+--------+
+|          0 | myjfs/chunks/0/0/80_0_807955 | 807955 |      0 | 807955 |
++------------+------------------------------+--------+--------+--------+
+```
+
+## gc
+
+`juicefs gc` is designed to handle "object leaks" and run compaction on data fragments created by file overwrites. It scans metadata and compare with object storage to find or clean up any object storage blocks that need processing.
+
+:::info
+**Object Leak** is a situation where a block of data is in the object storage, but there is no corresponding record in the metadata engine. Object leaks are rare and can be caused by program bugs, unanticipated problems with the metadata engine or object storage, power outages, network disconnections, etc.
+:::
+
+:::tip
+Temporary intermediate files may be produced when files are uploaded to the object storage, and they are cleaned up when the storage is complete. To avoid intermediate files being misclassified as leaked objects, `juicefs gc` skips files uploaded in the last 1 hour by default. The skipped time range (in seconds) can be adjusted via the `JFS_GC_SKIPPEDTIME` environment variable. For example, to set skip the last 2 hours of files: `export JFS_GC_SKIPPEDTIME=7200`.
+:::
+
+:::tip
+Because the `juicefs gc` command scans all objects in the object storage, there is some overhead in executing this command for file systems with large amounts of data.
+:::
+
+### Scan for leaked objects
+
+Although object leaks almost never occur, you can still perform the appropriate routine checks as needed, and by default `juicefs gc` only performs a scan for:
+
+```shell
+$ juicefs gc sqlite3://myjfs.db
+
+2022/11/10 11:35:53.662024 juicefs[24404] <INFO>: Meta address: sqlite3://myjfs.db [interface.go:402]
+2022/11/10 11:35:53.662759 juicefs[24404] <INFO>: Data use file:///Users/herald/.juicefs/local/myjfs/ [gc.go:108]
+  Listed slices count: 92
+Scanned objects count: 91 / 91 [======================================]  done
+  Valid objects count: 91
+  Valid objects bytes: 7.67 MiB (8040969 Bytes)
+ Leaked objects count: 0
+ Leaked objects bytes: 0.00 b   (0 Bytes)
+Skipped objects count: 0
+Skipped objects bytes: 0.00 b   (0 Bytes)
+2022/11/10 11:35:53.665015 juicefs[24404] <INFO>: scanned 91 objects, 91 valid, 0 leaked (0 bytes), 0 skipped (0 bytes) [gc.go:306]
+```
+
+### Purge leaked objects
+
+When the `juicefs gc` command scans for "leaked objects", you can purge them with the `--delete` option. The client starts 10 threads by default to perform the purge operation, you can adjust the number of threads with the `--threads, -p` option.
+
+```shell
+$ juicefs gc sqlite3://myjfs.db --delete
+
+2022/11/10 10:49:31.490016 juicefs[24086] <INFO>: Meta address: sqlite3://myjfs.db [interface.go:402]
+2022/11/10 10:49:31.490831 juicefs[24086] <INFO>: Data use file:///Users/herald/.juicefs/local/myjfs/ [gc.go:108]
+  Listed slices count: 92
+Deleted pending count: 0
+Scanned objects count: 103 / 103 [====================================]  done
+  Valid objects count: 92
+  Valid objects bytes: 7.67 MiB  (8045065 Bytes)
+ Leaked objects count: 11
+ Leaked objects bytes: 12.87 MiB (13494874 Bytes)
+Skipped objects count: 0
+Skipped objects bytes: 0.00 b    (0 Bytes)
+2022/11/10 10:49:31.493682 juicefs[24086] <INFO>: scanned 103 objects, 92 valid, 11 leaked (13494874 bytes), 0 skipped (0 bytes) [gc.go:306]
+```
+
+You can then run `juicefs gc` again to check if the purge was successful.
+
+## fsck
+
+`juicefs fsck` is a tool that performs block-by-block comparison with metadata, mainly to fix various problems that may occur and can be fixed within the file system. It can help you find cases where records exist in the metadata engine but there is no corresponding data block in the object storage, and it can also check if the file attribute information exists.
+
+```shell {5}
+$ juicefs fsck sqlite3://myjfs2.db
+
+2022/11/10 17:31:19.062348 juicefs[26158] <INFO>: Meta address: sqlite3://myjfs2.db [interface.go:402]
+2022/11/10 17:31:19.063132 juicefs[26158] <INFO>: Data use file:///Users/herald/.juicefs/local/myjfs/ [fsck.go:73]
+2022/11/10 17:31:19.065857 juicefs[26158] <ERROR>: can't find block 0/1/1063_0_2693747 for file /david-bruno-silva-Z19vToWBDIc-unsplash.jpg: stat /Users/herald/.juicefs/local/myjfs/chunks/0/1/1063_0_2693747: no such file or directory [fsck.go:146]
+  Found blocks count: 68
+  Found blocks bytes: 34.24 MiB (35904042 Bytes)
+ Listed slices count: 65
+Scanned slices count: 65 / 65 [=======================================]  done
+Scanned slices bytes: 36.81 MiB (38597789 Bytes)
+   Lost blocks count: 1
+   Lost blocks bytes: 2.57 MiB  (2693747 Bytes)
+2022/11/10 17:31:19.066243 juicefs[26158] <FATAL>: 1 objects are lost (2693747 bytes), 1 broken files:
+        INODE: PATH
+           57: /david-bruno-silva-Z19vToWBDIc-unsplash.jpg [fsck.go:168]
+```
+
+As you can see from the results, the `juicefs fsck` scan found a file corruption in the file system due to a missing data block.
+
+Although the result indicates that the file in the backend storage is corrupted, it is still necessary to check if the file is accessible at the mount point, because JuiceFS will cache the recently accessed file data locally, and the version of the file before the corruption can be re-uploaded with the cached file data block to avoid losing data if it is already cached locally. You can look for cached data in the cache directory (i.e. the path corresponding to the `--cache-dir` option) based on the path of the block output from the `juicefs fsck` command, e.g. the path of the missing block in the above example is `0/1/1063_0_2693747`.
diff --git a/docs/en/deployment/juicefs_on_docker.md b/docs/en/deployment/juicefs_on_docker.md
index f5be49a9..a65c7ef6 100644
--- a/docs/en/deployment/juicefs_on_docker.md
+++ b/docs/en/deployment/juicefs_on_docker.md
@@ -269,7 +269,7 @@ services:
 
 You can adjust the parameters of the format and mount commands in the above code as needed. For example, when there is some latency in the network connection between local and object storage and local storage is reliable, you can mount the file system by adding the `--writeback` option so that files can be stored to the local cache first and then uploaded to the object storage asynchronously, see [client-side write cache](./cache_management/#client-write-data-cache) for details.
 
-For more file system creation and mounting parameters, please see [command reference](./command_reference#juicefs-mount).
+For more file system creation and mounting parameters, please see [command reference](../reference/command_reference.md#mount).
 
 #### Deployment and Usage
 
diff --git a/docs/en/faq.md b/docs/en/faq.md
index 9afff3fe..0875b1ab 100644
--- a/docs/en/faq.md
+++ b/docs/en/faq.md
@@ -99,7 +99,7 @@ This is just a rough introduction to the implementation logic. The specific read
 
 ### How to copy a large number of small files into JuiceFS quickly?
 
-You could mount JuiceFS with [`--writeback` option](reference/command_reference.md#juicefs-mount), which will write the small files into local disks first, then upload them to object storage in background, this could speedup coping many small files into JuiceFS.
+You could mount JuiceFS with [`--writeback` option](reference/command_reference.md#mount), which will write the small files into local disks first, then upload them to object storage in background, this could speedup coping many small files into JuiceFS.
 
 See ["Write Cache in Client"](guide/cache_management.md#write-cache-in-client) for more information.
 
diff --git a/docs/en/getting-started/README.md b/docs/en/getting-started/README.md
index d767fe31..854670ed 100644
--- a/docs/en/getting-started/README.md
+++ b/docs/en/getting-started/README.md
@@ -69,7 +69,7 @@ Since no storage-related options are specified in this example, the local disk i
 
 ### Basic Concept
 
-JuiceFS client provides a command [`mount`](../reference/command_reference.md#juicefs-mount) to mount file systems in the following format.
+JuiceFS client provides a command [`mount`](../reference/command_reference.md#mount) to mount file systems in the following format.
 
 ```shell
 juicefs mount [command options] META-URL MOUNTPOINT
diff --git a/docs/en/guide/cache_management.md b/docs/en/guide/cache_management.md
index f1255974..fda7a8f3 100644
--- a/docs/en/guide/cache_management.md
+++ b/docs/en/guide/cache_management.md
@@ -10,20 +10,21 @@ For a file system driven by a combination of object storage and database, cache
 JuiceFS provides various caching mechanisms including metadata caching, data read/write caching, etc.
 
 :::tip Does my application really need cache?
-Cache improve performance, only when application needs to repeatedly read files, so if you know for sure you're in a scenario where data is only accessed once (e.g. data cleansing during ETL), you can safely turn off cache to prevent overhead.
+Data caching will effectively improve the performance of random reads. For applications that require high random read performance (e.g. Elasticsearch, ClickHouse), it is recommended to use faster storage medium and allocate more space for cache.
+
+Meanwhile, cache improve performance only when application needs to repeatedly read files, so if you know for sure you're in a scenario where data is only accessed once (e.g. data cleansing during ETL), you can safely turn off cache to prevent overhead.
 :::
 
 ## Data consistency
 
 JuiceFS provides a "close-to-open" consistency guarantee, which means that when two or more clients read and write the same file at the same time, the changes made by client A may not be immediately visible to client B. However, once the file is closed by client A, any client re-opened it afterwards is guaranteed to see the latest data, no matter it is on the same node with A or not.
 
-"Close-to-open" is the minimum consistency guarantee provided by JuiceFS, and in some cases it may not be necessary to reopen the file to access the latest written data. For example, multiple applications use the same JuiceFS client to access the same file (where file changes are immediately visible), or to view the latest data on different nodes with the `tail -f` command.
+"Close-to-open" is the minimum consistency guarantee provided by JuiceFS, and in some cases it may not be necessary to reopen the file to access the latest written data. For example:
 
-## Cache read mechanism
+* Multiple applications use the same JuiceFS client to access the same file (where file changes are immediately visible)
+* View the latest data on different nodes with the `tail -f` command (require use Linux)
 
-JuiceFS provides a multi-level cache to improve the performance of frequently accessed data. Read requests will try kernel page cache, the pre-read buffer of the JuiceFS process, and the local disk cache in turn. If the data requested is not found in any level of the cache, it will be read from the object storage, and also be written into every level of the cache asynchronously to improve the performance of subsequent accesses.
-
-![](../images/juicefs-cache.png)
+As for object storage, JuiceFS divides files into data blocks (default 4MiB), assigns unique IDs and saves them on object storage. Any modification operation of the file will generate a new data block, and the original block remains unchanged, including the cached data on the local disk. So don't worry about the consistency of the data cache, because once the file is modified, JuiceFS will read the new data block from the object storage, and will not read the data block (which will be deleted later) corresponding to the overwritten part of the file.
 
 ## Metadata cache
 
@@ -31,7 +32,7 @@ JuiceFS supports caching metadata both in kernel and in client memory (i.e. Juic
 
 ### Kernel metadata cache {#kernel-metadata-cache}
 
-There are three kinds of metadata which can be cached in kernel: **attribute**, **entry** and **directory**. The TTL of them can be specified by the following [mount options](../reference/command_reference.md#juicefs-mount):
+There are three kinds of metadata which can be cached in kernel: **attribute**, **entry** and **directory**. The TTL of them can be specified by the following [mount options](../reference/command_reference.md#mount):
 
 ```
 --attr-cache value       attributes cache TTL in seconds (default: 1)
@@ -39,7 +40,7 @@ There are three kinds of metadata which can be cached in kernel: **attribute**,
 --dir-entry-cache value  dir entry cache TTL in seconds (default: 1)
 ```
 
-JuiceFS caches attributes, file entries, and directory entries in kernel for 1 second by default to improve the performance of `lookup` and `getattr`. When clients on multiple nodes are sharing the same file system, the metadata cached in kernel will only expire due to TTL exceeding. That is, in an edge case, it may happen that the metadata modification (e.g., `chown`) on node A cannot be seen immediately on node B. Of course, all nodes will eventually be able to see the changes made by A after the expiry of the cache.
+Attribute, entry and direntries are cached for 1 second by default, this speeds up lookup and getattr operations. When clients on multiple nodes are sharing the same file system, the metadata cached in kernel will only expire by TTL. So in edge cases, it's possible that metadata modifications (e.g., `chown`) on node A cannot be seen immediately on node B. Nevertheless, all nodes will eventually be able to see the changes made by A after cache expiration.
 
 ### In-memory client metadata cache
 
@@ -47,19 +48,17 @@ JuiceFS caches attributes, file entries, and directory entries in kernel for 1 s
 This feature requires JuiceFS >= 0.15.2
 :::
 
-When a JuiceFS client `open()` a file, the attributes of the file are automatically cached in client memory. If the [`--open-cache`](../reference/command_reference.md#juicefs-mount) option is set to a value greater than 0 when mounting the file system, subsequent `getattr()` and `open()` operations will return the result from the in-memory cache immediately, as long as the cache has not expired.
+When a JuiceFS client `open()` a file, the attributes of the file are automatically cached in client memory. If the [`--open-cache`](../reference/command_reference.md#mount) option is set to a value greater than 0 when mounting the file system, subsequent `getattr()` and `open()` operations will return the result from the in-memory cache immediately, as long as the cache has not expired.
 
-When doing `read()` on a file, the chunk and slice information of the file is automatically cached in the client memory. Reading the chunk again during the cache lifetime will return the slice information from the in-memory cache immediately.
+When doing `read()` on a file, the chunk and slice information of the file is automatically cached in the client memory. Reading the chunk again during the cache lifetime will return the slice information from the in-memory cache immediately (check ["How JuiceFS Stores Files"](../introduction/architecture.md#how-juicefs-stores-files) to know what chunk and slice are).
 
-:::tip
-You can check ["How JuiceFS Stores Files"](../reference/how_juicefs_store_files.md) to know what chunk and slice are.
-:::
-
-By default, if there is a file whose metadata has been cached in memory and not been accessed by any process for more than 1 hour, all its metadata cache will be automatically deleted.
+To ensure consistency, `--open-cache` is disabled by default, and every time you open a file, the client need to directly access the metadata engine. However, if the file is rarely modified, or in a read-only scenario (such as AI model training), it is recommended to set `--open-cache` according to the situation to further improve the read performance.
 
 ## Data cache
 
-To improve performance, JuiceFS also provides various caching mechanisms for data, including page cache in the kernel, local file system cache in client host, and read/write buffer in client process itself.
+JuiceFS provides a multi-level cache to improve the performance of frequently accessed data. Read requests will try kernel page cache, the pre-read buffer of the JuiceFS process, and the local disk cache in turn. If the data requested is not found in any level of the cache, it will be read from the object storage, and also be written into every level of the cache asynchronously to improve the performance of subsequent accesses.
+
+![](../images/juicefs-cache.png)
 
 ### Read/Write Buffer {#buffer-size}
 
@@ -79,41 +78,39 @@ For a file that has already been read, the kernel automatically caches its conte
 
 Thanks to the kernel cache, repeated reads of the same file in JuiceFS can be extremely fast, with latencies as low as a few microseconds and throughput up to several GiBs per second.
 
-JuiceFS clients currently do not have kernel write caching enabled by default. Starting with [Linux kernel 3.15](https://github.com/torvalds/linux/commit/4d99ff8f12e), FUSE supports ["writeback-cache mode"]( https://www.kernel.org/doc/Documentation/filesystems/fuse-io.txt), which means that the `write()` system call can be done very quickly. You can set the [`-o writeback_cache`](../reference/fuse_mount_options.md#writeback_cache) option at [mounting file system](../reference/command_reference.md#juicefs-mount) to enable writeback-cache mode. It is recommended to enable this mount option when very small data (e.g. around 100 bytes) needs to be written frequently.
+JuiceFS clients currently do not have kernel write caching enabled by default. Starting with [Linux kernel 3.15](https://github.com/torvalds/linux/commit/4d99ff8f12e), FUSE supports ["writeback-cache mode"]( https://www.kernel.org/doc/Documentation/filesystems/fuse-io.txt), which means that the `write()` system call can be done very quickly. You can set the [`-o writeback_cache`](../reference/fuse_mount_options.md#writeback_cache) option at [mounting file system](../reference/command_reference.md#mount) to enable writeback-cache mode. It is recommended to enable this mount option when very small data (e.g. around 100 bytes) needs to be written frequently.
 
 ### Client read data cache {#client-read-cache}
 
-The JuiceFS client automatically prefetches data into the cache based on the read pattern, thus improving sequential read performance. By default, it prefetches 1 block concurrently and caches it locally when reading. The local cache can be set up on any local file system based on HDD, SSD or memory.
+Local cache can be set up on any local file system based on HDD, SSD or memory.
 
-:::tip Hint
-You can check ["How JuiceFS Stores Files"](../reference/how_juicefs_store_files.md) to learn what a block is.
-:::
+Data downloaded from object storage, as well as small data (smaller than a single block) uploaded to object storage will be cached by JuiceFS Client, without compression or encryption.
 
-The local cache can be adjusted at [mounting file system](../reference/command_reference.md#juicefs-mount) with the following options.
+Below are some important options for cache configuration (see [`juicefs mount`](../reference/command_reference.md#mount) for complete reference):
 
-```
---cache-dir value         directory paths of local cache, use colon to separate multiple paths (default: "$HOME/.juicefs/cache" or "/var/jfsCache")
---cache-size value        size of cached object for read in MiB (default: 102400)
---free-space-ratio value  minimum free space (ratio) (default: 0.1)
---cache-partial-only      cache only random/small read (default: false)
-```
+* `--prefetch`
+
+  Prefetch N blocks concurrently (default to 1), prefetch mechanism works like this: when reading a block at arbitrary position, the whole block is asynchronously scheduled for download. Prefetch often improves random read performance, but if your scenario cannot effectively utilize prefetched data (for example, reading large files randomly and sparsely), prefetch will bring read amplification, consider set to 0 to disable it.
 
-Specifically, there are two ways to store the local cache of JuiceFS in memory, i.e., setting `--cache-dir` to `memory` or `/dev/shm/<cache-dir>`. The difference between these two approaches is that the former deletes the cache data after remounting the JuiceFS file system, while the latter retains the cache data. Still, there is not much difference in performance between them.
+  JuiceFS is equipped with another internal similar mechanism called "readahead": when doing sequential reads, client will download nearby blocks in advance, improving sequential performance. The concurrency of readahead is affected by the size of ["Read/Write Buffer"](#buffer-size), the larger the read-write buffer, the higher the concurrency.
 
-The JuiceFS client writes data downloaded from the object storage (including new uploaded data containing less than 1 block) to the cache directory as fast as possible, without compression or encryption. **Because JuiceFS generates unique names for all block objects written to the object storage, and all block objects are not modified, there is no need to worry about the invalidation of the cached data when the file content is updated.**
+* `--cache-dir`
 
-Data caching can effectively improve the performance of random reads. For applications like Elasticsearch, ClickHouse, etc. that require higher random read performance, it is recommended to point the cache path to a faster storage medium and allocate more cache space.
+  Cache directory, default to `/var/jfsCache` or `$HOME/.juicefs/cache`. Please read ["Cache directory"](#cache-dir) for more information.
 
-#### Cache life cycle
+  If you are in urgent need to free up disk space, you can manually delete data under the cache directory, which is usually `<cache-dir>/<UUID>/raw/`.
 
-When the cache usage space reaches the upper limit (that is, the cache size is greater than or equal to `--cache-size`) or the disk is almost full (that is, the free disk space ratio is less than `--free-space-ratio`), the JuiceFS client will automatically expire cache using random two algorithm, i.e. pick two blocks and expire the least accessed one.
+* `--cache-size` and `--free-space-ratio`
 
-JuiceFS allocates 100GiB cache space by default, but this does not mean that you have to have a disk with a capacity more than that. This value represents the maximum capacity that a JuiceFS client may use if the disk capacity allows. When the remaining space on the disk is less than 100GiB, ensure that the remaining space is not less than 10% by default.
+  Cache size (in MiB, default to 102400) and minimum ratio of free size (default 0.1). Both parameters is able to control cache size, if any of the two criteria is met, JuiceFS Client will expire cache usage using an algorithm similar to LRU, i.e. remove older and less used blocks.
 
-For example, if you set `--cache-dir` to a partition with a capacity of 50GiB, then if `--cache-size` is set to 100GiB, JuiceFS will always keep the cache capacity around 45GiB, which means that more than 10% of the partition is reserved.
+  Actual cache size may exceed configured value, because it is difficult to calculate the exact disk space taken by cache. Currently, JuiceFS takes the sum of all cached objects sizes using a minimum 4 KiB size, which is often different from the result of `du`.
 
+* `--cache-partial-only`
 
-JuiceFS currently estimates the size of cached objects by adding up the size of all cached objects with a minimum size of 4KiB, which is usually different from the `du` results.
+  Only cache small files and random small reads, do not cache whole block. This applies to conditions where object storage throughput is higher than the local cache device. Default value is false.
+
+  There are two main read patterns, sequential read and random read. Sequential read usually demands higher throughput while random reads needs lower latency. When local disk throughput is lower than object storage, consider enable `--cache-partial-only` so that sequential reads do not cache the whole block, but rather, only small reads (like footer of Parquet / ORC file) are cached. This allows JuiceFS to take advantage of low latency provided by local disk, and high throughput provided by object storage, at the same time.
 
 ### Client write data cache {#writeback}
 
@@ -181,7 +178,7 @@ Then run the warm up command.
 juicefs warmup -f warm.txt
 ```
 
-## Cache directory
+## Cache directory {#cache-dir}
 
 Depending on the operating system, the default cache path for JuiceFS is as follows:
 
@@ -228,10 +225,14 @@ Then, using that path as a cache, mount the filesystem.
 juicefs mount --cache-dir /dev/shm/jfscache redis://127.0.0.1:6379/1 /mnt/myjfs
 ```
 
+Another way to use memory for cache is set `--cache-dir` option to `memory`, this puts cache directly in client process memory, which is simpler compared to `/dev/shm`, but obviously cache will be lost after process restart, use this for tests and evaluations.
+
 ### Shared folders
 
 Shared directories created via SMB or NFS can also be used as cache for JuiceFS. For the case where multiple devices on the LAN mount the same JuiceFS file system, using shared directories on the LAN as cache paths can effectively relieve the bandwidth pressure of duplicate caches for multiple devices.
 
+But special attention needs to be paid. Usually, when the file system where the cache directory is located fails to work properly, the JuiceFS client can immediately return an error and downgrade to direct access to object storage. If the abnormality of the shared directory shows that the read operation is stuck (such as some network file system in kernel mode), then JuiceFS will also be stuck together. This requires you to tune the underlying file system behavior of the shared directory to achieve rapid failure.
+
 Using SMB/CIFS as an example, mount the shared directories on the LAN by using the tools provided by the `cifs-utils` package.
 
 ```shell
@@ -246,14 +247,12 @@ sudo juicefs mount --cache-dir /mnt/jfscache redis://127.0.0.1:6379/1 /mnt/myjfs
 
 ### Multiple cache directories
 
-JuiceFS supports setting multiple cache directories at the same time, thus avoiding the problem of insufficient cache space by splitting multiple paths using `:` (Linux, macOS) or `;` (Windows), e.g.:
+JuiceFS supports setting multiple cache directories at the same time, thus avoiding the problem of insufficient cache space by separating multiple paths using `:` (Linux, macOS) or `;` (Windows), e.g.:
 
 ```shell
 sudo juicefs mount --cache-dir ~/jfscache:/mnt/jfscache:/dev/shm/jfscache redis://127.0.0.1:6379/1 /mnt/myjfs
 ```
 
-When multiple cache paths are set, the client will write data evenly to each cache path using hash policy.
-
-When multiple cache directories are set, the `--cache-size` option represents the total size of data in all cache directories. It is recommended to ensure that the free space of different cache directories are close to each other, otherwise, the space of a cache directory may not be fully utilized.
+When multiple cache directories are set, or multiple devices are used as cache disks, the `--cache-size` option represents the total size of data in all cache directories. The client will use the hash strategy to evenly write data to each cache path, and cannot perform special tuning for multiple cache disks with different capacities or performances.
 
-For example, `--cache-dir` is `/data1:/data2`, where `/data1` has a free space of 1GiB, `/data2` has a free space of 2GiB, `--cache-size` is 3GiB, `--free-space-ratio` is 0.1. Because the cache write strategy is to write evenly, the maximum space allocated to each cache directory is `3GiB / 2 = 1.5GiB`, resulting in a maximum of 1.5GiB cache space in the `/data2` directory instead of `2GiB * 0.9 = 1.8GiB`.
+Therefore, it is recommended that the available space of different cache directories/cache disks be consistent, otherwise it may cause the situation that the space of a certain cache directory cannot be fully utilized. For example, `--cache-dir` is `/data1:/data2`, where `/data1` has a free space of 1GiB, `/data2` has a free space of 2GiB, `--cache-size` is 3GiB, `--free-space-ratio` is 0.1. Because the cache write strategy is to write evenly, the maximum space allocated to each cache directory is `3GiB / 2 = 1.5GiB`, resulting in a maximum of 1.5GiB cache space in the `/data2` directory instead of `2GiB * 0.9 = 1.8GiB`.
diff --git a/docs/en/guide/mount_at_boot.md b/docs/en/guide/mount_at_boot.md
index 053d59c6..a37f0ee4 100644
--- a/docs/en/guide/mount_at_boot.md
+++ b/docs/en/guide/mount_at_boot.md
@@ -16,7 +16,7 @@ Copy `juicefs` as `/sbin/mount.juicefs`, then edit `/etc/fstab` with following l
 <META-URL>    <MOUNTPOINT>       juicefs     _netdev[,<MOUNT-OPTIONS>]     0  0
 ```
 
-For the format of `<META-URL>`, please refer to the ["How to Set Up Metadata Engine"](how_to_set_up_metadata_engine.md) document, such as `redis://localhost:6379/1`. Then replace `<MOUNTPOINT>` with the path you want JuiceFS to mount, e.g. `/jfs`. If you want to set [mount options](../reference/command_reference.md#juicefs-mount), separate the options list with commas and replace `[,<MOUNT-OPTIONS>]`. Here is an example:
+For the format of `<META-URL>`, please refer to the ["How to Set Up Metadata Engine"](how_to_set_up_metadata_engine.md) document, such as `redis://localhost:6379/1`. Then replace `<MOUNTPOINT>` with the path you want JuiceFS to mount, e.g. `/jfs`. If you want to set [mount options](../reference/command_reference.md#mount), separate the options list with commas and replace `[,<MOUNT-OPTIONS>]`. Here is an example:
 
 ```
 redis://localhost:6379/1    /jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=204800     0  0
diff --git a/docs/en/reference/fuse_mount_options.md b/docs/en/reference/fuse_mount_options.md
index cf09ecfe..5ddd4868 100644
--- a/docs/en/reference/fuse_mount_options.md
+++ b/docs/en/reference/fuse_mount_options.md
@@ -5,7 +5,7 @@ slug: /fuse_mount_options
 ---
 # FUSE Mount Options
 
-This guide lists important FUSE mount options. These mount options are specified by the option `-o`  when execute the command [`juicefs mount`](../reference/command_reference.md#juicefs-mount) (use comma to separate multiple options). For example:
+This guide lists important FUSE mount options. These mount options are specified by the option `-o`  when execute the command [`juicefs mount`](../reference/command_reference.md#mount) (use comma to separate multiple options). For example:
 
 ```bash
 juicefs mount -d -o allow_other,writeback_cache localhost ~/jfs
diff --git a/docs/en/tutorials/aws.md b/docs/en/tutorials/aws.md
index 32a992ca..aa9ed091 100644
--- a/docs/en/tutorials/aws.md
+++ b/docs/en/tutorials/aws.md
@@ -182,7 +182,7 @@ sudo juicefs mount -d redis://[<redis-username>]:<redis-password>@<redis-url>:63
 
 > **Note**: When mounting a file system, only the database address is required but not the file system name. The default cache path is `/var/jfsCache`. Please make sure the current user has enough read/write permissions.
 
-You can optimize JuiceFS by adjusting the [mount options](../reference/command_reference.md#juicefs-mount), for example by `--cache-size` to change cache to 20GB.
+You can optimize JuiceFS by adjusting the [mount options](../reference/command_reference.md#mount), for example by `--cache-size` to change cache to 20GB.
 
 ```shell
 sudo juicefs mount --cache-size 20480 -d redis://herald-demo.abcdefg.0001.apse1.cache.amazonaws.com:6379/1  /mnt/jfs
diff --git a/docs/zh_cn/administration/fault_diagnosis_and_analysis.md b/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
index ab17dd20..477c020f 100644
--- a/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
+++ b/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
@@ -17,7 +17,7 @@ JuiceFS 客户端在运行过程中会输出日志用于故障诊断，日志等
 
 ### 挂载点
 
-当挂载 JuiceFS 文件系统时加上了 [`-d` 选项](../reference/command_reference.md#juicefs-mount)（表示后台运行），日志会同时输出到系统日志和本地日志文件，取决于挂载文件系统时的运行用户，本地日志文件的路径稍有区别。root 用户对应的日志文件路径是 `/var/log/juicefs.log`，非 root 用户的日志文件路径是 `$HOME/.juicefs/juicefs.log`，具体请参见 [`--log` 选项](../reference/command_reference.md#juicefs-mount)。
+当挂载 JuiceFS 文件系统时加上了 [`-d` 选项](../reference/command_reference.md#mount)（表示后台运行），日志会同时输出到系统日志和本地日志文件，取决于挂载文件系统时的运行用户，本地日志文件的路径稍有区别。root 用户对应的日志文件路径是 `/var/log/juicefs.log`，非 root 用户的日志文件路径是 `$HOME/.juicefs/juicefs.log`，具体请参见 [`--log` 选项](../reference/command_reference.md#mount)。
 
 取决于你使用的操作系统，你可以通过不同的命令获取系统日志或直接读取本地日志文件：
 
diff --git a/docs/zh_cn/administration/monitoring.md b/docs/zh_cn/administration/monitoring.md
index 18e81f33..da53e272 100644
--- a/docs/zh_cn/administration/monitoring.md
+++ b/docs/zh_cn/administration/monitoring.md
@@ -76,7 +76,7 @@ scrape_configs:
 
 ### 挂载点
 
-当通过 [`juicefs mount`](../reference/command_reference.md#juicefs-mount) 命令挂载 JuiceFS 文件系统后，可以通过 `http://localhost:9567/metrics` 这个地址收集监控指标，你也可以通过 `--metrics` 选项自定义。如：
+当通过 [`juicefs mount`](../reference/command_reference.md#mount) 命令挂载 JuiceFS 文件系统后，可以通过 `http://localhost:9567/metrics` 这个地址收集监控指标，你也可以通过 `--metrics` 选项自定义。如：
 
 ```shell
 juicefs mount --metrics localhost:9567 ...
diff --git a/docs/zh_cn/administration/status_check_and_maintenance.md b/docs/zh_cn/administration/status_check_and_maintenance.md
index 5f9a9369..a48adda1 100644
--- a/docs/zh_cn/administration/status_check_and_maintenance.md
+++ b/docs/zh_cn/administration/status_check_and_maintenance.md
@@ -124,7 +124,7 @@ $ juicefs info -r ./mnt
 
 ### 使用 inode 检查元数据
 
-还可以 inode 来反向查找文件路径及数据块的信息，但需要先进入挂载点：
+还可以通过 inode 来反向查找文件路径及数据块的信息，但需要先进入挂载点：
 
 ```shell
 ~     $ cd mnt
diff --git a/docs/zh_cn/deployment/juicefs_on_docker.md b/docs/zh_cn/deployment/juicefs_on_docker.md
index 2a4edb3b..fd18b202 100644
--- a/docs/zh_cn/deployment/juicefs_on_docker.md
+++ b/docs/zh_cn/deployment/juicefs_on_docker.md
@@ -269,7 +269,7 @@ services:
 
 可以根据需要调整上述代码中 format 和 mount 命令的参数，例如，当本地与对象存储的网络连接存在一定延迟且本地存储相对可靠时，可以通过添加 `--writeback` 选项挂载文件系统，让文件可以先存储到本地缓存，再异步上传到对象存储，详情参考[客户端写缓存](./cache_management/#客户端写缓存)。
 
-更多文件系统创建和挂载参数请查看[命令参考](./command_reference#juicefs-mount)。
+更多文件系统创建和挂载参数请查看[命令参考](../reference/command_reference.md#mount)。
 
 #### 部署和使用
 
diff --git a/docs/zh_cn/faq.md b/docs/zh_cn/faq.md
index 929938d8..5f7b0b15 100644
--- a/docs/zh_cn/faq.md
+++ b/docs/zh_cn/faq.md
@@ -102,7 +102,7 @@ JuiceFS 不将原始文件存入对象存储，而是将其按照某个大小（
 
 ### 怎么快速地拷贝大量小文件到 JuiceFS？
 
-请在挂载时加上 [`--writeback` 选项](reference/command_reference.md#juicefs-mount)，它会先把数据写入本机的缓存，然后再异步上传到对象存储，会比直接上传到对象存储快很多倍。
+请在挂载时加上 [`--writeback` 选项](reference/command_reference.md#mount)，它会先把数据写入本机的缓存，然后再异步上传到对象存储，会比直接上传到对象存储快很多倍。
 
 请查看[「客户端写缓存」](guide/cache_management.md#客户端写缓存)了解更多信息。
 
diff --git a/docs/zh_cn/getting-started/README.md b/docs/zh_cn/getting-started/README.md
index b60c1246..0fbd53b9 100644
--- a/docs/zh_cn/getting-started/README.md
+++ b/docs/zh_cn/getting-started/README.md
@@ -69,7 +69,7 @@ juicefs format sqlite3://myjfs.db myjfs
 
 ### 基本概念
 
-挂载文件系统使用客户端提供的 [`mount`](../reference/command_reference.md#juicefs-mount) 命令，一般格式为：
+挂载文件系统使用客户端提供的 [`mount`](../reference/command_reference.md#mount) 命令，一般格式为：
 
 ```shell
 juicefs mount [command options] META-URL MOUNTPOINT
diff --git a/docs/zh_cn/guide/cache_management.md b/docs/zh_cn/guide/cache_management.md
index fb10779d..62ab38e3 100644
--- a/docs/zh_cn/guide/cache_management.md
+++ b/docs/zh_cn/guide/cache_management.md
@@ -9,21 +9,22 @@ slug: /cache_management
 
 JuiceFS 提供包括元数据缓存、数据读写缓存等多种缓存机制。
 
-:::tip 我的场景真的需要缓存吗
-缓存能提升性能的前提是，你的应用需要反复读取同一批文件。如果你确定你的应用对数据是“读取一次，然后再也不需要”的访问模式（比如大数据的数据清洗常常就是这样），可以关闭缓存功能，省去缓存不断的建立，又白白淘汰的开销。
+:::tip 我的场景真的需要缓存吗？
+数据缓存可以有效地提高随机读的性能，对于像 Elasticsearch、ClickHouse 等对随机读性能要求更高的应用，建议将缓存路径设置在速度更快的存储介质上并分配更大的缓存空间。
+
+然而缓存能提升性能的前提是，你的应用需要反复读取同一批文件。如果你确定你的应用对数据是「读取一次，然后再也不需要」的访问模式（比如大数据的数据清洗常常就是这样），可以关闭缓存功能，省去缓存不断建立，又反复淘汰的开销。
 :::
 
-## 数据一致性
+## 数据一致性 {#consistency}
 
 JuiceFS 提供「关闭再打开（close-to-open）」一致性保证，即当两个及以上客户端同时读写相同的文件时，客户端 A 的修改在客户端 B 不一定能立即看到。但是，一旦这个文件在客户端 A 写入完成并关闭，之后在任何一个客户端重新打开该文件都可以保证能访问到最新写入的数据，不论是否在同一个节点。
 
-「关闭再打开」是 JuiceFS 提供的最低限度一致性保证，在某些情况下可能也不需要重新打开文件才能访问到最新写入的数据。例如多个应用程序使用同一个 JuiceFS 客户端访问相同的文件（文件变更立即可见），或者在不同节点上通过 `tail -f` 命令查看最新数据。
-
-## 读缓存机制
+「关闭再打开」是 JuiceFS 提供的最低限度一致性保证，在某些情况下可能也不需要重新打开文件才能访问到最新写入的数据：
 
-当访问 JuiceFS 中的文件时，会有多级缓存给经常访问的数据提供更好的性能，读请求会依次尝试内核分页缓存、JuiceFS 进程的预读缓冲区、本地磁盘缓存，当缓存中没找到对应数据时才会从对象存储读取，并且会异步写入各级缓存保证下一次访问的性能。
+* 多个应用程序使用同一个 JuiceFS 客户端访问相同的文件时，文件变更立即对所有进程可见。
+* 在不同节点上通过 `tail -f` 命令查看最新数据（需使用 Linux 系统）
 
-![](../images/juicefs-cache.png)
+至于对象存储，JuiceFS 将文件分成一个个数据块（默认 4MiB），赋予唯一 ID 并保存在对象存储上。文件的任何修改操作都将生成新的数据块，原有块保持不变，包括本地磁盘上的缓存数据。所以不用担心数据缓存的一致性问题，因为一旦文件被修改过了，JuiceFS 会从对象存储读取新的数据块，不会再读取文件中被覆盖的部分对应的数据块（之后会被删除掉）。
 
 ## 元数据缓存
 
@@ -31,7 +32,7 @@ JuiceFS 支持在内核和客户端内存（即 JuiceFS 进程）中缓存元数
 
 ### 内核元数据缓存 {#kernel-metadata-cache}
 
-内核中可以缓存三种元数据：**属性（attribute)**、**文件项（entry）**和**目录项（direntry）**，可以通过以下[挂载参数](../reference/command_reference.md#juicefs-mount)控制缓存时间：
+内核中可以缓存三种元数据：**属性（attribute)**、**文件项（entry）**和**目录项（direntry）**，可以通过以下[挂载参数](../reference/command_reference.md#mount)控制缓存时间：
 
 ```
 --attr-cache value       属性缓存时长，单位秒 (默认值: 1)
@@ -47,19 +48,17 @@ JuiceFS 默认会在内核中缓存属性、文件项和目录项，缓存时长
 此特性需要使用 0.15.2 及以上版本的 JuiceFS
 :::
 
-JuiceFS 客户端在 `open()` 操作即打开一个文件时，其文件属性（attribute）会被自动缓存在客户端内存中。如果在挂载文件系统时设置了 [`--open-cache`](../reference/command_reference.md#juicefs-mount) 选项且值大于 0，只要缓存尚未超时失效，随后执行的 `getattr()` 和 `open()` 操作会从内存缓存中立即返回结果。
+JuiceFS 客户端在 `open()` 操作即打开一个文件时，其文件属性（attribute）会被自动缓存在客户端内存中。如果在挂载文件系统时设置了 [`--open-cache`](../reference/command_reference.md#mount) 选项且值大于 0，只要缓存尚未超时失效，随后执行的 `getattr()` 和 `open()` 操作会从内存缓存中立即返回结果。
 
-执行 `read()` 操作即读取一个文件时，文件的 chunk 和 slice 信息会被自动缓存在客户端内存。在缓存有效期内，再次读取 chunk 会从内存缓存中立即返回 slice 信息。
+执行 `read()` 操作即读取一个文件时，文件的 chunk 和 slice 信息会被自动缓存在客户端内存。在缓存有效期内，再次读取 chunk 会从内存缓存中立即返回 slice 信息（查阅[「JuiceFS 如何存储文件」](../introduction/architecture.md#如何存储文件)以了解 chunk 和 slice 是什么）。
 
-:::tip 提示
-您可以查阅[「JuiceFS 如何存储文件」](../reference/how_juicefs_store_files.md)了解 chunk 和 slice 是什么。
-:::
-
-默认情况下，对于一个元数据已经被缓存在内存的文件，超过 1 小时没有被任何进程访问，其所有元数据缓存会被自动删除。
+为保强一致性，`--open-cache` 默认关闭，每次打开文件都需直接访问元数据引擎。但如果文件很少发生修改，或者只读场景下（例如 AI 模型训练），则推荐根据情况设置 `--open-cache`，进一步提高读性能。
 
 ## 数据缓存
 
-JuiceFS 对数据也提供多种缓存机制来提高性能，包括内核中的页缓存和客户端所在机器的本地缓存，以及客户端自身的内存读写缓冲区。
+当访问 JuiceFS 中的文件时，会有多级缓存给经常访问的数据提供更好的性能，读请求会依次尝试内核分页缓存、JuiceFS 进程的预读缓冲区、本地磁盘缓存，当缓存中没找到对应数据时才会从对象存储读取，并且会异步写入各级缓存保证下一次访问的性能。
+
+![](../images/juicefs-cache.png)
 
 ### 读写缓冲区 {#buffer-size}
 
@@ -79,41 +78,39 @@ JuiceFS 对数据也提供多种缓存机制来提高性能，包括内核中的
 
 得益于内核缓存，重复读取 JuiceFS 中相同文件的速度会非常快，延时可低至微秒，吞吐量可以到每秒数 GiB。
 
-JuiceFS 客户端目前还未默认启用内核的写入缓存功能，从 [Linux 内核 3.15](https://github.com/torvalds/linux/commit/4d99ff8f12e) 开始，FUSE 支持[「writeback-cache 模式」](https://www.kernel.org/doc/Documentation/filesystems/fuse-io.txt)，这意味着可以非常快速地完成 `write()` 系统调用。你可以在[挂载文件系统](../reference/command_reference.md#juicefs-mount)时设置 [`-o writeback_cache`](../reference/fuse_mount_options.md#writeback_cache) 选项开启 writeback-cache 模式。当需要频繁写入非常小的数据（如 100 字节左右）时，建议启用此挂载选项。
+JuiceFS 客户端目前还未默认启用内核的写入缓存功能，从 [Linux 内核 3.15](https://github.com/torvalds/linux/commit/4d99ff8f12e) 开始，FUSE 支持[「writeback-cache 模式」](https://www.kernel.org/doc/Documentation/filesystems/fuse-io.txt)，这意味着可以非常快速地完成 `write()` 系统调用。你可以在[挂载文件系统](../reference/command_reference.md#mount)时设置 [`-o writeback_cache`](../reference/fuse_mount_options.md#writeback_cache) 选项开启 writeback-cache 模式。当需要频繁写入非常小的数据（如 100 字节左右）时，建议启用此挂载选项。
 
-### 客户端读缓存
+### 客户端读缓存 {#client-read-cache}
 
-JuiceFS 客户端会根据读取模式自动预读数据放入缓存，从而提高顺序读的性能。默认情况下，会在读取数据时并发预读 1 个 block 缓存在本地。本地缓存可以设置在基于机械硬盘、SSD 或内存的任意本地文件系统。
+本地缓存可以设置在基于机械硬盘、SSD 或内存的任意本地文件系统。
 
-:::tip 提示
-您可以查阅[「JuiceFS 如何存储文件」](../reference/how_juicefs_store_files.md)了解 block 是什么。
-:::
+JuiceFS 客户端会把从对象存储下载的数据，以及新上传的小于 1 个 block 大小的数据写入到缓存目录中，不做压缩和加密。
 
-本地缓存可以在[挂载文件系统](../reference/command_reference.md#juicefs-mount)时通过以下选项调整：
+以下是缓存配置的关键参数（完整参数列表见 [`juicefs mount`](../reference/command_reference.md#mount)）：
 
-```
---prefetch value          并发预读 N 个块 (默认: 1)
---cache-dir value         本地缓存目录路径；使用冒号隔离多个路径 (默认: "$HOME/.juicefs/cache" 或 "/var/jfsCache")
---cache-size value        缓存对象的总大小；单位为 MiB (默认: 102400)
---free-space-ratio value  最小剩余空间比例 (默认: 0.1)
---cache-partial-only      仅缓存随机小块读 (默认: false)
-```
+* `--prefetch`
 
-特别地，如果希望将 JuiceFS 的本地缓存存储在内存中有两种方式，一种是将 `--cache-dir` 设置为 `memory`，另一种是将其设置为 `/dev/shm/<cache-dir>`。这两种方式的区别是前者在重新挂载 JuiceFS 文件系统之后缓存数据就清空了，而后者还会保留，性能上两者没有太大差别。
+  并发预读 N 个块（默认 1）。所谓预读（prefetch），就是随机读取文件任意一小段，都会触发对应的整个对象存储块异步完整下载。预读往往能改善随机读性能，但如果你的场景的文件访问模式无法利用到预读数据（比如 offset 跨度极大的大文件随机访问），预读会带来比较明显的读放大，可以考虑设为 0 以禁用预读特性。
 
-JuiceFS 客户端会尽可能快地把从对象存储下载的数据（包括新上传的小于 1 个 block 大小的数据）写入到缓存目录中，不做压缩和加密。**因为 JuiceFS 会为所有写入对象存储的 block 对象生成唯一的名字，而且所有 block 对象不会被修改，因此当文件内容更新时，不用担心缓存的数据失效问题。**
+  JuiceFS 还内置着另一种类似的预读机制：在顺序读时，会提前下载临近的对象存储块，这在 JuiceFS 内称为 readahead 机制，能有效提高顺序读性能。Readahead 的并发度受[「读写缓冲区」](#buffer-size)的大小影响，读写缓冲区越大并发度越高。
 
-数据缓存可以有效地提高随机读的性能，对于像 Elasticsearch、ClickHouse 等对随机读性能要求更高的应用，建议将缓存路径设置在速度更快的存储介质上并分配更大的缓存空间。
+* `--cache-dir`
 
-#### 缓存生命周期
+  缓存目录，默认为 `/var/jfsCache` 或 `$HOME/.juicefs/cache`。请阅读[「缓存位置」](#cache-dir)了解更多信息。
 
-在缓存使用空间到达上限（即缓存大小大于等于 `--cache-size`）或磁盘将被存满（即磁盘可用空间比例小于 `--free-space-ratio`）时，JuiceFS 客户端会自动清理缓存。JuiceFS 客户端会采用类 LRU 的算法对缓存进行清理，即尽量清理较早且较少使用的缓存。
+  如果急需释放磁盘空间，你可以手动清理缓存目录下的文件，缓存路径通常为 `<cache-dir>/<UUID>/raw/`。
 
-JuiceFS 默认会分配 100GiB 的缓存空间，但这并不代表你要使用超过该容量的磁盘。这个值代表的是在磁盘容量允许的情况下 JuiceFS 客户端可能使用的最大容量。当磁盘的剩余空间不足 100GiB 时，默认情况下会保证剩余空间不低于 10%。
+* `--cache-size` 与 `--free-space-ratio`
 
-例如，你将 `--cache-dir` 设置在了容量为 50GiB 的分区，此时如果 `--cache-size` 设置为 100GiB，JuiceFS 的缓存容量始终保持在 45GiB 左右，即保留该分区有 10% 以上的剩余空间。
+  缓存空间大小（单位 MiB，默认 102400）与缓存盘的最少剩余空间占比（默认 0.1）。这两个参数任意一个达到阈值，均会自动触发缓存淘汰，使用的是类似于 LRU 的策略，即尽量清理较早且较少使用的缓存。
 
-对于总量相同的缓存数据，在不同的文件系统上会有不同的容量计算规则。JuiceFS 目前是通过累加所有被缓存对象的大小（最小 4KiB）来估算得到的，这与 `du` 命令得到的数值并不完全一致。
+  实际缓存数据占用空间大小可能会略微超过设置值，这是因为对同样一批缓存数据，很难精确计算它们在不同的本地文件系统上所占用的存储空间，JuiceFS 累加所有被缓存对象大小时会按照 4KiB 的最小值来计算，因此与 `du` 得到的数值往往不一致。
+
+* `--cache-partial-only`
+
+  只缓存小文件和随机读的部分，适合对象存储的吞吐比缓存盘还高的情况。默认为 false。
+
+  读一般有两种模式，连续读和随机读。对于连续读，一般需要较高的吞吐。对于随机读，一般需要较低的时延。当本地磁盘的吞吐反而比不上对象存储时，可以考虑启用 `--cache-partial-only`，这样一来，连续读虽然会将一整个对象块读取下来，但并不会被缓存。而随机读（例如读 Parquet 或者 ORC 文件的 footer）所读取的字节数比较小，不会读取整个对象块，此类读取就会被缓存。充分地利用了本地磁盘低时延和网络高吞吐的优势。
 
 ### 客户端写缓存 {#writeback}
 
@@ -181,7 +178,7 @@ juicefs warmup /mnt/jfs/dataset-1
 juicefs warmup -f warm.txt
 ```
 
-## 缓存位置
+## 缓存位置 {#cache-dir}
 
 取决于操作系统，JuiceFS 的默认缓存路径如下：
 
@@ -229,10 +226,14 @@ sudo mount -o size=32000M -o remount /dev/shm
 juicefs mount --cache-dir /dev/shm/jfscache redis://127.0.0.1:6379/1 /mnt/myjfs
 ```
 
+除此之外，还可以将 `--cache-dir` 选项设置为 `memory` 来直接使用进程内存作为缓存，与 `/dev/shm` 相比，好处是简单不依赖外部设备，但相应地也无法持久化，一般在测试评估的时候使用。
+
 ### 共享目录
 
 SMB、NFS 等共享目录也可以用作 JuiceFS 的缓存，对于局域网有多个设备挂载了相同 JuiceFS 文件系统的情况，将局域网中的共享目录作为缓存路径，可以有效缓解多个设备重复预热缓存的带宽压力。
 
+但是需要特别注意，通常情况下当缓存目录所在的文件系统无法正常工作时 JuiceFS 客户都能立刻返回错误，并降级成直接访问对象存储。如果共享目录异常时体现为读操作卡死（如某些内核态的网络文件系统），那么 JuiceFS 也会随之一起卡住，这就要求你对共享目录底层的文件系统行为进行调优，做到快速失败。
+
 以 SMB/CIFS 共享为例，使用 `cifs-utils` 包提供的工具挂载局域网中的共享目录：
 
 ```shell
@@ -247,12 +248,12 @@ sudo juicefs mount --cache-dir /mnt/jfscache redis://127.0.0.1:6379/1 /mnt/myjfs
 
 ### 多缓存目录
 
-JuiceFS 支持同时设置多个缓存目录，从而解决缓存空间不足的问题，使用 `:`（Linux、macOS）或 `;`（Windows）分割多个路径，例如：
+JuiceFS 支持同时设置多个缓存目录，从而解决缓存空间不足的问题，使用 `:`（Linux、macOS）或 `;`（Windows）字符分隔多个路径，例如：
 
 ```shell
 sudo juicefs mount --cache-dir ~/jfscache:/mnt/jfscache:/dev/shm/jfscache redis://127.0.0.1:6379/1 /mnt/myjfs
 ```
 
-设置了多个缓存路径时，客户端会采用 hash 策略向各个缓存路径中均匀地写入数据。当设置了多个缓存目录时，`--cache-size` 选项表示所有缓存目录中的数据总大小。
+当设置了多个缓存目录，或者使用多块设备作为缓存盘，`--cache-size` 选项表示所有缓存目录中的数据总大小。客户端会采用哈希策略向各个缓存路径中均匀地写入数据，无法对多块容量或性能不同的缓存盘进行特殊调优。
 
-建议不同缓存目录的可用空间保持一致，否则可能造成不能充分利用某个缓存目录空间的情况。例如 `--cache-dir` 为 `/data1:/data2`，其中 `/data1` 的可用空间为 1GiB，`/data2` 的可用空间为 2GiB，`--cache-size` 为 3GiB，`--free-space-ratio` 为 0.1。因为缓存的写入策略是均匀写入，所以分配给每个缓存目录的最大空间是 `3GiB / 2 = 1.5GiB`，会造成 `/data2` 目录的缓存空间最大为 1.5GiB，而不是 `2GiB * 0.9 = 1.8GiB`。
+因此建议不同缓存目录／缓存盘的可用空间保持一致，否则可能造成不能充分利用某个缓存目录空间的情况。例如 `--cache-dir` 为 `/data1:/data2`，其中 `/data1` 的可用空间为 1GiB，`/data2` 的可用空间为 2GiB，`--cache-size` 为 3GiB，`--free-space-ratio` 为 0.1。因为缓存的写入策略是均匀写入，所以分配给每个缓存目录的最大空间是 `3GiB / 2 = 1.5GiB`，会造成 `/data2` 目录的缓存空间最大为 1.5GiB，而不是 `2GiB * 0.9 = 1.8GiB`。
diff --git a/docs/zh_cn/guide/mount_at_boot.md b/docs/zh_cn/guide/mount_at_boot.md
index cb3d1553..2d8498be 100644
--- a/docs/zh_cn/guide/mount_at_boot.md
+++ b/docs/zh_cn/guide/mount_at_boot.md
@@ -16,7 +16,7 @@ slug: /mount_juicefs_at_boot_time
 <META-URL>    <MOUNTPOINT>       juicefs     _netdev[,<MOUNT-OPTIONS>]     0  0
 ```
 
-`<META-URL>` 的格式请参考[「如何设置元数据引擎」](how_to_set_up_metadata_engine.md)文档，比如 `redis://localhost:6379/1`。然后用你希望 JuiceFS 挂载的路径替换 `<MOUNTPOINT>` ，比如 `/jfs`。如果你想要设置[挂载选项](../reference/command_reference.md#juicefs-mount)，用逗号分隔选项列表并替换 `[,<MOUNT-OPTIONS>]` 。下面是一个示例：
+`<META-URL>` 的格式请参考[「如何设置元数据引擎」](how_to_set_up_metadata_engine.md)文档，比如 `redis://localhost:6379/1`。然后用你希望 JuiceFS 挂载的路径替换 `<MOUNTPOINT>` ，比如 `/jfs`。如果你想要设置[挂载选项](../reference/command_reference.md#mount)，用逗号分隔选项列表并替换 `[,<MOUNT-OPTIONS>]` 。下面是一个示例：
 
 ```
 redis://localhost:6379/1    /jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=204800     0  0
diff --git a/docs/zh_cn/reference/fuse_mount_options.md b/docs/zh_cn/reference/fuse_mount_options.md
index 945bafbb..48c5d655 100644
--- a/docs/zh_cn/reference/fuse_mount_options.md
+++ b/docs/zh_cn/reference/fuse_mount_options.md
@@ -5,7 +5,7 @@ slug: /fuse_mount_options
 ---
 # FUSE 挂载选项
 
-本指南列出了重要的 FUSE 挂载选项。当执行 [`juicefs mount`](../reference/command_reference.md#juicefs-mount) 命令时，这些安装选项由 `-o` 选项指定，多个选项使用半角逗号分隔。例如：
+本指南列出了重要的 FUSE 挂载选项。当执行 [`juicefs mount`](../reference/command_reference.md#mount) 命令时，这些安装选项由 `-o` 选项指定，多个选项使用半角逗号分隔。例如：
 
 ```bash
 juicefs mount -d -o allow_other,writeback_cache localhost ~/jfs
diff --git a/docs/zh_cn/tutorials/aws.md b/docs/zh_cn/tutorials/aws.md
index 6243fbb1..1b6a3e7b 100644
--- a/docs/zh_cn/tutorials/aws.md
+++ b/docs/zh_cn/tutorials/aws.md
@@ -183,7 +183,7 @@ sudo juicefs mount -d redis://[<redis-username>]:<redis-password>@<redis-url>:63
 
 > **注意**：挂载文件系统时，只需填写数据库地址，不需要文件系统名称。默认的缓存路径为 `/var/jfsCache`，请确保当前用户有足够的读写权限。
 
-你可以通过调整[挂载参数](../reference/command_reference.md#juicefs-mount)，对 JuiceFS 进行优化，比如可以通过 `--cache-size` 将缓存修改为 20GB：
+你可以通过调整[挂载参数](../reference/command_reference.md#mount)，对 JuiceFS 进行优化，比如可以通过 `--cache-size` 将缓存修改为 20GB：
 
 ```shell
 sudo juicefs mount --cache-size 20480 -d redis://herald-demo.abcdefg.0001.apse1.cache.amazonaws.com:6379/1  /mnt/jfs
diff --git a/pkg/meta/base.go b/pkg/meta/base.go
index e4d6d2ed..4cebfc1a 100644
--- a/pkg/meta/base.go
+++ b/pkg/meta/base.go
@@ -22,6 +22,7 @@ import (
 	"fmt"
 	"path"
 	"path/filepath"
+	"reflect"
 	"runtime"
 	"sort"
 	"strings"
@@ -107,7 +108,7 @@ type baseMeta struct {
 	sync.Mutex
 	addr string
 	conf *Config
-	fmt  Format
+	fmt  *Format
 
 	root         Ino
 	txlocks      [nlocks]sync.Mutex // Pessimistic locks to reduce conflict
@@ -119,6 +120,7 @@ type baseMeta struct {
 	maxDeleting  chan struct{}
 	symlinks     *sync.Map
 	msgCallbacks *msgCallbacks
+	reloadCb     []func(*Format)
 	umounting    bool
 	sesMu        sync.Mutex
 
@@ -257,19 +259,21 @@ func (m *baseMeta) Load(checkVersion bool) (*Format, error) {
 	if err != nil {
 		return nil, err
 	}
-	if err = json.Unmarshal(body, &m.fmt); err != nil {
+	var format Format
+	if err = json.Unmarshal(body, &format); err != nil {
 		return nil, fmt.Errorf("json: %s", err)
 	}
 	if checkVersion {
-		if err = m.fmt.CheckVersion(); err != nil {
+		if err = format.CheckVersion(); err != nil {
 			return nil, fmt.Errorf("check version: %s", err)
 		}
 	}
-	return &m.fmt, nil
+	m.fmt = &format
+	return m.fmt, nil
 }
 
 func (m *baseMeta) NewSession() error {
-	go m.refreshUsage()
+	go m.refresh()
 	if m.conf.ReadOnly {
 		logger.Infof("Create read-only session OK with version: %s", version.Version())
 		return nil
@@ -291,7 +295,6 @@ func (m *baseMeta) NewSession() error {
 	}
 	logger.Infof("Create session %d OK with version: %s", m.sid, version.Version())
 
-	go m.refreshSession()
 	if !m.conf.NoBGJob {
 		go m.cleanupDeletedFiles()
 		go m.cleanupSlices()
@@ -304,7 +307,13 @@ func (m *baseMeta) expireTime() int64 {
 	return time.Now().Add(5 * m.conf.Heartbeat).Unix()
 }
 
-func (m *baseMeta) refreshSession() {
+func (m *baseMeta) OnReload(fn func(f *Format)) {
+	m.msgCallbacks.Lock()
+	defer m.msgCallbacks.Unlock()
+	m.reloadCb = append(m.reloadCb, fn)
+}
+
+func (m *baseMeta) refresh() {
 	for {
 		utils.SleepWithJitter(m.conf.Heartbeat)
 		m.sesMu.Lock()
@@ -312,18 +321,39 @@ func (m *baseMeta) refreshSession() {
 			m.sesMu.Unlock()
 			return
 		}
-		m.en.doRefreshSession()
+		if !m.conf.ReadOnly {
+			m.en.doRefreshSession()
+		}
 		m.sesMu.Unlock()
 
-		old := m.fmt.UUID
+		old := m.fmt
 		if _, err := m.Load(false); err != nil {
 			logger.Warnf("reload setting: %s", err)
 		} else if m.fmt.MetaVersion > MaxVersion {
 			logger.Fatalf("incompatible metadata version %d > max version %d", m.fmt.MetaVersion, MaxVersion)
-		} else if m.fmt.UUID != old {
+		} else if m.fmt.UUID != old.UUID {
 			logger.Fatalf("UUID changed from %s to %s", old, m.fmt.UUID)
+		} else if !reflect.DeepEqual(m.fmt, old) {
+			m.msgCallbacks.Lock()
+			cbs := m.reloadCb
+			m.msgCallbacks.Unlock()
+			for _, cb := range cbs {
+				cb(m.fmt)
+			}
+		}
+
+		if v, err := m.en.getCounter(usedSpace); err == nil {
+			atomic.StoreInt64(&m.usedSpace, v)
+		} else {
+			logger.Warnf("Get counter %s: %s", usedSpace, err)
+		}
+		if v, err := m.en.getCounter(totalInodes); err == nil {
+			atomic.StoreInt64(&m.usedInodes, v)
+		} else {
+			logger.Warnf("Get counter %s: %s", totalInodes, err)
 		}
-		if m.conf.NoBGJob {
+
+		if m.conf.ReadOnly || m.conf.NoBGJob {
 			continue
 		}
 		if ok, err := m.en.setIfSmall("lastCleanupSessions", time.Now().Unix(), int64(m.conf.Heartbeat/time.Second)); err != nil {
@@ -361,22 +391,6 @@ func (m *baseMeta) CloseSession() error {
 	return nil
 }
 
-func (m *baseMeta) refreshUsage() {
-	for {
-		if v, err := m.en.getCounter(usedSpace); err == nil {
-			atomic.StoreInt64(&m.usedSpace, v)
-		} else {
-			logger.Warnf("Get counter %s: %s", usedSpace, err)
-		}
-		if v, err := m.en.getCounter(totalInodes); err == nil {
-			atomic.StoreInt64(&m.usedInodes, v)
-		} else {
-			logger.Warnf("Get counter %s: %s", totalInodes, err)
-		}
-		utils.SleepWithJitter(time.Second * 10)
-	}
-}
-
 func (m *baseMeta) checkQuota(size, inodes int64) bool {
 	if size > 0 && m.fmt.Capacity > 0 && atomic.LoadInt64(&m.usedSpace)+atomic.LoadInt64(&m.newSpace)+size > int64(m.fmt.Capacity) {
 		return true
diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go
index 7b7146a8..09926e9b 100644
--- a/pkg/meta/interface.go
+++ b/pkg/meta/interface.go
@@ -252,7 +252,7 @@ type Meta interface {
 	// Name of database
 	Name() string
 	// Init is used to initialize a meta service.
-	Init(format Format, force bool) error
+	Init(format *Format, force bool) error
 	// Shutdown close current database connections.
 	Shutdown() error
 	// Reset cleans up all metadata, VERY DANGEROUS!
@@ -358,6 +358,8 @@ type Meta interface {
 
 	// OnMsg add a callback for the given message type.
 	OnMsg(mtype uint32, cb MsgCallback)
+	// OnReload register a callback for any change founded after reloaded.
+	OnReload(func(new *Format))
 
 	// Dump the tree under root, which may be modified by checkRoot
 	DumpMeta(w io.Writer, root Ino, keepSecret bool) error
diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go
index 82b91dac..594dcb76 100644
--- a/pkg/meta/redis.go
+++ b/pkg/meta/redis.go
@@ -218,7 +218,7 @@ func (m *redisMeta) Name() string {
 	return "redis"
 }
 
-func (m *redisMeta) Init(format Format, force bool) error {
+func (m *redisMeta) Init(format *Format, force bool) error {
 	ctx := Background
 	body, err := m.rdb.Get(ctx, m.setting()).Bytes()
 	if err != nil && err != redis.Nil {
@@ -3301,7 +3301,7 @@ func (m *redisMeta) DumpMeta(w io.Writer, root Ino, keepSecret bool) (err error)
 	}
 
 	dm := &DumpedMeta{
-		Setting: m.fmt,
+		Setting: *m.fmt,
 		Counters: &DumpedCounters{
 			UsedSpace:   cs[0],
 			UsedInodes:  cs[1],
diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go
index 4f42048c..09f8b801 100644
--- a/pkg/meta/sql.go
+++ b/pkg/meta/sql.go
@@ -255,7 +255,7 @@ func (m *dbMeta) syncTable(beans ...interface{}) error {
 	return err
 }
 
-func (m *dbMeta) Init(format Format, force bool) error {
+func (m *dbMeta) Init(format *Format, force bool) error {
 	if err := m.syncTable(new(setting), new(counter)); err != nil {
 		return fmt.Errorf("create table setting, counter: %s", err)
 	}
@@ -3089,7 +3089,7 @@ func (m *dbMeta) DumpMeta(w io.Writer, root Ino, keepSecret bool) (err error) {
 		}
 
 		dm := DumpedMeta{
-			Setting:   m.fmt,
+			Setting:   *m.fmt,
 			Counters:  counters,
 			Sustained: sessions,
 			DelFiles:  dels,
diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go
index 6c5add92..10fa01c3 100644
--- a/pkg/meta/tkv.go
+++ b/pkg/meta/tkv.go
@@ -328,7 +328,7 @@ func (m *kvMeta) scanValues(prefix []byte, limit int, filter func(k, v []byte) b
 	return values, err
 }
 
-func (m *kvMeta) Init(format Format, force bool) error {
+func (m *kvMeta) Init(format *Format, force bool) error {
 	body, err := m.get(m.fmtKey("setting"))
 	if err != nil {
 		return err
@@ -2616,7 +2616,7 @@ func (m *kvMeta) DumpMeta(w io.Writer, root Ino, keepSecret bool) (err error) {
 	}
 
 	dm := DumpedMeta{
-		Setting: m.fmt,
+		Setting: *m.fmt,
 		Counters: &DumpedCounters{
 			UsedSpace:   cs[0],
 			UsedInodes:  cs[1],
diff --git a/sdk/java/libjfs/main.go b/sdk/java/libjfs/main.go
index de405e56..8ca7548d 100644
--- a/sdk/java/libjfs/main.go
+++ b/sdk/java/libjfs/main.go
@@ -425,14 +425,10 @@ func jfs_init(cname, jsonConf, user, group, superuser, supergroup *C.char) uintp
 		if jConf.Bucket != "" {
 			format.Bucket = jConf.Bucket
 		}
-		blob, err := cmd.NewReloadableStorage(format, func() (*meta.Format, error) {
-			format, err := m.Load(true)
-			if err == nil {
-				if jConf.Bucket != "" {
-					format.Bucket = jConf.Bucket
-				}
+		blob, err := cmd.NewReloadableStorage(format, m, func(f *meta.Format) {
+			if jConf.Bucket != "" {
+				format.Bucket = jConf.Bucket
 			}
-			return format, err
 		})
 		if err != nil {
 			logger.Errorf("object storage: %s", err)


Test patch:
diff --git a/cmd/mdtest.go b/cmd/mdtest.go
index 05a0a737..af1e67bc 100644
--- a/cmd/mdtest.go
+++ b/cmd/mdtest.go
@@ -205,9 +205,7 @@ func initForMdtest(c *cli.Context, mp string, metaUrl string) *fs.FileSystem {
 	}
 	registerer, registry := wrapRegister(mp, format.Name)
 
-	blob, err := NewReloadableStorage(format, func() (*meta.Format, error) {
-		return getFormat(c, m)
-	})
+	blob, err := NewReloadableStorage(format, m, updateFormat(c))
 	if err != nil {
 		logger.Fatalf("object storage: %s", err)
 	}
diff --git a/cmd/mount_test.go b/cmd/mount_test.go
index 310e325f..6de3c422 100644
--- a/cmd/mount_test.go
+++ b/cmd/mount_test.go
@@ -47,7 +47,7 @@ func Test_exposeMetrics(t *testing.T) {
 		Convey("Test_exposeMetrics", func() {
 			addr := "redis://127.0.0.1:6379/12"
 			client := meta.NewClient(addr, &meta.Config{})
-			format := meta.Format{
+			format := &meta.Format{
 				Name:      "test",
 				BlockSize: 4096,
 				Capacity:  1 << 30,
diff --git a/pkg/fs/fs_test.go b/pkg/fs/fs_test.go
index 2d3725d8..74fca021 100644
--- a/pkg/fs/fs_test.go
+++ b/pkg/fs/fs_test.go
@@ -269,7 +269,7 @@ func createTestFS(t *testing.T) *FileSystem {
 	checkAccessFile = time.Millisecond
 	rotateAccessLog = 500
 	m := meta.NewClient("memkv://", &meta.Config{})
-	format := meta.Format{
+	format := &meta.Format{
 		Name:      "test",
 		BlockSize: 4096,
 		Capacity:  1 << 30,
diff --git a/pkg/fuse/fuse_test.go b/pkg/fuse/fuse_test.go
index f4fbaa91..84214272 100644
--- a/pkg/fuse/fuse_test.go
+++ b/pkg/fuse/fuse_test.go
@@ -46,7 +46,7 @@ import (
 
 func format(url string) {
 	m := meta.NewClient(url, &meta.Config{})
-	format := meta.Format{
+	format := &meta.Format{
 		Name:      "test",
 		UUID:      uuid.New().String(),
 		Storage:   "file",
diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go
index 1ca41da8..02ba58bf 100644
--- a/pkg/meta/base_test.go
+++ b/pkg/meta/base_test.go
@@ -91,10 +91,10 @@ func testMetaClient(t *testing.T, m Meta) {
 		t.Fatalf("getattr root: %s", st)
 	}
 
-	if err := m.Init(Format{Name: "test"}, true); err != nil {
+	if err := m.Init(&Format{Name: "test"}, true); err != nil {
 		t.Fatalf("initialize failed: %s", err)
 	}
-	if err := m.Init(Format{Name: "test2"}, false); err == nil { // not allowed
+	if err := m.Init(&Format{Name: "test2"}, false); err == nil { // not allowed
 		t.Fatalf("change name without --force is not allowed")
 	}
 	format, err := m.Load(true)
@@ -475,7 +475,7 @@ func testMetaClient(t *testing.T, m Meta) {
 	}
 	format.Capacity = 1 << 20
 	format.Inodes = 100
-	if err = m.Init(*format, false); err != nil {
+	if err = m.Init(format, false); err != nil {
 		t.Fatalf("set quota failed: %s", err)
 	}
 	if st := m.StatFS(ctx, &totalspace, &availspace, &iused, &iavail); st != 0 {
@@ -530,7 +530,7 @@ func testMetaClient(t *testing.T, m Meta) {
 }
 
 func testStickyBit(t *testing.T, m Meta) {
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 	ctx := Background
 	var sticky, normal, inode Ino
 	var attr = &Attr{}
@@ -597,7 +597,7 @@ func testStickyBit(t *testing.T, m Meta) {
 }
 
 func testLocks(t *testing.T, m Meta) {
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 	ctx := Background
 	var inode Ino
 	var attr = &Attr{}
@@ -734,7 +734,7 @@ func testLocks(t *testing.T, m Meta) {
 }
 
 func testRemove(t *testing.T, m Meta) {
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 	ctx := Background
 	var inode, parent Ino
 	var attr = &Attr{}
@@ -776,7 +776,7 @@ func testRemove(t *testing.T, m Meta) {
 }
 
 func testCaseIncensi(t *testing.T, m Meta) {
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 	ctx := Background
 	var inode Ino
 	var attr = &Attr{}
@@ -825,9 +825,9 @@ type compactor interface {
 
 func testCompaction(t *testing.T, m Meta, trash bool) {
 	if trash {
-		_ = m.Init(Format{Name: "test", TrashDays: 1}, false)
+		_ = m.Init(&Format{Name: "test", TrashDays: 1}, false)
 	} else {
-		_ = m.Init(Format{Name: "test"}, false)
+		_ = m.Init(&Format{Name: "test"}, false)
 	}
 	var l sync.Mutex
 	deleted := make(map[uint64]int)
@@ -940,7 +940,7 @@ func testConcurrentWrite(t *testing.T, m Meta) {
 	m.OnMsg(CompactChunk, func(args ...interface{}) error {
 		return nil
 	})
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 
 	ctx := Background
 	var inode Ino
@@ -982,7 +982,7 @@ func testTruncateAndDelete(t *testing.T, m Meta) {
 	// remove quota
 	format, _ := m.Load(false)
 	format.Capacity = 0
-	_ = m.Init(*format, false)
+	_ = m.Init(format, false)
 
 	ctx := Background
 	var inode Ino
@@ -1043,7 +1043,7 @@ func testCopyFileRange(t *testing.T, m Meta) {
 	m.OnMsg(DeleteSlice, func(args ...interface{}) error {
 		return nil
 	})
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 
 	ctx := Background
 	var iin, iout Ino
@@ -1093,7 +1093,7 @@ func testCopyFileRange(t *testing.T, m Meta) {
 }
 
 func testCloseSession(t *testing.T, m Meta) {
-	_ = m.Init(Format{Name: "test"}, false)
+	_ = m.Init(&Format{Name: "test"}, false)
 	if err := m.NewSession(); err != nil {
 		t.Fatalf("new session: %s", err)
 	}
@@ -1152,7 +1152,7 @@ func testCloseSession(t *testing.T, m Meta) {
 }
 
 func testTrash(t *testing.T, m Meta) {
-	if err := m.Init(Format{Name: "test", TrashDays: 1}, false); err != nil {
+	if err := m.Init(&Format{Name: "test", TrashDays: 1}, false); err != nil {
 		t.Fatalf("init: %s", err)
 	}
 	ctx := Background
@@ -1248,7 +1248,7 @@ func testTrash(t *testing.T, m Meta) {
 }
 
 func testParents(t *testing.T, m Meta) {
-	if err := m.Init(Format{Name: "test"}, false); err != nil {
+	if err := m.Init(&Format{Name: "test"}, false); err != nil {
 		t.Fatalf("init: %s", err)
 	}
 	ctx := Background
@@ -1382,7 +1382,7 @@ func testConcurrentDir(t *testing.T, m Meta) {
 	format, err := m.Load(false)
 	format.Capacity = 0
 	format.Inodes = 0
-	if err = m.Init(*format, false); err != nil {
+	if err = m.Init(format, false); err != nil {
 		t.Fatalf("set quota failed: %s", err)
 	}
 	for i := 0; i < 100; i++ {
diff --git a/pkg/meta/benchmarks_test.go b/pkg/meta/benchmarks_test.go
index c88c757d..66f1f699 100644
--- a/pkg/meta/benchmarks_test.go
+++ b/pkg/meta/benchmarks_test.go
@@ -621,7 +621,7 @@ func benchmarkData(b *testing.B, m Meta) {
 }
 
 func benchmarkAll(b *testing.B, m Meta) {
-	_ = m.Init(Format{Name: "benchmarkAll"}, true)
+	_ = m.Init(&Format{Name: "benchmarkAll"}, true)
 	_ = m.NewSession()
 	benchmarkDir(b, m)
 	benchmarkFile(b, m)
diff --git a/pkg/vfs/vfs_test.go b/pkg/vfs/vfs_test.go
index 1825458b..de31c07b 100644
--- a/pkg/vfs/vfs_test.go
+++ b/pkg/vfs/vfs_test.go
@@ -44,7 +44,7 @@ func createTestVFS() (*VFS, object.ObjectStorage) {
 		MountPoint: mp,
 	}
 	m := meta.NewClient("memkv://", metaConf)
-	format := meta.Format{
+	format := &meta.Format{
 		Name:        "test",
 		UUID:        uuid.New().String(),
 		Storage:     "mem",
@@ -57,7 +57,7 @@ func createTestVFS() (*VFS, object.ObjectStorage) {
 	}
 	conf := &Config{
 		Meta:    metaConf,
-		Format:  &format,
+		Format:  format,
 		Version: "Juicefs",
 		Chunk: &chunk.Config{
 			BlockSize:  format.BlockSize * 1024,