Repository: juicedata/juicefs
Pull request #2397 (2022-07-25 08:10:33)

refactor: distinguish Slice from Chunk

codecov-commenter: # [Codecov](https://codecov.io/gh/juicedata/juicefs/pull/2397?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata) Report
> Merging [#2397](https://codecov.io/gh/juicedata/juicefs/pull/2397?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata) (55edf77) into [main](https://codecov.io/gh/juicedata/juicefs/commit/43fe7289856438895ca3682741cef1e328315b86?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata) (43fe728) will **decrease** coverage by `0.05%`.
> The diff coverage is `78.94%`.

```diff
@@            Coverage Diff             @@
##             main    #2397      +/-   ##
==========================================
- Coverage   61.60%   61.54%   -0.06%     
==========================================
  Files         137      137              
  Lines       25503    25530      +27     
==========================================
+ Hits        15711    15713       +2     
- Misses       7954     7972      +18     
- Partials     1838     1845       +7     
```


| [Impacted Files](https://codecov.io/gh/juicedata/juicefs/pull/2397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata) | Coverage Δ | |
|---|---|---|
| [cmd/mdtest.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-Y21kL21kdGVzdC5nbw==) | `28.48% <0.00%> (ø)` | |
| [pkg/meta/interface.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-cGtnL21ldGEvaW50ZXJmYWNlLmdv) | `51.61% <ø> (ø)` | |
| [pkg/object/hdfs.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-cGtnL29iamVjdC9oZGZzLmdv) | `66.66% <ø> (-2.12%)` | :arrow_down: |
| [pkg/vfs/internal.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-cGtnL3Zmcy9pbnRlcm5hbC5nbw==) | `60.18% <0.00%> (ø)` | |
| [cmd/fsck.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-Y21kL2ZzY2suZ28=) | `65.68% <33.33%> (ø)` | |
| [pkg/object/redis.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-cGtnL29iamVjdC9yZWRpcy5nbw==) | `50.95% <48.00%> (-1.50%)` | :arrow_down: |
| [cmd/gc.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-Y21kL2djLmdv) | `78.26% <54.54%> (ø)` | |
| [cmd/objbench.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-Y21kL29iamJlbmNoLmdv) | `63.01% <58.82%> (-0.12%)` | :arrow_down: |
| [pkg/meta/sql.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-cGtnL21ldGEvc3FsLmdv) | `65.34% <72.91%> (-0.33%)` | :arrow_down: |
| [pkg/meta/base.go](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata#diff-cGtnL21ldGEvYmFzZS5nbw==) | `71.36% <75.00%> (ø)` | |
| ... and [21 more](https://codecov.io/gh/juicedata/juicefs/pull/2397/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/juicedata/juicefs/pull/2397?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/juicedata/juicefs/pull/2397?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata). Last update [421cee7...55edf77](https://codecov.io/gh/juicedata/juicefs/pull/2397?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=juicedata).


Patch:
diff --git a/cmd/fsck.go b/cmd/fsck.go
index 7dcfd4e6..63c8bd8e 100644
--- a/cmd/fsck.go
+++ b/cmd/fsck.go
@@ -127,13 +127,13 @@ func fsck(ctx *cli.Context) error {
 				if i == n {
 					sz = int(s.Size) - int(i)*chunkConf.BlockSize
 				}
-				key := fmt.Sprintf("%d_%d_%d", s.Chunkid, i, sz)
+				key := fmt.Sprintf("%d_%d_%d", s.Id, i, sz)
 				if _, ok := blocks[key]; !ok {
 					var objKey string
 					if format.HashPrefix {
-						objKey = fmt.Sprintf("%02X/%v/%s", s.Chunkid%256, s.Chunkid/1000/1000, key)
+						objKey = fmt.Sprintf("%02X/%v/%s", s.Id%256, s.Id/1000/1000, key)
 					} else {
-						objKey = fmt.Sprintf("%v/%v/%s", s.Chunkid/1000/1000, s.Chunkid/1000, key)
+						objKey = fmt.Sprintf("%v/%v/%s", s.Id/1000/1000, s.Id/1000, key)
 					}
 					if _, err := blob.Head(objKey); err != nil {
 						if _, ok := brokens[inode]; !ok {
diff --git a/cmd/gc.go b/cmd/gc.go
index 39624641..e06424cf 100644
--- a/cmd/gc.go
+++ b/cmd/gc.go
@@ -73,9 +73,9 @@ $ juicefs gc redis://localhost --delete`,
 	}
 }
 
-type dChunk struct {
-	chunkid uint64
-	length  uint32
+type dSlice struct {
+	id     uint64
+	length uint32
 }
 
 func gc(ctx *cli.Context) error {
@@ -113,7 +113,7 @@ func gc(ctx *cli.Context) error {
 	if ctx.Bool("compact") {
 		bar := progress.AddCountBar("Scanned chunks", 0)
 		spin := progress.AddDoubleSpinner("Compacted slices")
-		m.OnMsg(meta.DeleteChunk, func(args ...interface{}) error {
+		m.OnMsg(meta.DeleteSlice, func(args ...interface{}) error {
 			return store.Remove(args[0].(uint64), int(args[1].(uint32)))
 		})
 		m.OnMsg(meta.CompactChunk, func(args ...interface{}) error {
@@ -143,30 +143,30 @@ func gc(ctx *cli.Context) error {
 	// put it above delete count spinner
 	sliceCSpin := progress.AddCountSpinner("Listed slices")
 
-	// Delete pending chunks while listing slices
+	// Delete pending slices while listing all slices
 	delete := ctx.Bool("delete")
 	threads := ctx.Int("threads")
 	if delete && threads <= 0 {
 		logger.Fatal("threads should be greater than 0 to delete objects")
 	}
 	var delSpin *utils.Bar
-	var chunkChan chan *dChunk // pending delete chunks
+	var sliceChan chan *dSlice // pending delete slices
 	var wg sync.WaitGroup
 	if delete {
 		delSpin = progress.AddCountSpinner("Deleted pending")
-		chunkChan = make(chan *dChunk, 10240)
-		m.OnMsg(meta.DeleteChunk, func(args ...interface{}) error {
+		sliceChan = make(chan *dSlice, 10240)
+		m.OnMsg(meta.DeleteSlice, func(args ...interface{}) error {
 			delSpin.Increment()
-			chunkChan <- &dChunk{args[0].(uint64), args[1].(uint32)}
+			sliceChan <- &dSlice{args[0].(uint64), args[1].(uint32)}
 			return nil
 		})
 		for i := 0; i < threads; i++ {
 			wg.Add(1)
 			go func() {
 				defer wg.Done()
-				for c := range chunkChan {
-					if err := store.Remove(c.chunkid, int(c.length)); err != nil {
-						logger.Warnf("remove %d_%d: %s", c.chunkid, c.length, err)
+				for s := range sliceChan {
+					if err := store.Remove(s.id, int(s.length)); err != nil {
+						logger.Warnf("remove %d_%d: %s", s.id, s.length, err)
 					}
 				}
 			}()
@@ -181,11 +181,11 @@ func gc(ctx *cli.Context) error {
 		logger.Fatalf("list all slices: %s", r)
 	}
 	if delete {
-		close(chunkChan)
+		close(sliceChan)
 		wg.Wait()
 		delSpin.Done()
 		if progress.Quiet {
-			logger.Infof("Deleted %d pending chunks", delSpin.Current())
+			logger.Infof("Deleted %d pending slices", delSpin.Current())
 		}
 	}
 	sliceCSpin.Done()
@@ -201,7 +201,7 @@ func gc(ctx *cli.Context) error {
 	var totalBytes uint64
 	for _, ss := range slices {
 		for _, s := range ss {
-			keys[s.Chunkid] = s.Size
+			keys[s.Id] = s.Size
 			total += int64(int(s.Size-1)/chunkConf.BlockSize) + 1 // s.Size should be > 0
 			totalBytes += uint64(s.Size)
 		}
diff --git a/cmd/info.go b/cmd/info.go
index 6bca9a9d..ce9c1501 100644
--- a/cmd/info.go
+++ b/cmd/info.go
@@ -158,7 +158,7 @@ func printChunks(resp string, raw bool) {
 	result[0] = []string{"chunkIndex", "objectName", "size", "offset", "length"}
 	leftAlign := 1
 	if raw {
-		result[0][1] = "sliceID"
+		result[0][1] = "sliceId"
 		leftAlign = -1
 	}
 	for i := 1; i < len(result); i++ {
diff --git a/cmd/mount.go b/cmd/mount.go
index a3c44b85..967c2e4b 100644
--- a/cmd/mount.go
+++ b/cmd/mount.go
@@ -249,7 +249,7 @@ func getVfsConf(c *cli.Context, metaConf *meta.Config, format *meta.Format, chun
 }
 
 func registerMetaMsg(m meta.Meta, store chunk.ChunkStore, chunkConf *chunk.Config) {
-	m.OnMsg(meta.DeleteChunk, func(args ...interface{}) error {
+	m.OnMsg(meta.DeleteSlice, func(args ...interface{}) error {
 		return store.Remove(args[0].(uint64), int(args[1].(uint32)))
 	})
 	m.OnMsg(meta.CompactChunk, func(args ...interface{}) error {
diff --git a/cmd/profile.go b/cmd/profile.go
index 7d759180..51e9d08e 100644
--- a/cmd/profile.go
+++ b/cmd/profile.go
@@ -401,9 +401,7 @@ func profile(ctx *cli.Context) error {
 	go prof.flusher()
 	var input string
 	for {
-		if _, err = fmt.Scanln(&input); err != nil {
-			logger.Fatalf("Failed to scan input: %s", err)
-		}
+		_, _ = fmt.Scanln(&input)
 		if prof.tty {
 			fmt.Print("\033[1A\033[K") // move cursor back
 		}
diff --git a/docs/en/administration/fault_diagnosis_and_analysis.md b/docs/en/administration/fault_diagnosis_and_analysis.md
index 7b9660dd..54f97094 100644
--- a/docs/en/administration/fault_diagnosis_and_analysis.md
+++ b/docs/en/administration/fault_diagnosis_and_analysis.md
@@ -4,6 +4,9 @@ sidebar_position: 5
 slug: /fault_diagnosis_and_analysis
 ---
 
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
 # Fault Diagnosis and Analysis
 
 ## Client log
@@ -14,29 +17,45 @@ Different JuiceFS clients obtain logs in different ways, which are described bel
 
 ### Mount point
 
-When the JuiceFS file system is mounted with the [`-d` option](../reference/command_reference.md#juicefs-mount) (indicating running in the background), the log will be output to the syslog and `/var/log/juicefs.log` (requires v0.15 and above client, see [`--log` option](../reference/command_reference.md#juicefs-mount)). Depending on the operating system you are using, you can get the logs with different commands:
+When the JuiceFS file system is mounted with the [`-d` option](../reference/command_reference.md#juicefs-mount) (indicating running in the background), the log will be output to the system log and local log file at the same time, depending on the running user when the file system is mounted, the path of the local log file is slightly different. The log file path corresponding to root user is `/var/log/juicefs.log`, and the log file path of non-root user is `$HOME/.juicefs/juicefs.log`. For details, see [`--log` option](../reference/command_reference.md#juicefs-mount).
+
+Depending on the operating system you are using, you can get the logs with different commands:
+
+<Tabs>
+  <TabItem value="local-log-file" label="Local log file">
+
+```bash
+tail -n 100 /var/log/juicefs.log
+```
+
+  </TabItem>
+  <TabItem value="macos-syslog" label="macOS system log">
 
 ```bash
-# macOS
 syslog | grep 'juicefs'
+```
 
-# Debian based system
+  </TabItem>
+  <TabItem value="debian-syslog" label="Debian system log">
+
+```bash
 cat /var/log/syslog | grep 'juicefs'
+```
 
-# CentOS based system
-cat /var/log/messages | grep 'juicefs'
+  </TabItem>
+  <TabItem value="centos-syslog" label="CentOS system log">
 
-# All system (require v0.15+ JuiceFS)
-tail -n 100 /var/log/juicefs.log
+```bash
+cat /var/log/messages | grep 'juicefs'
 ```
 
+  </TabItem>
+</Tabs>
+
 You can use the `grep` command to filter different levels of logs for performance analysis or troubleshooting:
 
-```
-cat /var/log/syslog | grep 'juicefs' | grep '<INFO>'
-cat /var/log/syslog | grep 'juicefs' | grep '<WARNING>'
+```shell
 cat /var/log/syslog | grep 'juicefs' | grep '<ERROR>'
-cat /var/log/syslog | grep 'juicefs' | grep '<FATAL>'
 ```
 
 ### Kubernetes CSI Driver
diff --git a/docs/en/administration/metadata/redis_best_practices.md b/docs/en/administration/metadata/redis_best_practices.md
index 28d02ff1..39ca9717 100644
--- a/docs/en/administration/metadata/redis_best_practices.md
+++ b/docs/en/administration/metadata/redis_best_practices.md
@@ -11,8 +11,6 @@ This is a guide about Redis best practices. Redis is a critical component in Jui
 It's highly recommended to use Redis service managed by public cloud provider if possible. See ["Recommended Managed Redis Service"](#recommended-managed-redis-service) for more information.
 :::
 
-### Sentinel mode
-
 If you still want to operate Redis by yourself in production environment, please keep in mind that JuiceFS requires Redis version 4.0+. Moreover, it's recommended to pick an [official stable version](https://redis.io/download), and read the following contents before deploying Redis.
 
 :::note
@@ -40,11 +38,9 @@ used_memory_dataset_perc: 70.12%
 
 Among them, `used_memory_rss` is the total memory size actually used by Redis, which includes not only the size of data stored in Redis (that is, `used_memory_dataset` above), but also some Redis [system overhead](https://redis.io/commands/memory-stats) (that is, `used_memory_overhead` above). As mentioned earlier, the metadata of each file occupies about 300 bytes and is calculated by `used_memory_dataset`. If you find that the metadata of a single file in your JuiceFS file system occupies much more than 300 bytes, you can try to run [`juicefs gc`](../../reference/command_reference.md#juicefs-gc) command to clean up possible redundant data.
 
-## High Availability
+## High availability
 
-:::caution
-JuiceFS uses "[Redis transactions](https://redis.io/docs/manual/transactions)" to guarantee the atomicity of metadata operations. However, since Redis Cluster does not support transaction, Redis Cluster cannot be used as a JuiceFS metadata store. For high availability of Redis, please use Redis Sentinel.
-:::
+### Sentinel mode
 
 [Redis Sentinel](https://redis.io/docs/manual/sentinel) is the official high availability solution for Redis. It provides following capabilities:
 
@@ -81,19 +77,23 @@ Since JuiceFS v1.0.0, it is supported to connect only Redis replica nodes when m
 It should be noted that since the data of the Redis master node is asynchronously replicated to the replica nodes, it is possible that the metadata read may not be the latest.
 :::
 
-
 ### Cluster mode
-Juicefs also supports redis of cluster mode as meta engine, the `META-URL` format is `redis[s]://[[USER]:PASSWORD@]ADDR:PORT,[ADDR:PORT],[ADDR:PORT][/DB]`. For example:
+
+:::note
+This feature requires JuiceFS v1.0.0 or higher
+:::
+
+Juicefs also supports Redis Cluster as metadata engine, the `META-URL` format is `redis[s]://[[USER]:PASSWORD@]ADDR:PORT,[ADDR:PORT],[ADDR:PORT][/PREFIX]`. For example:
 
 ```shell
-juicefs format redis://127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002/1 ~/jfs
+juicefs format redis://127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002/jfs1 myjfs
 ```
 
-:::tip 
-Although Redis cluster does not support db isolation mechanism, when it is a metadata engine, Juicefs uses the function of redis cluster [Hash Tag](https://redis.io/docs/reference/cluster-spec/#hash-tags) to implement isolation and transaction assurance in cluster mode by using `{db}` as the prefix of key, so db in cluster mode still has an isolation effect.
+:::tip
+Redis Cluster does not support multiple databases like the standalone mode, instead it splits the key space into 16384 hash slots, and distributes the slots to several Redis master nodes. Based on Redis Cluster's [Hash Tag](https://redis.io/docs/reference/cluster-spec/#hash-tags) feature, JuiceFS adds `{prefix}` before all file system keys to ensure they will be hashed to the same hash slot, assuring that transactions can still work. Besides, one Redis Cluster can serve for multiple JuiceFS file systems as long as they use different prefixes.
 :::
 
-## Data Durability
+## Data durability
 
 Redis provides a different range of [persistence](https://redis.io/docs/manual/persistence) options:
 
@@ -115,7 +115,7 @@ Redis is very data backup friendly since you can copy RDB files while the databa
 
 Please read the [official documentation](https://redis.io/docs/manual/persistence) for more information.
 
-## Backing up Redis Data
+## Backing up Redis data
 
 **Make Sure to Backup Your Database.** Disks break, instances in the cloud disappear, and so forth.
 
@@ -131,7 +131,7 @@ This means that copying the RDB file is completely safe while the server is runn
 
 Please read the [official documentation](https://redis.io/docs/manual/persistence) for more information.
 
-## Restore Redis Data
+## Restore Redis data
 
 After generating the AOF or RDB backup file, you can restore the data by copying the backup file to the path corresponding to the `dir` configuration of the new Redis instance, which you can get by using the [`CONFIG GET dir`](https://redis.io/commands/config-get) command.
 
@@ -143,14 +143,14 @@ After recovering Redis data, you can continue to use the JuiceFS file system wit
 
 ## Recommended Managed Redis Service
 
+### Amazon MemoryDB for Redis
+
+[Amazon MemoryDB for Redis](https://aws.amazon.com/memorydb) is a durable, in-memory database service that delivers ultra-fast performance. MemoryDB is compatible with Redis, with MemoryDB, all of your data is stored in memory, which enables you to achieve microsecond read and single-digit millisecond write latency and high throughput. MemoryDB also stores data durably across multiple Availability Zones (AZs) using a Multi-AZ transactional log to enable fast failover, database recovery, and node restarts.
+
 ### Amazon ElastiCache for Redis
 
 [Amazon ElastiCache for Redis](https://aws.amazon.com/elasticache/redis) is a fully managed, Redis-compatible in-memory data store built for the cloud. It provides [automatic failover](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/AutoFailover.html), [automatic backup](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/backups-automatic.html) features to ensure availability and durability.
 
-:::info
-Amazon ElastiCache for Redis has two type: cluster mode disabled and cluster mode enabled. Because JuiceFS uses [transaction](https://redis.io/docs/manual/transactions) to guarantee the atomicity of metadata operations, so you couldn't use "cluster mode enabled" type.
-:::
-
 ### Google Cloud Memorystore for Redis
 
 [Google Cloud Memorystore for Redis](https://cloud.google.com/memorystore/docs/redis) is a fully managed Redis service for the Google Cloud. Applications running on Google Cloud can achieve extreme performance by leveraging the highly scalable, available, secure Redis service without the burden of managing complex Redis deployments.
@@ -163,14 +163,6 @@ Amazon ElastiCache for Redis has two type: cluster mode disabled and cluster mod
 
 [Alibaba Cloud ApsaraDB for Redis](https://www.alibabacloud.com/product/apsaradb-for-redis) is a database service that is compatible with native Redis protocols. It supports a hybrid of memory and hard disks for data persistence. ApsaraDB for Redis provides a highly available hot standby architecture and can scale to meet requirements for high-performance and low-latency read/write operations.
 
-:::info
-ApsaraDB for Redis supports 3 type [architectures](https://www.alibabacloud.com/help/doc-detail/86132.htm): standard, cluster and read/write splitting. Because JuiceFS uses [transaction](https://redis.io/docs/manual/transactions) to guarantee the atomicity of metadata operations, so you couldn't use cluster type architecture.
-:::
-
 ### Tencent Cloud TencentDB for Redis
 
 [Tencent Cloud TencentDB for Redis](https://intl.cloud.tencent.com/product/crs) is a caching and storage service compatible with the Redis protocol. It features a rich variety of data structure options to help you develop different types of business scenarios, and offers a complete set of database services such as primary-secondary hot backup, automatic switchover for disaster recovery, data backup, failover, instance monitoring, online scaling and data rollback.
-
-:::info
-TencentDB for Redis supports 2 type [architectures](https://intl.cloud.tencent.com/document/product/239/3205): standard and cluster. Because JuiceFS uses [transaction](https://redis.io/docs/manual/transactions) to guarantee the atomicity of metadata operations, so you couldn't use cluster type architecture.
-:::
diff --git a/docs/en/faq.md b/docs/en/faq.md
index fdb11898..5cbcb99d 100644
--- a/docs/en/faq.md
+++ b/docs/en/faq.md
@@ -4,11 +4,9 @@
 
 JuiceFS already supported many object storage, please check [the list](guide/how_to_setup_object_storage.md#supported-object-storage) first. If this object storage is compatible with S3, you could treat it as S3. Otherwise, try reporting issue.
 
-## Can I use Redis Cluster as metadata engine?
+## Does support Redis in Sentinel or Cluster-mode as the metadata engine for JuiceFS?
 
-Yes. Since [v1.0.0 Beta3](https://github.com/juicedata/juicefs/releases/tag/v1.0.0-beta3) JuiceFS supports the use of [Redis Cluster](https://redis.io/docs/manual/scaling) as the metadata engine, but it should be noted that Redis Cluster requires that the keys of all operations in a transaction must be in the same hash slot, so a JuiceFS file system can only use one hash slot.
-
-See ["Redis Best Practices"](administration/metadata/redis_best_practices.md) for more information.
+Yes，There is also a [best practice document](administration/metadata/redis_best_practices.md) for Redis as the JuiceFS metadata engine for reference.
 
 ## What's the difference between JuiceFS and XXX?
 
@@ -20,13 +18,39 @@ JuiceFS is a distributed file system, the latency of metedata is determined by 1
 
 JuiceFS is built with multiple layers of caching (invalidated automatically), once the caching is warmed up, the latency and throughput of JuiceFS could be close to local filesystem (having the overhead of FUSE).
 
+## Why can't I see the original files that have been stored in JuiceFS in object storage?
+
+While using JuiceFS, files will eventually be split into Chunks, Slices and Blocks and stored in object storage. Therefore, you may notice that the source files stored in JuiceFS cannot be found in the file browser of the object storage platform; instead, there are only a directory of chunks and a bunch of directories and files named by numbers in the bucket. Don't panic! That's exactly what makes JuiceFS a high-performance file system. For details, please refer to ["How JuiceFS Store Files"](introduction/architecture.md#how-juicefs-stores-files).
+
 ## Does JuiceFS support random read/write?
 
 Yes, including those issued using mmap. Currently JuiceFS is optimized for sequential reading/writing, and optimized for random reading/writing is work in progress. If you want better random reading performance, it's recommended to turn off compression ([`--compress none`](reference/command_reference.md#juicefs-format)).
 
+## What is the implementation principle of JuiceFS supporting random write?
+
+JuiceFS does not store the original file in the object storage, but splits it into N data blocks according to a certain size (4MiB by default), uploads it to the object storage, and stores the ID of the data block in the metadata engine. When writing randomly, it is logical to overwrite the original content. In fact, the metadata of the **data block to be overwritten** is marked as old data, and only the **new data block** generated during random writing is uploaded to the object storage, and update the metadata corresponding to the **new data block** to the metadata engine.
+
+When reading the data of the overwritten part, according to the **latest metadata**, it can be read from the **new data block** uploaded during random writing, and the **old data block** may be deleted by the background garbage collection tasks automatically clean up. This shifts the complexity of random writes to the complexity of reads.
+
+This is just a rough introduction to the implementation logic. The specific read and write process is very complicated. You can study the two documents ["JuiceFS Internals"](development/data_structures.md) and ["Data Processing Flow"](introduction/io_processing.md) and comb them together with the code.
+
+## Why do I delete files at the mount point, but there is no change or very little change in object storage footprint?
+
+The first reason is that you may have enabled the trash feature. In order to ensure data security, the trash is enabled by default. The deleted files are actually placed in the trash and are not actually deleted, so the size of the object storage will not change. trash retention time can be specified with `juicefs format` or modified with `juicefs config`. Please refer to the ["Trash"](security/trash.md) documentation for more information.
+
+The second reason is that JuiceFS deletes the data in the object storage asynchronously, so the space change of the object storage will be slower. If you need to immediately clean up the data in the object store that needs to be deleted, you can try running the [`juicefs gc`](reference/command_reference.md#juicefs-gc) command.
+
+## Why is the size displayed at the mount point different from the object storage footprint?
+
+From the answer to this question ["What is the implementation principle of JuiceFS supporting random write?"](#what-is-the-implementation-principle-of-juicefs-supporting-random-write), it can be inferred that the occupied space of object storage is greater than or equal to the actual size in most cases, especially after a large number of overwrites in a short period of time generate many file fragments. These fragments still occupy space in object storage until merges and reclamations are triggered. But don't worry about these fragments occupying space all the time, because every time you read/write a file, it will check and trigger the defragmentation of the file when necessary. Alternatively, you can manually trigger merges and recycles with the `juicefs gc --compact --delete` command.
+
+In addition, if the JuiceFS file system has compression enabled (not enabled by default), the objects stored on the object storage may be smaller than the actual file size (depending on the compression ratio of different types of files).
+
+If the above factors have been excluded, please check the [storage class](guide/how_to_setup_object_storage.md#storage-class) of the object storage you are using. The cloud service provider may set the minimum billable size for some storage classes. For example, the [minimum billable size](https://www.alibabacloud.com/help/en/object-storage-service/latest/storage-fees) of Alibaba Cloud OSS IA storage is 64KB. If a single file is smaller than 64KB, it will be calculated as 64KB.
+
 ## When my update will be visible to other clients?
 
-All the metadata updates are immediately visible to all others. JuiceFS guarantees close-to-open consistency, see ["Consistency"](guide/cache_management.md#consistency) for more information.
+All the metadata updates are immediately visible to all others. JuiceFS guarantees close-to-open consistency, see ["Consistency"](guide/cache_management.md#data-consistency) for more information.
 
 The new data written by `write()` will be buffered in kernel or client, visible to other processes on the same machine, not visible to other machines.
 
@@ -50,6 +74,10 @@ See ["Read Cache in Client"](guide/cache_management.md#read-cache-in-client) for
 
 Use [`juicefs umount`](reference/command_reference.md#juicefs-umount) command to unmount the volume.
 
+## `Resource busy -- try 'diskutil unmount'` error when unmounting the mount point
+
+This means that a file or directory under the mount point is in use and cannot be `umount` directly. You can check (such as through the `lsof` command) whether an open terminal is located in a directory on the JuiceFS mount point, or an application is processing files in the mount point. If so, exit the terminal or application and try to unmount the file system using the `juicefs umount` command.
+
 ## How to upgrade JuiceFS client?
 
 First unmount JuiceFS volume, then re-mount the volume with newer version client.
@@ -108,3 +136,41 @@ uid=1201(alice) gid=500(staff) groups=500(staff)
 ```
 
 Read ["Sync Accounts between Multiple Hosts"](administration/sync_accounts_between_multiple_hosts.md) to resolve this problem.
+
+## What other ways JuiceFS supports access to data besides mount?
+
+In addition to mounting, the following methods are also supported:
+
+- Kuberenetes CSI Driver: Use JuiceFS as the storage layer of Kubernetes cluster through the Kubernetes CSI Driver. For details, please refer to ["Use JuiceFS on Kubernetes"](deployment/how_to_use_on_kubernetes.md).
+- Hadoop Java SDK: It is convenient to use a Java client compatible with the HDFS interface to access JuiceFS in the Hadoop ecosystem. For details, please refer to ["Use JuiceFS on Hadoop Ecosystem"](deployment/hadoop_java_sdk.md).
+- S3 Gateway: Access JuiceFS through the S3 protocol. For details, please refer to ["Deploy JuiceFS S3 Gateway"](deployment/s3_gateway.md).
+- Docker Volume Plugin: A convenient way to use JuiceFS in Docker, please refer to ["Use JuiceFS on Docker"](deployment/juicefs_on_docker.md).
+- WebDAV Gateway: Access JuiceFS via WebDAV protocol
+
+## Where is the JuiceFS log?
+
+Different types of JuiceFS clients have different ways to obtain logs. For details, please refer to ["Client log"](administration/fault_diagnosis_and_analysis.md#client-log) document.
+
+## How to destroy a file system?
+
+Destroy a file system with `juicefs destroy` command, which will clear the metadata engine and object storage related data. For details about the use of this command, please refer to the [document](administration/destroy.md).
+
+## Does JuiceFS Gateway support advanced features such as multi-user management?
+
+The built-in `gateway` subcommand of JuiceFS does not support functions such as multi-user management, and only provides basic S3 gateway functions. If you need to use these advanced features, please refer to the [documentation](deployment/s3_gateway.md#use-a-full-featured-s3-gateway).
+
+## Does JuiceFS support using a directory in object storage as the value of the `--bucket` option?
+
+As of the release of JuiceFS 1.0, this feature is not supported.
+
+## Does JuiceFS support accessing data that already exists in object storage?
+
+As of the release of JuiceFS 1.0, this feature is not supported.
+
+## Does JuiceFS currently support distributed caching?
+
+As of the release of JuiceFS 1.0, this feature is not supported.
+
+## Is there currently an SDK available for JuiceFS?
+
+As of the release of JuiceFS 1.0, the community has two SDKs, one is the [Java SDK](deployment/hadoop_java_sdk.md) that is highly compatible with the HDFS interface officially maintained by Juicedata, and the other is the [Python SDK](https://github.com/megvii-research/juicefs-python) maintained by community users.
diff --git a/docs/en/mount_at_boot.md b/docs/en/guide/mount_at_boot.md
similarity index 59%
rename from docs/en/mount_at_boot.md
rename to docs/en/guide/mount_at_boot.md
index 4abb7052..192f9b31 100644
--- a/docs/en/mount_at_boot.md
+++ b/docs/en/guide/mount_at_boot.md
@@ -1,4 +1,10 @@
-# Mount JuiceFS at Boot
+---
+sidebar_label: Mount JuiceFS at Boot Time
+sidebar_position: 2
+slug: /mount_juicefs_at_boot_time
+---
+
+# Mount JuiceFS at Boot Time
 
 This is a guide about how to mount JuiceFS automatically at boot.
 
@@ -10,23 +16,25 @@ Copy `juicefs` as `/sbin/mount.juicefs`, then edit `/etc/fstab` with following l
 <META-URL>    <MOUNTPOINT>       juicefs     _netdev[,<MOUNT-OPTIONS>]     0  0
 ```
 
-The format of `<META-URL>` is `redis://<user>:<password>@<host>:<port>/<db>`, e.g. `redis://localhost:6379/1`. And replace `<MOUNTPOINT>` with specific path you wanna mount JuiceFS to, e.g. `/jfs`. If you need set [mount options](reference/command_reference.md#juicefs-mount), replace `[,<MOUNT-OPTIONS>]` with comma separated options list. The following line is an example:
+For the format of `<META-URL>`, please refer to the ["How to Setup Metadata Engine"](how_to_setup_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:
 
 ```
-redis://localhost:6379/1    /jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=2048     0  0
+redis://localhost:6379/1    /jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=204800     0  0
 ```
 
-**Note: By default, CentOS 6 will NOT mount network file system after boot, run following command to enable it:**
+:::tip
+By default, CentOS 6 will NOT mount network file system after boot, run following command to enable it:
 
 ```bash
 sudo chkconfig --add netfs
 ```
+:::
 
 ## macOS
 
-Create a file named `io.juicefs.<NAME>.plist` under `~/Library/LaunchAgents`. Replace `<NAME>` with JuiceFS volume name. Add following contents to the file (again, replace `NAME`, `PATH-TO-JUICEFS`, `META-URL` and `MOUNTPOINT` with appropriate value):
+Create a file named `io.juicefs.<NAME>.plist` under `~/Library/LaunchAgents`. Replace `<NAME>` with JuiceFS file system name. Add following contents to the file (again, replace `NAME`, `PATH-TO-JUICEFS`, `META-URL`, `MOUNTPOINT` and `MOUNT-OPTIONS` with appropriate value):
 
-```xml
+```xml title="io.juicefs.<NAME>.plist"
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
@@ -39,6 +47,7 @@ Create a file named `io.juicefs.<NAME>.plist` under `~/Library/LaunchAgents`. Re
                 <string>mount</string>
                 <string>META-URL</string>
                 <string>MOUNTPOINT</string>
+                <string>MOUNT-OPTIONS</string>
         </array>
         <key>RunAtLoad</key>
         <true/>
@@ -46,7 +55,18 @@ Create a file named `io.juicefs.<NAME>.plist` under `~/Library/LaunchAgents`. Re
 </plist>
 ```
 
-Use following commands to load the file created in the previous step and test whether the loading is successful. **Please ensure Redis server is already running.**
+:::tip
+If there are multiple mount options, they can be set in multiple lines, for example:
+
+```xml
+                <string>--max-uploads</string>
+                <string>50</string>
+                <string>--cache-size</string>
+                <string>204800</string>
+```
+:::
+
+Use following commands to load the file created in the previous step and test whether the loading is successful. **Please make sure the metadata engine is running properly.**
 
 ```bash
 $ launchctl load ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
@@ -56,7 +76,7 @@ $ ls <MOUNTPOINT>
 
 If mount failed, you can add following configuration to `io.juicefs.<NAME>.plist` file for debug purpose:
 
-```xml
+```xml title="io.juicefs.<NAME>.plist"
         <key>StandardOutPath</key>
         <string>/tmp/juicefs.out</string>
         <key>StandardErrorPath</key>
@@ -80,11 +100,10 @@ brew services start redis
 
 Then add following configuration to `io.juicefs.<NAME>.plist` file for ensure Redis server is loaded:
 
-```xml
+```xml title="io.juicefs.<NAME>.plist"
         <key>KeepAlive</key>
         <dict>
                 <key>OtherJobEnabled</key>
                 <string>homebrew.mxcl.redis</string>
         </dict>
 ```
-
diff --git a/docs/zh_cn/administration/fault_diagnosis_and_analysis.md b/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
index ff431319..17ffdffb 100644
--- a/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
+++ b/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
@@ -4,6 +4,9 @@ sidebar_position: 5
 slug: /fault_diagnosis_and_analysis
 ---
 
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
 # 故障诊断和分析
 
 ## 客户端日志
@@ -14,42 +17,46 @@ JuiceFS 客户端在运行过程中会输出日志用于故障诊断，日志等
 
 ### 挂载点
 
-当挂载 JuiceFS 文件系统时加上了 [`-d` 选项](../reference/command_reference.md#juicefs-mount)（表示后台运行），日志会输出到系统日志和 `/var/log/juicefs.log`（需要 v0.15 及以上版本客户端，参见 [`--log` 选项](../reference/command_reference.md#juicefs-mount)）。取决于你使用的操作系统，你可以通过不同的命令获取日志：
+当挂载 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)。
+
+取决于你使用的操作系统，你可以通过不同的命令获取系统日志或直接读取本地日志文件：
+
+<Tabs>
+  <TabItem value="local-log-file" label="本地日志文件">
+
+```bash
+tail -n 100 /var/log/juicefs.log
+```
+
+  </TabItem>
+  <TabItem value="macos-syslog" label="macOS 系统日志">
 
-macOS
 ```bash
 syslog | grep 'juicefs'
 ```
 
-Debian based system
+  </TabItem>
+  <TabItem value="debian-syslog" label="Debian 系统日志">
+
 ```bash
 cat /var/log/syslog | grep 'juicefs'
 ```
 
-CentOS based system
+  </TabItem>
+  <TabItem value="centos-syslog" label="CentOS 系统日志">
+
 ```bash
 cat /var/log/messages | grep 'juicefs'
 ```
 
-All system (require v0.15+ JuiceFS)
-```bash
-tail -n 100 /var/log/juicefs.log
-```
+  </TabItem>
+</Tabs>
 
 你可以使用 `grep` 命令过滤显示不同等级的日志信息，从而进行性能统计和故障追踪，例如：
 
-```bash
-cat /var/log/syslog | grep 'juicefs' | grep '<INFO>'
-```
-```bash
-cat /var/log/syslog | grep 'juicefs' | grep '<WARNING>'
-```
-```bash
+```shell
 cat /var/log/syslog | grep 'juicefs' | grep '<ERROR>'
 ```
-```bash
-cat /var/log/syslog | grep 'juicefs' | grep '<FATAL>'
-```
 
 ### Kubernetes CSI 驱动
 
diff --git a/docs/zh_cn/administration/metadata/redis_best_practices.md b/docs/zh_cn/administration/metadata/redis_best_practices.md
index 6192d341..97999b26 100644
--- a/docs/zh_cn/administration/metadata/redis_best_practices.md
+++ b/docs/zh_cn/administration/metadata/redis_best_practices.md
@@ -40,11 +40,7 @@ used_memory_dataset_perc: 70.12%
 
 ## 数据可用性
 
-:::caution 注意
-JuiceFS 使用 「[Redis 事务](https://redis.io/docs/manual/transactions)」保证元数据操作的原子性。但由于 Redis Cluster 集群模式不支持事务（Transactions），因此 Redis 集群不可用作 JuiceFS 元数据存储。如有 Redis 高可用需求，请使用 Sentinel 哨兵模式。
-:::
-
-### Sentinel 模式
+### 哨兵模式
 
 [Redis 哨兵](https://redis.io/docs/manual/sentinel) 是 Redis 官方的高可用解决方案，它提供以下功能：
 
@@ -81,18 +77,22 @@ juicefs mount redis://:password@masterName,1.2.3.4,1.2.5.6:26379/2 ~/jfs
 需要注意由于 Redis 主节点的数据是异步复制到副本节点，因此有可能读到的元数据不是最新的。
 :::
 
-### Cluster 模式
-Juicefs 同样支持 cluster 模式的 redis 作为元数据引擎，redis cluster 模式的 `META-URL` 为 `redis[s]://[[USER]:PASSWORD@]ADDR:PORT,[ADDR:PORT],[ADDR:PORT][/DB]`，例如：
+### 集群模式
+
+:::note 注意
+此特性需要使用 1.0.0 及以上版本的 JuiceFS
+:::
+
+Juicefs 同样支持集群模式的 Redis 作为元数据引擎，Redis 集群模式的 `META-URL` 为 `redis[s]://[[USER]:PASSWORD@]ADDR:PORT,[ADDR:PORT],[ADDR:PORT][/PREFIX]`，例如：
 
 ```shell
-juicefs format redis://127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 ~/jfs
+juicefs format redis://127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002/jfs1 myjfs
 ```
 
 :::tip 提示
-Redis cluster 虽然不支持 db 隔离机制，但是在其作为元数据引擎的时候，Juicefs 利用 redis cluster [Hash Tag](https://redis.io/docs/reference/cluster-spec/#hash-tags) 的特性，通过将 `{db}` 作为 key 的前缀实现了 cluster 模式下的隔离与事务保证，因此 cluster 模式下的 db 仍旧有隔离的效果。可以通过设置不同的 `db` 让一个 redis cluster 同时作为多个文件系统的元数据库。
+Redis 集群不再支持多数据库，而是将所有 keys 分散到 16384 个 hash slots 中，再将这些 hash slots 打散到多个 Redis master 节点来存储。JuiceFS 利用了 Redis 集群的 [Hash Tag](https://redis.io/docs/reference/cluster-spec/#hash-tags) 特性，通过将 `{prefix}` 作为 key 的前缀来将一个文件系统中的所有 keys 都存放在同一个 hash slot，以保证集群模式下操作的事务性。另外，通过设置不同的 `prefix` 可以让一个 Redis 集群同时作为多个 JuiceFS 的元数据库。
 :::
 
-
 ## 数据持久性
 
 Redis 提供了不同范围的[持久性](https://redis.io/docs/manual/persistence)选项：
@@ -143,14 +143,14 @@ Redis 对数据备份非常友好，因为您可以在数据库运行时复制 R
 
 ## 推荐的 Redis 托管服务
 
+### Amazon MemoryDB for Redis
+
+[Amazon MemoryDB for Redis](https://aws.amazon.com/memorydb) 是一种持久的内存数据库服务，可提供超快的性能。MemoryDB 与 Redis 兼容，使用 MemoryDB，你的所有数据都存储在内存中，这使你能够实现微秒级读取和数毫秒的写入延迟和高吞吐。MemoryDB 还使用多可用区事务日志跨多个可用区持久存储数据，以实现快速故障切换、数据库恢复和节点重启。
+
 ### Amazon ElastiCache for Redis
 
 [Amazon ElastiCache for Redis](https://aws.amazon.com/elasticache/redis) 是为云构建的完全托管的、与 Redis 兼容的内存数据存储。它提供[自动故障切换](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/AutoFailover.html)、[自动备份](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/backups-automatic.html)等功能以确保可用性和持久性。
 
-:::info 说明
-Amazon ElastiCache for Redis 有两种类型：禁用集群模式和启用集群模式。因为 JuiceFS 使用[事务](https://redis.io/docs/manual/transactions)来保证元数据操作的原子性，所以不能使用「启用集群模式」类型。
-:::
-
 ### Google Cloud Memorystore for Redis
 
 [Google Cloud Memorystore for Redis](https://cloud.google.com/memorystore/docs/redis) 是针对 Google Cloud 的完全托管的 Redis 服务。通过利用高度可扩展、可用且安全的 Redis 服务，在 Google Cloud 上运行的应用程序可以实现卓越的性能，而无需管理复杂的 Redis 部署。
@@ -159,18 +159,10 @@ Amazon ElastiCache for Redis 有两种类型：禁用集群模式和启用集群
 
 [Azure Cache for Redis](https://azure.microsoft.com/en-us/services/cache) 是一个完全托管的内存缓存，支持高性能和可扩展的架构。使用它来创建云或混合部署，以亚毫秒级延迟处理每秒数百万个请求——所有这些都具有托管服务的配置、安全性和可用性优势。
 
-### 阿里云 ApsaraDB for Redis
+### 阿里云云数据库 Redis 版
 
-[阿里云 ApsaraDB for Redis](https://www.alibabacloud.com/product/apsaradb-for-redis) 是一种兼容原生 Redis 协议的数据库服务。它支持混合内存和硬盘以实现数据持久性。云数据库 Redis 版提供高可用的热备架构，可扩展以满足高性能、低延迟的读写操作需求。
+[阿里云云数据库 Redis 版](https://www.aliyun.com/product/kvstore)是一种兼容原生 Redis 协议的数据库服务。它支持混合内存和硬盘以实现数据持久性。云数据库 Redis 版提供高可用的热备架构，可扩展以满足高性能、低延迟的读写操作需求。
 
-:::info 说明
-ApsaraDB for Redis 支持 3 种类型的[架构](https://www.alibabacloud.com/help/doc-detail/86132.htm)：标准、集群和读写分离。因为 JuiceFS 使用[事务](https://redis.io/docs/manual/transactions)来保证元数据操作的原子性，所以不能使用集群类型架构。
-:::
+### 腾讯云云数据库 Redis
 
-### 腾讯云 TencentDB for Redis
-
-[腾讯云 TencentDB for Redis](https://intl.cloud.tencent.com/product/crs) 是一种兼容 Redis 协议的缓存和存储服务。丰富多样的数据结构选项，帮助您开发不同类型的业务场景，提供主从热备份、容灾自动切换、数据备份、故障转移、实例监控、在线等一整套数据库服务缩放和数据回滚。
-
-:::info 说明
-TencentDB for Redis 支持两种类型的[架构](https://intl.cloud.tencent.com/document/product/239/3205)：标准和集群。因为 JuiceFS 使用[事务](https://redis.io/docs/manual/transactions)来保证元数据操作的原子性，所以不能使用集群类型架构。
-:::
+[腾讯云云数据库 Redis](https://cloud.tencent.com/product/crs) 是一种兼容 Redis 协议的缓存和存储服务。丰富多样的数据结构选项，帮助您开发不同类型的业务场景，提供主从热备份、容灾自动切换、数据备份、故障转移、实例监控、在线等一整套数据库服务缩放和数据回滚。
diff --git a/docs/zh_cn/development/data_structures.md b/docs/zh_cn/development/data_structures.md
index f65e698f..a973bbb3 100644
--- a/docs/zh_cn/development/data_structures.md
+++ b/docs/zh_cn/development/data_structures.md
@@ -84,7 +84,7 @@ type Format struct {
 - totalInodes：文件系统的已使用文件数
 
 - nextInode：下一个可用的 inode 号（Redis 中为当前已用的最大 inode 号）
-- nextChunk：下一个可用的 sliceID（Redis 中为当前已用的最大 sliceID）
+- nextChunk：下一个可用的 sliceId（Redis 中为当前已用的最大 sliceId）
 - nextSession：当前已用的最大 sid（sessionID）
 - nextTrash：当前已用的最大 trash inode 号
 - nextCleanupSlices：上一次检查清理残留 slices 的时间点
@@ -202,7 +202,7 @@ type Slice struct {
 记录 Slice 的引用计数，具体为：
 
 ```
-sliceID, size -> refs
+sliceId, size -> refs
 ```
 
 由于绝大部分 Slice 的引用计数均为 1，为减少数据库中相关 entry 数量，在 Redis 和 TKV 中以实际值减 1 作为存储的计数值。这样，大部分的 Slice 对应 refs 值为 0，则不必在数据库中创建相关 entry。
@@ -269,10 +269,10 @@ inode, length -> expire
 记录延迟删除的 Slices。当回收站功能开启时，因 Slice Compaction 功能删除的旧 Slices 会被保留与回收站配置相同的时间，以被在必要时可用来恢复数据。其内容为：
 
 ```
-sliceID, deleted -> []slice
+sliceId, deleted -> []slice
 ```
 
-其中 sliceID 为 compact 后新 Slice 的 ID，deleted 为 compact 完成的时间戳，映射值为被 compacted 的所有旧 slice 列表，每个 slice 仅编码了 ID 和 size 信息：
+其中 sliceId 为 compact 后新 Slice 的 ID，deleted 为 compact 完成的时间戳，映射值为被 compacted 的所有旧 slice 列表，每个 slice 仅编码了 ID 和 size 信息：
 
 ```go
 type slice struct {
@@ -366,7 +366,7 @@ Redis 中 Key 的通用格式为 `${prefix}${JFSKey}`，其中：
 - Key：`sliceRef`
 - Value Type：Hash
 - Value：所有需记录的 Slices 的计数值。在 Hash 中：
-  - Key：`k${sliceID}_${size}`
+  - Key：`k${sliceId}_${size}`
   - Value：此 Slice 的引用计数值减 1（若引用计数为 1，则一般不创建对应 entry）
 
 #### 3.2.10 Symlink
@@ -412,7 +412,7 @@ Redis 中 Key 的通用格式为 `${prefix}${JFSKey}`，其中：
 - Key：`delSlices`
 - Value Type：Hash
 - Value：所有待清理的 Slices。在 Hash 中：
-  - Key：`${sliceID}_${deleted}`
+  - Key：`${sliceId}_${deleted}`
   - Value：字节数组，其中每 12 字节对应一个 [slice](#3.1.15-DelSlices)
 
 #### 3.2.16 Sustained
@@ -514,15 +514,13 @@ Slices 是一段字节数组，每 24 字节对应一个 [Slice](#3.1.8-Chunk)
 #### 3.3.9 SliceRef
 
 ```go
-type chunkRef struct {
-	Chunkid uint64 `xorm:"pk"`
-	Size    uint32 `xorm:"notnull"`
-	Refs    int    `xorm:"notnull"`
+type sliceRef struct {
+	Id   uint64 `xorm:"pk chunkid"`
+	Size uint32 `xorm:"notnull"`
+	Refs int    `xorm:"notnull"`
 }
 ```
 
-Chunkid 实际等同 [3.1.9](#3.1.9-SliceRef) 里的 sliceID。
-
 #### 3.3.10 Symlink
 
 ```go
@@ -583,13 +581,13 @@ type delfile struct {
 
 ```go
 type delslices struct {
-	Chunkid uint64 `xorm:"pk"`
+	Id      uint64 `xorm:"pk chunkid"`
 	Deleted int64  `xorm:"notnull"`
 	Slices  []byte `xorm:"blob notnull"`
 }
 ```
 
-Chunkid 实际等同 [3.1.15](#3.1.15-DelSlices) 里的 sliceID。Slices 是一段字节数组，每 12 字节对应一个 [slice](#3.1.15-DelSlices)。
+Slices 是一段字节数组，每 12 字节对应一个 [slice](#3.1.15-DelSlices)。
 
 #### 3.3.16 Sustained
 
@@ -611,7 +609,7 @@ TKV（Transactional Key-Value Database）中 Key 的通用格式为 `${prefix}${
 在 TKV 的 Keys 中，所有整数都以编码后的二进制形式存储：
 
 - inode 和 counter value 占 8 个字节，使用**小端**编码
-- sid、sliceID 和 timestamp 占 8 个字节，使用**大端**编码
+- sid、sliceId 和 timestamp 占 8 个字节，使用**大端**编码
 
 #### 3.4.1 Setting
 
@@ -666,7 +664,7 @@ A${inode}C${index} -> Slices
 #### 3.4.9 SliceRef
 
 ```
-K${sliceID}${size} -> counter value
+K${sliceId}${size} -> counter value
 ```
 
 其中 size 占 4 个字节，使用**大端**编码。
@@ -729,7 +727,7 @@ D${inode}${length} -> timestamp
 #### 3.4.15 DelSlices
 
 ```
-L${timestamp}${sliceID} -> slices
+L${timestamp}${sliceId} -> slices
 ```
 
 其中 slices 是一段字节数组，每 12 字节对应一个 [slice](#3.1.15-DelSlices)。
@@ -767,7 +765,7 @@ Chunk: |<---        Chunk 0        --->|<---        Chunk 1        --->|<-- Chun
 在单机 Redis 中，这意味着有 3 个 [Chunk Keys](#3.1.8-Chunk)，分别为 `c100_0`， `c100_1` 和 `c100_2`，每个 Key 对应一个 Slices 列表。这些 Slices 主要在数据写入时生成，可能互相之间有覆盖，也可能未完全填充满 Chunk。因此，在使用前需要顺序遍历这个 Slices 列表，并重新构建出最新版的数据分布，做到：
 
 1. 有多个 Slice 覆盖的部分以最后加入的 Slice 为准
-2. 没有被 Slice 覆盖的部分自动补零，用 sliceID = 0 来表示
+2. 没有被 Slice 覆盖的部分自动补零，用 sliceId = 0 来表示
 3. 根据文件 size 截断 Chunk
 
 现假设 Chunk 0 中有 3 个 Slices，分别为：
@@ -809,19 +807,19 @@ Block 是 JuiceFS 管理数据的基本单元，其大小默认为 4 MiB，且
 - fsname 是文件系统名称
 - “chunks” 为固定字符串，代表 JuiceFS 的数据对象
 - hash 是根据 basename 算出来的哈希值，起到一定的隔离管理的作用
-- basename 是对象的有效名称，格式为 `${sliceID}_${index}_${size}`，其中：
-  - sliceID 为该对象所属 Slice 的 ID，JuiceFS 中每个 Slice 都有一个全局唯一的 ID
+- basename 是对象的有效名称，格式为 `${sliceId}_${index}_${size}`，其中：
+  - sliceId 为该对象所属 Slice 的 ID，JuiceFS 中每个 Slice 都有一个全局唯一的 ID
   - index 是该对象在所属 Slice 中的序号，默认一个 Slice 最多能拆成 16 个 Blocks，因此其取值范围为 [0, 16)
   - size 是该 Block 的大小，默认情况下其取值范围为 (0, 4 MiB]
 
-目前使用的 hash 算法有两种，以 basename 中的 sliceID 为参数，根据文件系统格式化时的 [HashPrefix](#3.1.1-Setting) 配置选择：
+目前使用的 hash 算法有两种，以 basename 中的 sliceId 为参数，根据文件系统格式化时的 [HashPrefix](#3.1.1-Setting) 配置选择：
 
 ```go
-func hash(sliceID int) string {
+func hash(sliceId int) string {
 	if HashPrefix {
-		return fmt.Sprintf("%02X/%d", sliceID%256, sliceID/1000/1000)
+		return fmt.Sprintf("%02X/%d", sliceId%256, sliceId/1000/1000)
 	}
-	return fmt.Sprintf("%d/%d", sliceID/1000/1000, sliceID/1000)
+	return fmt.Sprintf("%d/%d", sliceId/1000/1000, sliceId/1000)
 }
 ```
 
diff --git a/docs/zh_cn/faq.md b/docs/zh_cn/faq.md
index 7a69c974..54d4432d 100644
--- a/docs/zh_cn/faq.md
+++ b/docs/zh_cn/faq.md
@@ -4,11 +4,9 @@
 
 已经支持了绝大部分对象存储，参考这个[列表](guide/how_to_setup_object_storage.md#支持的存储服务)。如果它跟 S3 兼容的话，也可以当成 S3 来使用。否则，请创建一个 issue 来增加支持。
 
-## 是否可以使用 Redis 集群版作为元数据引擎？
+## 支持哨兵或者集群模式的 Redis 作为 JuiceFS 的元数据引擎吗？
 
-可以。自 [v1.0.0 Beta3](https://github.com/juicedata/juicefs/releases/tag/v1.0.0-beta3) 版本开始 JuiceFS 支持使用 [Redis 集群版](https://redis.io/docs/manual/scaling)作为元数据引擎，不过需要注意的是 Redis 集群版要求一个事务中所有操作的 key 必须在同一个 hash slot 中，因此一个 JuiceFS 文件系统只能使用一个 hash slot。
-
-请查看[「Redis 最佳实践」](administration/metadata/redis_best_practices.md)了解更多信息。
+支持，另外这里还有一篇 Redis 作为 JuiceFS 元数据引擎的[最佳实践文档](administration/metadata/redis_best_practices.md)可供参考。
 
 ## JuiceFS 与 XXX 的区别是什么？
 
@@ -20,13 +18,39 @@ JuiceFS 是一个分布式文件系统，元数据访问的延时取决于挂载
 
 JuiceFS 内置多级缓存（主动失效），一旦缓存预热好，访问的延时和吞吐量非常接近单机文件系统的性能（FUSE 会带来少量的开销）。
 
+## 为什么在对象存储中看不到那些已经存入 JuiceFS 的原始文件？
+
+使用 JuiceFS，文件最终会被拆分成 Chunks、Slices 和 Blocks 存储在对象存储。因此，你会发现在对象存储平台的文件浏览器中找不到存入 JuiceFS 的原始文件，存储桶中只有一个 chunks 目录和一堆数字编号的目录和文件。不要惊慌，这正是 JuiceFS 文件系统高效运作的秘诀！详情请参考[「JuiceFS 如何存储文件」](introduction/architecture.md#如何存储文件)。
+
 ## JuiceFS 支持随机读写吗？
 
 支持，包括通过 mmap 等进行的随机读写。目前 JuiceFS 主要是对顺序读写进行了大量优化，对随机读写的优化也在进行中。如果想要更好的随机读性能，建议关闭压缩（[`--compress none`](reference/command_reference.md#juicefs-format)）。
 
+## JuiceFS 支持随机写的实现原理是什么？
+
+JuiceFS 不将原始文件存入对象存储，而是将其按照某个大小（默认为 4MiB）拆分为 N 个数据块（Block）后，上传到对象存储，然后将数据块的 ID 存入元数据引擎。随机写的时候，逻辑上是要覆盖原本的内容，实际上是把**要覆盖的数据块**的元数据标记为旧数据，同时只上传随机写时产生的**新数据块**到对象存储，并将**新数据块**对应的元数据更新到元数据引擎中。
+
+当读取被覆盖部分的数据时，根据**最新的元数据**，从随机写时上传的**新数据块**读取即可，同时**旧数据块**可能会被后台运行的垃圾回收任务自动清理。这样就将随机写的复杂度转移到读的复杂度上，。
+
+这个只是很粗略的实现逻辑介绍，具体的读写流程非常复杂，可以研读[「JuiceFS 内部实现」](development/data_structures.md)与[「读写请求处理流程」](introduction/io_processing.md)这两篇文档并结合代码一起梳理。
+
+## 为什么我在挂载点删除了文件，但是对象存储占用空间没有变化或者变化很小？
+
+第一个原因是你可能开启了回收站特性。为了保证数据安全回收站默认开启，删除的文件其实被放到了回收站，实际并没有被删除，所以对象存储大小不会变化。回收站的保留时间可以通过 `juicefs format` 指定或者通过 `juicefs config` 修改。请参考[「回收站」](security/trash.md)文档了解更多信息。
+
+第二个原因是 JuiceFS 是异步删除对象存储中的数据，所以对象存储的空间变化会慢一点。如果你需要立即清理对象存储中需要被删除的数据，可以尝试运行 [`juicefs gc`](reference/command_reference.md#juicefs-gc) 命令。
+
+## 为什么挂载点显示的大小与对象存储占用空间存在差异？
+
+通过[「JuiceFS 支持随机写的实现原理是什么？」](#juicefs-支持随机写的实现原理是什么)这个问题的答案可以推断出，对象存储的占用空间大部分情况下是大于等于实际大小的，尤其是短时间内进行大量的覆盖写产生许多文件碎片后。这些碎片在未触发合并与回收前其仍旧占用着对象存储的空间。不过也不必担心这些碎片一直占用空间，因为在每次读／写文件的时候都会检查并在必要的时候触发该文件相关碎片的整理工作。另外你可以通过 `juicefs gc —-compact -—delete` 命令手动触发合并与回收。
+
+另外如果 JuiceFS 文件系统开启了压缩功能（默认不开启），那么对象存储上存储的对象有可能比实际文件大小更小（取决于不同类型文件的压缩比）。
+
+如果以上因素都已经排除，请检查你使用的对象存储的[存储类型](guide/how_to_setup_object_storage.md#存储类型)是什么，云服务商可能会针对某些存储类型设置最小计量单位。例如阿里云 OSS 低频访问存储的[最小计量单位](https://help.aliyun.com/document_detail/173534.html)是 64KB，如果单个文件小于 64KB 也会按照 64KB 计算。
+
 ## 数据更新什么时候会对其它客户端可见？
 
-所有的元数据更新都是立即对其它客户端可见。JuiceFS 保证关闭再打开（close-to-open）一致性，请查看[「一致性」](guide/cache_management.md#一致性)了解更多信息。
+所有的元数据更新都是立即对其它客户端可见。JuiceFS 保证关闭再打开（close-to-open）一致性，请查看[「一致性」](guide/cache_management.md#数据一致性)了解更多信息。
 
 通过 `write()` 新写入的数据会缓存在内核和客户端中，可以被当前机器的其它进程看到，其它机器暂时看不到。
 
@@ -50,6 +74,10 @@ JuiceFS 内置多级缓存（主动失效），一旦缓存预热好，访问的
 
 请使用 [`juicefs umount`](reference/command_reference.md#juicefs-umount) 命令卸载。
 
+## 卸载挂载点时报 `Resource busy -- try 'diskutil unmount'` 错误
+
+这代表挂载点下的某个文件或者目录正在被使用，无法直接 `umount`，可以检查（如通过 `lsof` 命令）是否有打开的终端正位于 JuiceFS 挂载点的某个目录，或者某个应用程序正在处理挂载点中的文件。如果有，则退出终端或应用程序后再尝试使用 `juicefs umount` 命令卸载文件系统。
+
 ## 怎么升级 JuiceFS 客户端？
 
 首先请卸载 JuiceFS 文件系统，然后使用新版本的客户端重新挂载。
@@ -108,3 +136,41 @@ uid=1201(alice) gid=500(staff) groups=500(staff)
 ```
 
 阅读文档[「多主机间同步账户」](administration/sync_accounts_between_multiple_hosts.md)解决这个问题。
+
+## JuiceFS 除了挂载外还支持哪些方式访问数据？
+
+除了挂载外，还支持以下几种方式：
+
+- Kuberenetes CSI 驱动：通过 Kubernetes CSI 驱动的方式将 JuiceFS 作为 Kubernetes 集群的存储层，详情请参考[「Kubernetes 使用 JuiceFS」](deployment/how_to_use_on_kubernetes.md)。
+- Hadoop Java SDK：方便在 Hadoop 体系中使用兼容 HDFS 接口的 Java 客户端访问 JuiceFS。详情请参考[「Hadoop 使用 JuiceFS」](deployment/hadoop_java_sdk.md)。
+- S3 网关：通过 S3 协议访问 JuiceFS，详情请参考[「配置 JuiceFS S3 网关」](deployment/s3_gateway.md)。
+- Docker Volume 插件：在 Docker 中方便使用 JuiceFS 的方式，详情请参考[「Docker 使用 JuiceFS」](deployment/juicefs_on_docker.md)。
+- WebDAV 网关：通过 WebDAV 协议访问 JuiceFS
+
+## JuiceFS 的日志在哪里？
+
+不同类型的 JuiceFS 客户端获取日志的方式也不同，详情请参考[「客户端日志」](administration/fault_diagnosis_and_analysis.md#客户端日志)文档。
+
+## 如何销毁一个文件系统？
+
+使用 `juicefs destroy` 命令销毁一个文件系统，该命令将会清空元数据引擎与对象存储中的相关数据。关于该命令的使用详情请参考[文档](administration/destroy.md)。
+
+## JuiceFS S3 网关支持多用户管理等高级功能吗？
+
+JuiceFS 内置的 `gateway` 子命令不支持多用户管理等功能，只提供基本的 S3 网关功能。如果需要使用这些高级功能，请参考[文档](deployment/s3_gateway.md#使用功能更完整的-s3-网关)。
+
+## JuiceFS 支持使用对象存储中的某个目录作为 `—-bucket` 选项的值吗？
+
+到 JuiceFS 1.0 为止，还不支持该功能。
+
+## JuiceFS 支持访问对象存储中已经存在的数据吗？
+
+到 JuiceFS 1.0 为止，还不支持该功能。
+
+## JuiceFS 目前支持分布式缓存吗？
+
+到 JuiceFS 1.0 为止，还不支持该功能。
+
+## JuiceFS 目前有 SDK 可以使用吗？
+
+截止到 JuiceFS 1.0 发布，社区有两个 SDK，一个是 Juicedata 官方维护的 HDFS 接口高度兼容的 [Java SDK](deployment/hadoop_java_sdk.md)，另一个是由社区用户维护的 [Python SDK](https://github.com/megvii-research/juicefs-python)。
diff --git a/docs/zh_cn/guide/mount_at_boot.md b/docs/zh_cn/guide/mount_at_boot.md
new file mode 100644
index 00000000..f9215d4d
--- /dev/null
+++ b/docs/zh_cn/guide/mount_at_boot.md
@@ -0,0 +1,109 @@
+---
+sidebar_label: 启动时自动挂载 JuiceFS
+sidebar_position: 2
+slug: /mount_juicefs_at_boot_time
+---
+
+# 启动时自动挂载 JuiceFS
+
+这是一篇关于如何在启动时自动挂载 JuiceFS 的指南。
+
+## Linux
+
+拷贝 `juicefs` 为 `/sbin/mount.juicefs`，然后按照下面的格式添加一行到 `/etc/fstab`：
+
+```
+<META-URL>    <MOUNTPOINT>       juicefs     _netdev[,<MOUNT-OPTIONS>]     0  0
+```
+
+`<META-URL>` 的格式请参考[「如何设置元数据引擎」](how_to_setup_metadata_engine.md)文档，比如 `redis://localhost:6379/1`。然后用你希望 JuiceFS 挂载的路径替换 `<MOUNTPOINT>` ，比如 `/jfs`。如果你想要设置[挂载选项](../reference/command_reference.md#juicefs-mount)，用逗号分隔选项列表并替换 `[,<MOUNT-OPTIONS>]` 。下面是一个示例：
+
+```
+redis://localhost:6379/1    /jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=204800     0  0
+```
+
+:::tip 提示
+默认情况下，CentOS 6 在启动后不会自动挂载网络文件系统，你可以使用下面的命令开启它：
+
+```bash
+sudo chkconfig --add netfs
+```
+:::
+
+## macOS
+
+在 `~/Library/LaunchAgents` 下创建名为 `io.juicefs.<NAME>.plist` 的文件。替换 `<NAME>` 为 JuiceFS 文件系统的名字。添加如下内容到文件中（再次替换 `NAME`、`PATH-TO-JUICEFS`、`META-URL`、`MOUNTPOINT` 和 `MOUNT-OPTIONS` 为适当的值）：
+
+```xml title="io.juicefs.<NAME>.plist"
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>Label</key>
+        <string>io.juicefs.NAME</string>
+        <key>ProgramArguments</key>
+        <array>
+                <string>PATH-TO-JUICEFS</string>
+                <string>mount</string>
+                <string>META-URL</string>
+                <string>MOUNTPOINT</string>
+                <string>MOUNT-OPTIONS</string>
+        </array>
+        <key>RunAtLoad</key>
+        <true/>
+</dict>
+</plist>
+```
+
+:::tip 提示
+如果有多个挂载选项可以分为多行依次设置，例如：
+
+```xml
+                <string>--max-uploads</string>
+                <string>50</string>
+                <string>--cache-size</string>
+                <string>204800</string>
+```
+:::
+
+使用以下命令加载上一步创建的文件，并测试加载是否成功。**请确保元数据引擎已正常运行。**
+
+```bash
+$ launchctl load ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
+$ launchctl start ~/Library/LaunchAgents/io.juicefs.<NAME>
+$ ls <MOUNTPOINT>
+```
+
+如果挂载失败，可以将以下配置添加到 `io.juicefs.<NAME>.plist` 文件来调试：
+
+```xml title="io.juicefs.<NAME>.plist"
+        <key>StandardOutPath</key>
+        <string>/tmp/juicefs.out</string>
+        <key>StandardErrorPath</key>
+        <string>/tmp/juicefs.err</string>
+```
+
+使用以下命令重新加载最新的配置并检查输出：
+
+```bash
+$ launchctl unload ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
+$ launchctl load ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
+$ cat /tmp/juicefs.out
+$ cat /tmp/juicefs.err
+```
+
+如果你是使用 Homebrew 安装的 Redis 服务，你可以使用以下命令让其在机器启动时启动它：
+
+```bash
+brew services start redis
+```
+
+然后添加以下配置到 `io.juicefs.<NAME>.plist` 文件确保 Redis 服务已经启动：
+
+```xml title="io.juicefs.<NAME>.plist"
+        <key>KeepAlive</key>
+        <dict>
+                <key>OtherJobEnabled</key>
+                <string>homebrew.mxcl.redis</string>
+        </dict>
+```
diff --git a/docs/zh_cn/mount_at_boot.md b/docs/zh_cn/mount_at_boot.md
deleted file mode 100644
index bab25b31..00000000
--- a/docs/zh_cn/mount_at_boot.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# Mount JuiceFS at Boot
-
-This is a guide about how to mount JuiceFS automatically at boot.
-
-## Linux
-
-Copy `juicefs` as `/sbin/mount.juicefs`, then edit `/etc/fstab` with following line:
-
-```
-<META-URL>    <MOUNTPOINT>       juicefs     _netdev[,<MOUNT-OPTIONS>]     0  0
-```
-
-The format of `<META-URL>` is `redis://<user>:<password>@<host>:<port>/<db>`, e.g. `redis://localhost:6379/1`. And replace `<MOUNTPOINT>` with specific path you wanna mount JuiceFS to, e.g. `/jfs`. If you need set [mount options](reference/command_reference.md#juicefs-mount), replace `[,<MOUNT-OPTIONS>]` with comma separated options list. The following line is an example:
-
-```
-redis://localhost:6379/1    /jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=2048     0  0
-```
-
-**Note: By default, CentOS 6 will NOT mount network file system after boot, run following command to enable it:**
-
-```bash
-sudo chkconfig --add netfs
-```
-
-## macOS
-
-Create a file named `io.juicefs.<NAME>.plist` under `~/Library/LaunchAgents`. Replace `<NAME>` with JuiceFS volume name. Add following contents to the file (again, replace `NAME`, `PATH-TO-JUICEFS`, `META-URL` and `MOUNTPOINT` with appropriate value):
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-        <key>Label</key>
-        <string>io.juicefs.NAME</string>
-        <key>ProgramArguments</key>
-        <array>
-                <string>PATH-TO-JUICEFS</string>
-                <string>mount</string>
-                <string>META-URL</string>
-                <string>MOUNTPOINT</string>
-        </array>
-        <key>RunAtLoad</key>
-        <true/>
-</dict>
-</plist>
-```
-
-Use following commands to load the file created in the previous step and test whether the loading is successful. **Please ensure Redis server is already running.**
-
-```bash
-$ launchctl load ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
-$ launchctl start ~/Library/LaunchAgents/io.juicefs.<NAME>
-$ ls <MOUNTPOINT>
-```
-
-If mount failed, you can add following configuration to `io.juicefs.<NAME>.plist` file for debug purpose:
-
-```xml
-        <key>StandardOutPath</key>
-        <string>/tmp/juicefs.out</string>
-        <key>StandardErrorPath</key>
-        <string>/tmp/juicefs.err</string>
-```
-
-Use following commands to reload the latest configuration and inspect the output:
-
-```bash
-$ launchctl unload ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
-$ launchctl load ~/Library/LaunchAgents/io.juicefs.<NAME>.plist
-$ cat /tmp/juicefs.out
-$ cat /tmp/juicefs.err
-```
-
-If you install Redis server by Homebrew, you could use following command to start it at boot:
-
-```bash
-$ brew services start redis
-```
-
-Then add following configuration to `io.juicefs.<NAME>.plist` file for ensure Redis server is loaded:
-
-```xml
-        <key>KeepAlive</key>
-        <dict>
-                <key>OtherJobEnabled</key>
-                <string>homebrew.mxcl.redis</string>
-        </dict>
-```
-
diff --git a/pkg/chunk/cached_store.go b/pkg/chunk/cached_store.go
index 4bdf1b59..33985069 100644
--- a/pkg/chunk/cached_store.go
+++ b/pkg/chunk/cached_store.go
@@ -48,68 +48,68 @@ type pendingItem struct {
 	fpath string
 }
 
-// chunk for read only
-type rChunk struct {
+// slice for read and remove
+type rSlice struct {
 	id     uint64
 	length int
 	store  *cachedStore
 }
 
-func chunkForRead(id uint64, length int, store *cachedStore) *rChunk {
-	return &rChunk{id, length, store}
+func sliceForRead(id uint64, length int, store *cachedStore) *rSlice {
+	return &rSlice{id, length, store}
 }
 
-func (c *rChunk) blockSize(indx int) int {
-	bsize := c.length - indx*c.store.conf.BlockSize
-	if bsize > c.store.conf.BlockSize {
-		bsize = c.store.conf.BlockSize
+func (s *rSlice) blockSize(indx int) int {
+	bsize := s.length - indx*s.store.conf.BlockSize
+	if bsize > s.store.conf.BlockSize {
+		bsize = s.store.conf.BlockSize
 	}
 	return bsize
 }
 
-func (c *rChunk) key(indx int) string {
-	if c.store.conf.HashPrefix {
-		return fmt.Sprintf("chunks/%02X/%v/%v_%v_%v", c.id%256, c.id/1000/1000, c.id, indx, c.blockSize(indx))
+func (s *rSlice) key(indx int) string {
+	if s.store.conf.HashPrefix {
+		return fmt.Sprintf("chunks/%02X/%v/%v_%v_%v", s.id%256, s.id/1000/1000, s.id, indx, s.blockSize(indx))
 	}
-	return fmt.Sprintf("chunks/%v/%v/%v_%v_%v", c.id/1000/1000, c.id/1000, c.id, indx, c.blockSize(indx))
+	return fmt.Sprintf("chunks/%v/%v/%v_%v_%v", s.id/1000/1000, s.id/1000, s.id, indx, s.blockSize(indx))
 }
 
-func (c *rChunk) index(off int) int {
-	return off / c.store.conf.BlockSize
+func (s *rSlice) index(off int) int {
+	return off / s.store.conf.BlockSize
 }
 
-func (c *rChunk) keys() []string {
-	if c.length <= 0 {
+func (s *rSlice) keys() []string {
+	if s.length <= 0 {
 		return nil
 	}
-	lastIndx := (c.length - 1) / c.store.conf.BlockSize
+	lastIndx := (s.length - 1) / s.store.conf.BlockSize
 	keys := make([]string, lastIndx+1)
 	for i := 0; i <= lastIndx; i++ {
-		keys[i] = c.key(i)
+		keys[i] = s.key(i)
 	}
 	return keys
 }
 
-func (c *rChunk) ReadAt(ctx context.Context, page *Page, off int) (n int, err error) {
+func (s *rSlice) ReadAt(ctx context.Context, page *Page, off int) (n int, err error) {
 	p := page.Data
 	if len(p) == 0 {
 		return 0, nil
 	}
-	if off >= c.length {
+	if off >= s.length {
 		return 0, io.EOF
 	}
 
-	indx := c.index(off)
-	boff := off % c.store.conf.BlockSize
-	blockSize := c.blockSize(indx)
+	indx := s.index(off)
+	boff := off % s.store.conf.BlockSize
+	blockSize := s.blockSize(indx)
 	if boff+len(p) > blockSize {
 		// read beyond currend page
 		var got int
 		for got < len(p) {
 			// aligned to current page
-			l := utils.Min(len(p)-got, c.blockSize(c.index(off))-off%c.store.conf.BlockSize)
+			l := utils.Min(len(p)-got, s.blockSize(s.index(off))-off%s.store.conf.BlockSize)
 			pp := page.Slice(got, l)
-			n, err = c.ReadAt(ctx, pp, off)
+			n, err = s.ReadAt(ctx, pp, off)
 			pp.Release()
 			if err != nil {
 				return got + n, err
@@ -123,17 +123,17 @@ func (c *rChunk) ReadAt(ctx context.Context, page *Page, off int) (n int, err er
 		return got, nil
 	}
 
-	key := c.key(indx)
-	if c.store.conf.CacheSize > 0 {
+	key := s.key(indx)
+	if s.store.conf.CacheSize > 0 {
 		start := time.Now()
-		r, err := c.store.bcache.load(key)
+		r, err := s.store.bcache.load(key)
 		if err == nil {
 			n, err = r.ReadAt(p, int64(boff))
 			_ = r.Close()
 			if err == nil {
-				c.store.cacheHits.Add(1)
-				c.store.cacheHitBytes.Add(float64(n))
-				c.store.cacheReadHist.Observe(time.Since(start).Seconds())
+				s.store.cacheHits.Add(1)
+				s.store.cacheHitBytes.Add(float64(n))
+				s.store.cacheReadHist.Observe(time.Since(start).Seconds())
 				return n, nil
 			}
 			if f, ok := r.(*os.File); ok {
@@ -143,16 +143,16 @@ func (c *rChunk) ReadAt(ctx context.Context, page *Page, off int) (n int, err er
 		}
 	}
 
-	c.store.cacheMiss.Add(1)
-	c.store.cacheMissBytes.Add(float64(len(p)))
+	s.store.cacheMiss.Add(1)
+	s.store.cacheMissBytes.Add(float64(len(p)))
 
-	if c.store.seekable && boff > 0 && len(p) <= blockSize/4 {
-		if c.store.downLimit != nil {
-			c.store.downLimit.Wait(int64(len(p)))
+	if s.store.seekable && boff > 0 && len(p) <= blockSize/4 {
+		if s.store.downLimit != nil {
+			s.store.downLimit.Wait(int64(len(p)))
 		}
 		// partial read
 		st := time.Now()
-		in, err := c.store.storage.Get(key, int64(boff), int64(len(p)))
+		in, err := s.store.storage.Get(key, int64(boff), int64(len(p)))
 		if err == nil {
 			n, err = io.ReadFull(in, p)
 			_ = in.Close()
@@ -162,17 +162,17 @@ func (c *rChunk) ReadAt(ctx context.Context, page *Page, off int) (n int, err er
 		if used > SlowRequest {
 			logger.Infof("slow request: GET %s (%v, %.3fs)", key, err, used.Seconds())
 		}
-		c.store.objectDataBytes.WithLabelValues("GET").Add(float64(n))
-		c.store.objectReqsHistogram.WithLabelValues("GET").Observe(used.Seconds())
-		c.store.fetcher.fetch(key)
+		s.store.objectDataBytes.WithLabelValues("GET").Add(float64(n))
+		s.store.objectReqsHistogram.WithLabelValues("GET").Observe(used.Seconds())
+		s.store.fetcher.fetch(key)
 		if err == nil {
 			return n, nil
 		} else {
-			c.store.objectReqErrors.Add(1)
+			s.store.objectReqErrors.Add(1)
 		}
 	}
 
-	block, err := c.store.group.Execute(key, func() (*Page, error) {
+	block, err := s.store.group.Execute(key, func() (*Page, error) {
 		tmp := page
 		if boff > 0 || len(p) < blockSize {
 			tmp = NewOffPage(blockSize)
@@ -182,8 +182,8 @@ func (c *rChunk) ReadAt(ctx context.Context, page *Page, off int) (n int, err er
 		tmp.Acquire()
 		err := utils.WithTimeout(func() error {
 			defer tmp.Release()
-			return c.store.load(key, tmp, c.store.shouldCache(blockSize), false)
-		}, c.store.conf.GetTimeout)
+			return s.store.load(key, tmp, s.store.shouldCache(blockSize), false)
+		}, s.store.conf.GetTimeout)
 		return tmp, err
 	})
 	defer block.Release()
@@ -196,48 +196,48 @@ func (c *rChunk) ReadAt(ctx context.Context, page *Page, off int) (n int, err er
 	return len(p), nil
 }
 
-func (c *rChunk) delete(indx int) error {
-	key := c.key(indx)
+func (s *rSlice) delete(indx int) error {
+	key := s.key(indx)
 	st := time.Now()
-	err := c.store.storage.Delete(key)
+	err := s.store.storage.Delete(key)
 	used := time.Since(st)
 	logger.Debugf("DELETE %v (%v, %.3fs)", key, err, used.Seconds())
 	if used > SlowRequest {
 		logger.Infof("slow request: DELETE %v (%v, %.3fs)", key, err, used.Seconds())
 	}
-	c.store.objectReqsHistogram.WithLabelValues("DELETE").Observe(used.Seconds())
+	s.store.objectReqsHistogram.WithLabelValues("DELETE").Observe(used.Seconds())
 	if err != nil {
-		c.store.objectReqErrors.Add(1)
+		s.store.objectReqErrors.Add(1)
 	}
 	return err
 }
 
-func (c *rChunk) Remove() error {
-	if c.length == 0 {
+func (s *rSlice) Remove() error {
+	if s.length == 0 {
 		// no block
 		return nil
 	}
 
-	lastIndx := (c.length - 1) / c.store.conf.BlockSize
+	lastIndx := (s.length - 1) / s.store.conf.BlockSize
 	for i := 0; i <= lastIndx; i++ {
 		// there could be multiple clients try to remove the same chunk in the same time,
 		// any of them should succeed if any blocks is removed
-		key := c.key(i)
-		c.store.removePending(key)
-		c.store.bcache.remove(key)
+		key := s.key(i)
+		s.store.removePending(key)
+		s.store.bcache.remove(key)
 	}
 
-	if c.store.conf.MaxDeletes == 0 {
+	if s.store.conf.MaxDeletes == 0 {
 		return errors.New("skip deleting objects because MaxDeletes is 0")
 	}
 	var err error
-	c.store.currentDelete <- struct{}{}
+	s.store.currentDelete <- struct{}{}
 	for i := 0; i <= lastIndx; i++ {
-		if e := c.delete(i); e != nil {
+		if e := s.delete(i); e != nil {
 			err = e
 		}
 	}
-	<-c.store.currentDelete
+	<-s.store.currentDelete
 	return err
 }
 
@@ -267,9 +267,9 @@ func freePage(p *Page) {
 	}
 }
 
-// chunk for write only
-type wChunk struct {
-	rChunk
+// slice for write only
+type wSlice struct {
+	rSlice
 	pages       [][]*Page
 	uploaded    int
 	errors      chan error
@@ -277,48 +277,48 @@ type wChunk struct {
 	pendings    int
 }
 
-func chunkForWrite(id uint64, store *cachedStore) *wChunk {
-	return &wChunk{
-		rChunk: rChunk{id, 0, store},
+func sliceForWrite(id uint64, store *cachedStore) *wSlice {
+	return &wSlice{
+		rSlice: rSlice{id, 0, store},
 		pages:  make([][]*Page, chunkSize/store.conf.BlockSize),
 		errors: make(chan error, chunkSize/store.conf.BlockSize),
 	}
 }
 
-func (c *wChunk) SetID(id uint64) {
-	c.id = id
+func (s *wSlice) SetID(id uint64) {
+	s.id = id
 }
 
-func (c *wChunk) WriteAt(p []byte, off int64) (n int, err error) {
+func (s *wSlice) WriteAt(p []byte, off int64) (n int, err error) {
 	if int(off)+len(p) > chunkSize {
 		return 0, fmt.Errorf("write out of chunk boudary: %d > %d", int(off)+len(p), chunkSize)
 	}
-	if off < int64(c.uploaded) {
-		return 0, fmt.Errorf("Cannot overwrite uploaded block: %d < %d", off, c.uploaded)
+	if off < int64(s.uploaded) {
+		return 0, fmt.Errorf("Cannot overwrite uploaded block: %d < %d", off, s.uploaded)
 	}
 
 	// Fill previous blocks with zeros
-	if c.length < int(off) {
-		zeros := make([]byte, int(off)-c.length)
-		_, _ = c.WriteAt(zeros, int64(c.length))
+	if s.length < int(off) {
+		zeros := make([]byte, int(off)-s.length)
+		_, _ = s.WriteAt(zeros, int64(s.length))
 	}
 
 	for n < len(p) {
-		indx := c.index(int(off) + n)
-		boff := (int(off) + n) % c.store.conf.BlockSize
+		indx := s.index(int(off) + n)
+		boff := (int(off) + n) % s.store.conf.BlockSize
 		var bs = pageSize
-		if indx > 0 || bs > c.store.conf.BlockSize {
-			bs = c.store.conf.BlockSize
+		if indx > 0 || bs > s.store.conf.BlockSize {
+			bs = s.store.conf.BlockSize
 		}
 		bi := boff / bs
 		bo := boff % bs
 		var page *Page
-		if bi < len(c.pages[indx]) {
-			page = c.pages[indx][bi]
+		if bi < len(s.pages[indx]) {
+			page = s.pages[indx][bi]
 		} else {
 			page = allocPage(bs)
 			page.Data = page.Data[:0]
-			c.pages[indx] = append(c.pages[indx], page)
+			s.pages[indx] = append(s.pages[indx], page)
 		}
 		left := len(p) - n
 		if bo+left > bs {
@@ -328,8 +328,8 @@ func (c *wChunk) WriteAt(p []byte, off int64) (n int, err error) {
 		}
 		n += copy(page.Data[bo:], p[n:])
 	}
-	if int(off)+n > c.length {
-		c.length = int(off) + n
+	if int(off)+n > s.length {
+		s.length = int(off) + n
 	}
 	return n, nil
 }
@@ -357,8 +357,8 @@ func (store *cachedStore) put(key string, p *Page) error {
 	}, store.conf.PutTimeout)
 }
 
-func (store *cachedStore) upload(key string, block *Page, c *wChunk) error {
-	sync := c != nil
+func (store *cachedStore) upload(key string, block *Page, s *wSlice) error {
+	sync := s != nil
 	blen := len(block.Data)
 	bufSize := store.compressor.CompressBound(blen)
 	var buf *Page
@@ -386,7 +386,7 @@ func (store *cachedStore) upload(key string, block *Page, c *wChunk) error {
 	}
 	for ; try < max; try++ {
 		time.Sleep(time.Second * time.Duration(try*try))
-		if c != nil && c.uploadError != nil {
+		if s != nil && s.uploadError != nil {
 			err = fmt.Errorf("(cancelled) upload block %s: %s (after %d tries)", key, err, try)
 			break
 		}
@@ -401,12 +401,12 @@ func (store *cachedStore) upload(key string, block *Page, c *wChunk) error {
 	return err
 }
 
-func (c *wChunk) upload(indx int) {
-	blen := c.blockSize(indx)
-	key := c.key(indx)
-	pages := c.pages[indx]
-	c.pages[indx] = nil
-	c.pendings++
+func (s *wSlice) upload(indx int) {
+	blen := s.blockSize(indx)
+	key := s.key(indx)
+	pages := s.pages[indx]
+	s.pages[indx] = nil
+	s.pendings++
 
 	go func() {
 		var block *Page
@@ -424,96 +424,96 @@ func (c *wChunk) upload(indx int) {
 		if off != blen {
 			panic(fmt.Sprintf("block length does not match: %v != %v", off, blen))
 		}
-		if c.store.conf.Writeback {
-			stagingPath, err := c.store.bcache.stage(key, block.Data, c.store.shouldCache(blen))
+		if s.store.conf.Writeback {
+			stagingPath, err := s.store.bcache.stage(key, block.Data, s.store.shouldCache(blen))
 			if err != nil {
 				logger.Warnf("write %s to disk: %s, upload it directly", stagingPath, err)
 			} else {
-				c.errors <- nil
-				if c.store.conf.UploadDelay == 0 {
+				s.errors <- nil
+				if s.store.conf.UploadDelay == 0 {
 					select {
-					case c.store.currentUpload <- true:
-						defer func() { <-c.store.currentUpload }()
-						if err = c.store.upload(key, block, nil); err == nil {
-							c.store.bcache.uploaded(key, blen)
+					case s.store.currentUpload <- true:
+						defer func() { <-s.store.currentUpload }()
+						if err = s.store.upload(key, block, nil); err == nil {
+							s.store.bcache.uploaded(key, blen)
 							if os.Remove(stagingPath) == nil {
-								if m, ok := c.store.bcache.(*cacheManager); ok {
+								if m, ok := s.store.bcache.(*cacheManager); ok {
 									m.stageBlocks.Sub(1)
 									m.stageBlockBytes.Sub(float64(blen))
 								}
 							}
 						} else { // add to delay list and wait for later scanning
-							c.store.addDelayedStaging(key, stagingPath, time.Now().Add(time.Second*30), false)
+							s.store.addDelayedStaging(key, stagingPath, time.Now().Add(time.Second*30), false)
 						}
 						return
 					default:
 					}
 				}
 				block.Release()
-				c.store.addDelayedStaging(key, stagingPath, time.Now(), c.store.conf.UploadDelay == 0)
+				s.store.addDelayedStaging(key, stagingPath, time.Now(), s.store.conf.UploadDelay == 0)
 				return
 			}
 		}
-		c.store.currentUpload <- true
-		defer func() { <-c.store.currentUpload }()
-		c.errors <- c.store.upload(key, block, c)
+		s.store.currentUpload <- true
+		defer func() { <-s.store.currentUpload }()
+		s.errors <- s.store.upload(key, block, s)
 	}()
 }
 
-func (c *wChunk) ID() uint64 {
-	return c.id
+func (s *wSlice) ID() uint64 {
+	return s.id
 }
 
-func (c *wChunk) Len() int {
-	return c.length
+func (s *wSlice) Len() int {
+	return s.length
 }
 
-func (c *wChunk) FlushTo(offset int) error {
-	if offset < c.uploaded {
-		panic(fmt.Sprintf("Invalid offset: %d < %d", offset, c.uploaded))
+func (s *wSlice) FlushTo(offset int) error {
+	if offset < s.uploaded {
+		panic(fmt.Sprintf("Invalid offset: %d < %d", offset, s.uploaded))
 	}
-	for i, block := range c.pages {
-		start := i * c.store.conf.BlockSize
-		end := start + c.store.conf.BlockSize
-		if start >= c.uploaded && end <= offset {
+	for i, block := range s.pages {
+		start := i * s.store.conf.BlockSize
+		end := start + s.store.conf.BlockSize
+		if start >= s.uploaded && end <= offset {
 			if block != nil {
-				c.upload(i)
+				s.upload(i)
 			}
-			c.uploaded = end
+			s.uploaded = end
 		}
 	}
 
 	return nil
 }
 
-func (c *wChunk) Finish(length int) error {
-	if c.length != length {
-		return fmt.Errorf("Length mismatch: %v != %v", c.length, length)
+func (s *wSlice) Finish(length int) error {
+	if s.length != length {
+		return fmt.Errorf("Length mismatch: %v != %v", s.length, length)
 	}
 
-	n := (length-1)/c.store.conf.BlockSize + 1
-	if err := c.FlushTo(n * c.store.conf.BlockSize); err != nil {
+	n := (length-1)/s.store.conf.BlockSize + 1
+	if err := s.FlushTo(n * s.store.conf.BlockSize); err != nil {
 		return err
 	}
-	for i := 0; i < c.pendings; i++ {
-		if err := <-c.errors; err != nil {
-			c.uploadError = err
+	for i := 0; i < s.pendings; i++ {
+		if err := <-s.errors; err != nil {
+			s.uploadError = err
 			return err
 		}
 	}
 	return nil
 }
 
-func (c *wChunk) Abort() {
-	for i := range c.pages {
-		for _, b := range c.pages[i] {
+func (s *wSlice) Abort() {
+	for i := range s.pages {
+		for _, b := range s.pages[i] {
 			freePage(b)
 		}
-		c.pages[i] = nil
+		s.pages[i] = nil
 	}
 	// delete uploaded blocks
-	c.length = c.uploaded
-	_ = c.Remove()
+	s.length = s.uploaded
+	_ = s.Remove()
 }
 
 // Config contains options for cachedStore
@@ -879,21 +879,21 @@ func (store *cachedStore) uploader() {
 	}
 }
 
-func (store *cachedStore) NewReader(chunkid uint64, length int) Reader {
-	return chunkForRead(chunkid, length, store)
+func (store *cachedStore) NewReader(id uint64, length int) Reader {
+	return sliceForRead(id, length, store)
 }
 
-func (store *cachedStore) NewWriter(chunkid uint64) Writer {
-	return chunkForWrite(chunkid, store)
+func (store *cachedStore) NewWriter(id uint64) Writer {
+	return sliceForWrite(id, store)
 }
 
-func (store *cachedStore) Remove(chunkid uint64, length int) error {
-	r := chunkForRead(chunkid, length, store)
+func (store *cachedStore) Remove(id uint64, length int) error {
+	r := sliceForRead(id, length, store)
 	return r.Remove()
 }
 
-func (store *cachedStore) FillCache(chunkid uint64, length uint32) error {
-	r := chunkForRead(chunkid, int(length), store)
+func (store *cachedStore) FillCache(id uint64, length uint32) error {
+	r := sliceForRead(id, int(length), store)
 	keys := r.keys()
 	var err error
 	for _, k := range keys {
diff --git a/pkg/chunk/chunk.go b/pkg/chunk/chunk.go
index 8ee75a1d..29bcff8e 100644
--- a/pkg/chunk/chunk.go
+++ b/pkg/chunk/chunk.go
@@ -28,16 +28,16 @@ type Reader interface {
 type Writer interface {
 	io.WriterAt
 	ID() uint64
-	SetID(chunkid uint64)
+	SetID(id uint64)
 	FlushTo(offset int) error
 	Finish(length int) error
 	Abort()
 }
 
 type ChunkStore interface {
-	NewReader(chunkid uint64, length int) Reader
-	NewWriter(chunkid uint64) Writer
-	Remove(chunkid uint64, length int) error
-	FillCache(chunkid uint64, length uint32) error
+	NewReader(id uint64, length int) Reader
+	NewWriter(id uint64) Writer
+	Remove(id uint64, length int) error
+	FillCache(id uint64, length uint32) error
 	UsedMemory() int64
 }
diff --git a/pkg/meta/base.go b/pkg/meta/base.go
index 0d9d2cee..be978333 100644
--- a/pkg/meta/base.go
+++ b/pkg/meta/base.go
@@ -34,7 +34,7 @@ import (
 
 const (
 	inodeBatch    = 100
-	chunkIDBatch  = 1000
+	sliceIdBatch  = 1000
 	minUpdateTime = time.Millisecond * 10
 	nlocks        = 1024
 )
@@ -59,7 +59,7 @@ type engine interface {
 	doDeleteFileData(inode Ino, length uint64)
 	doCleanupSlices()
 	doCleanupDelayedSlices(edge int64, limit int) (int, error)
-	doDeleteSlice(chunkid uint64, size uint32) error
+	doDeleteSlice(id uint64, size uint32) error
 
 	doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno
 	doLookup(ctx Context, parent Ino, name string, inode *Ino, attr *Attr) syscall.Errno
@@ -101,7 +101,7 @@ type baseMeta struct {
 
 	freeMu     sync.Mutex
 	freeInodes freeID
-	freeChunks freeID
+	freeSlices freeID
 
 	usedSpaceG  prometheus.Gauge
 	usedInodesG prometheus.Gauge
@@ -568,9 +568,9 @@ func (m *baseMeta) marshal(attr *Attr) []byte {
 	return w.Bytes()
 }
 
-func (m *baseMeta) encodeDelayedSlice(chunkid uint64, size uint32) []byte {
+func (m *baseMeta) encodeDelayedSlice(id uint64, size uint32) []byte {
 	w := utils.NewBuffer(8 + 4)
-	w.Put64(chunkid)
+	w.Put64(id)
 	w.Put32(size)
 	return w.Bytes()
 }
@@ -580,7 +580,7 @@ func (m *baseMeta) decodeDelayedSlices(buf []byte, ss *[]Slice) {
 		return
 	}
 	for rb := utils.FromBuffer(buf); rb.HasMore(); {
-		*ss = append(*ss, Slice{Chunkid: rb.Get64(), Size: rb.Get32()})
+		*ss = append(*ss, Slice{Id: rb.Get64(), Size: rb.Get32()})
 	}
 }
 
@@ -840,19 +840,19 @@ func (m *baseMeta) InvalidateChunkCache(ctx Context, inode Ino, indx uint32) sys
 	return 0
 }
 
-func (m *baseMeta) NewChunk(ctx Context, chunkid *uint64) syscall.Errno {
+func (m *baseMeta) NewSlice(ctx Context, id *uint64) syscall.Errno {
 	m.freeMu.Lock()
 	defer m.freeMu.Unlock()
-	if m.freeChunks.next >= m.freeChunks.maxid {
-		v, err := m.en.incrCounter("nextChunk", chunkIDBatch)
+	if m.freeSlices.next >= m.freeSlices.maxid {
+		v, err := m.en.incrCounter("nextChunk", sliceIdBatch)
 		if err != nil {
 			return errno(err)
 		}
-		m.freeChunks.next = uint64(v) - chunkIDBatch
-		m.freeChunks.maxid = uint64(v)
+		m.freeSlices.next = uint64(v) - sliceIdBatch
+		m.freeSlices.maxid = uint64(v)
 	}
-	*chunkid = m.freeChunks.next
-	m.freeChunks.next++
+	*id = m.freeSlices.next
+	m.freeSlices.next++
 	return 0
 }
 
@@ -955,16 +955,16 @@ func (m *baseMeta) tryDeleteFileData(inode Ino, length uint64) {
 	}
 }
 
-func (m *baseMeta) deleteSlice(chunkid uint64, size uint32) {
-	if chunkid == 0 {
+func (m *baseMeta) deleteSlice(id uint64, size uint32) {
+	if id == 0 {
 		return
 	}
-	if err := m.newMsg(DeleteChunk, chunkid, size); err == nil || strings.Contains(err.Error(), "NoSuchKey") || strings.Contains(err.Error(), "not found") {
-		if err = m.en.doDeleteSlice(chunkid, size); err != nil {
-			logger.Errorf("delete slice %d: %s", chunkid, err)
+	if err := m.newMsg(DeleteSlice, id, size); err == nil || strings.Contains(err.Error(), "NoSuchKey") || strings.Contains(err.Error(), "not found") {
+		if err = m.en.doDeleteSlice(id, size); err != nil {
+			logger.Errorf("delete slice %d: %s", id, err)
 		}
 	} else if !strings.Contains(err.Error(), "skip deleting") {
-		logger.Warnf("delete chunk %d (%d bytes): %s", chunkid, size, err)
+		logger.Warnf("delete slice %d (%d bytes): %s", id, size, err)
 	}
 }
 
diff --git a/pkg/meta/dump.go b/pkg/meta/dump.go
index 06971071..0c9fa854 100644
--- a/pkg/meta/dump.go
+++ b/pkg/meta/dump.go
@@ -71,7 +71,8 @@ type DumpedAttr struct {
 }
 
 type DumpedSlice struct {
-	Chunkid uint64 `json:"chunkid"`
+	Chunkid uint64 `json:"chunkid,omitempty"`
+	Id      uint64 `json:"id"`
 	Pos     uint32 `json:"pos,omitempty"`
 	Size    uint32 `json:"size"`
 	Off     uint32 `json:"off,omitempty"`
@@ -408,13 +409,17 @@ func decodeEntry(dec *json.Decoder, parent Ino, cs *DumpedCounters, parents map[
 			if err == nil && len(e.Parents) == 1 {
 				for _, c := range e.Chunks {
 					for _, s := range c.Slices {
-						ck := chunkKey{s.Chunkid, s.Size}
+						if s.Chunkid != 0 && s.Id == 0 {
+							s.Id = s.Chunkid
+							s.Chunkid = 0
+						}
+						ck := chunkKey{s.Id, s.Size}
 						refs[ck]++
 						if addChunk != nil && refs[ck] == 1 {
 							addChunk(&ck)
 						}
-						if cs.NextChunk <= int64(s.Chunkid) {
-							cs.NextChunk = int64(s.Chunkid) + 1
+						if cs.NextChunk <= int64(s.Id) {
+							cs.NextChunk = int64(s.Id) + 1
 						}
 					}
 				}
diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go
index 606a3f3c..6b52d126 100644
--- a/pkg/meta/interface.go
+++ b/pkg/meta/interface.go
@@ -34,8 +34,8 @@ import (
 const (
 	// ChunkSize is size of a chunk
 	ChunkSize = 1 << 26 // 64M
-	// DeleteChunk is a message to delete a chunk from object store.
-	DeleteChunk = 1000
+	// DeleteSlice is a message to delete a slice from object store.
+	DeleteSlice = 1000
 	// CompactChunk is a message to compact a chunk in object store.
 	CompactChunk = 1001
 	// Rmr is a message to remove a directory recursively.
@@ -195,10 +195,10 @@ type Entry struct {
 // Slice is a slice of a chunk.
 // Multiple slices could be combined together as a chunk.
 type Slice struct {
-	Chunkid uint64
-	Size    uint32
-	Off     uint32
-	Len     uint32
+	Id   uint64
+	Size uint32
+	Off  uint32
+	Len  uint32
 }
 
 // Summary represents the total number of files/directories and
@@ -308,9 +308,9 @@ type Meta interface {
 	// Close a file.
 	Close(ctx Context, inode Ino) syscall.Errno
 	// Read returns the list of slices on the given chunk.
-	Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) syscall.Errno
-	// NewChunk returns a new id for new data.
-	NewChunk(ctx Context, chunkid *uint64) syscall.Errno
+	Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno
+	// NewSlice returns an id for new slice.
+	NewSlice(ctx Context, id *uint64) syscall.Errno
 	// Write put a slice of data on top of the given chunk.
 	Write(ctx Context, inode Ino, indx uint32, off uint32, slice Slice) syscall.Errno
 	// InvalidateChunkCache invalidate chunk cache
diff --git a/pkg/meta/metadata-sub.sample b/pkg/meta/metadata-sub.sample
index ee69ea67..3321f24d 100644
--- a/pkg/meta/metadata-sub.sample
+++ b/pkg/meta/metadata-sub.sample
@@ -35,13 +35,13 @@
       "big": {
         "attr": {"inode":6,"type":"regular","mode":420,"uid":501,"gid":0,"atime":1637150857,"mtime":1637150858,"ctime":1637150878,"atimensec":961503000,"mtimensec":961503000,"ctimensec":961503000,"nlink":1,"length":104857600},
         "chunks": [
-          {"index":0,"slices":[{"chunkid":5,"size":67108864,"len":67108864}]},
-          {"index":1,"slices":[{"chunkid":6,"size":37748736,"len":37748736}]}
+          {"index":0,"slices":[{"id":5,"size":67108864,"len":67108864}]},
+          {"index":1,"slices":[{"id":6,"size":37748736,"len":37748736}]}
         ]
       },
       "f11": {
         "attr": {"inode":4,"type":"regular","mode":420,"uid":501,"gid":20,"atime":1623746610,"mtime":1623746610,"ctime":1623746639,"atimensec":591590000,"mtimensec":591590000,"ctimensec":591590000,"nlink":2,"length":12},
-        "chunks": [{"index":0,"slices":[{"chunkid":2,"size":12,"len":12}]}]
+        "chunks": [{"index":0,"slices":[{"id":2,"size":12,"len":12}]}]
       }
     }
   }
diff --git a/pkg/meta/metadata.sample b/pkg/meta/metadata.sample
index a49cf548..87868412 100644
--- a/pkg/meta/metadata.sample
+++ b/pkg/meta/metadata.sample
@@ -55,24 +55,24 @@
           "big": {
             "attr": {"inode":6,"type":"regular","mode":420,"uid":501,"gid":0,"atime":1637150857,"mtime":1637150858,"ctime":1637150878,"atimensec":961503000,"mtimensec":961503000,"ctimensec":961503000,"nlink":1,"length":104857600},
             "chunks": [
-              {"index":0,"slices":[{"chunkid":5,"size":67108864,"len":67108864}]},
-              {"index":1,"slices":[{"chunkid":6,"size":37748736,"len":37748736}]}
+              {"index":0,"slices":[{"id":5,"size":67108864,"len":67108864}]},
+              {"index":1,"slices":[{"id":6,"size":37748736,"len":37748736}]}
             ]
           },
           "f11": {
             "attr": {"inode":4,"type":"regular","mode":420,"uid":501,"gid":20,"atime":1623746610,"mtime":1623746610,"ctime":1623746639,"atimensec":591590000,"mtimensec":591590000,"ctimensec":591590000,"nlink":2,"length":12},
-            "chunks": [{"index":0,"slices":[{"chunkid":2,"size":12,"len":12}]}]
+            "chunks": [{"index":0,"slices":[{"id":2,"size":12,"len":12}]}]
           }
         }
       },
       "f1": {
         "attr": {"inode":2,"type":"regular","mode":420,"uid":501,"gid":20,"atime":1623746580,"mtime":1623746661,"ctime":1623746661,"atimensec":219686000,"mtimensec":219686000,"ctimensec":219686000,"nlink":1,"length":24},
         "xattrs": [{"name":"k","value":"v"}],
-        "chunks": [{"index":0,"slices":[{"chunkid":1,"size":6,"len":6},{"chunkid":2,"size":12,"len":12},{"chunkid":4,"size":24,"len":24}]}]
+        "chunks": [{"index":0,"slices":[{"id":1,"size":6,"len":6},{"id":2,"size":12,"len":12},{"id":4,"size":24,"len":24}]}]
       },
       "l1": {
         "attr": {"inode":4,"type":"regular","mode":420,"uid":501,"gid":20,"atime":1623746610,"mtime":1623746610,"ctime":1623746639,"atimensec":591590000,"mtimensec":591590000,"ctimensec":591590000,"nlink":2,"length":12},
-        "chunks": [{"index":0,"slices":[{"chunkid":2,"size":12,"len":12}]}]
+        "chunks": [{"index":0,"slices":[{"id":2,"size":12,"len":12}]}]
       },
       "s1": {
         "attr": {"inode":5,"type":"symlink","mode":420,"uid":501,"gid":20,"atime":1623746645,"mtime":1623746645,"ctime":1623746645,"atimensec":984144000,"mtimensec":984144000,"ctimensec":984144000,"nlink":1,"length":0},
@@ -92,7 +92,7 @@
         "entries": {
           "1-27-tf1": {
             "attr": {"inode":27,"type":"regular","mode":420,"uid":501,"gid":0,"atime":1638437852,"mtime":1638437852,"ctime":1638437877,"atimensec":28186000,"mtimensec":28186000,"ctimensec":28186000,"nlink":1,"length":11},
-            "chunks": [{"index":0,"slices":[{"chunkid":7,"size":11,"len":11}]}]
+            "chunks": [{"index":0,"slices":[{"id":7,"size":11,"len":11}]}]
           },
           "1-28-td1": {
             "attr": {"inode":28,"type":"directory","mode":493,"uid":501,"gid":0,"atime":1638437856,"mtime":1638437879,"ctime":1638437879,"atimensec":59246000,"mtimensec":59246000,"ctimensec":59246000,"nlink":2,"length":0},
@@ -101,7 +101,7 @@
           },
           "28-29-tdf1": {
             "attr": {"inode":29,"type":"regular","mode":420,"uid":501,"gid":0,"atime":1638437873,"mtime":1638437873,"ctime":1638437879,"atimensec":449880000,"mtimensec":449880000,"ctimensec":449880000,"nlink":1,"length":10485760},
-            "chunks": [{"index":0,"slices":[{"chunkid":8,"size":10485760,"len":10485760}]}]
+            "chunks": [{"index":0,"slices":[{"id":8,"size":10485760,"len":10485760}]}]
           }
         }
       }
diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go
index 5bbb0ddb..27b0ae59 100644
--- a/pkg/meta/redis.go
+++ b/pkg/meta/redis.go
@@ -61,7 +61,7 @@ import (
 	locked: locked$sid -> { lockf$inode or lockp$inode }
 
 	Removed files: delfiles -> [$inode:$length -> seconds]
-	Slices refs: k$chunkid_$size -> refcount
+	Slices refs: k$sliceId_$size -> refcount
 
 	Redis features:
 	  Sorted Set: 1.2+
@@ -210,8 +210,8 @@ func (m *redisMeta) Shutdown() error {
 	return m.rdb.Close()
 }
 
-func (m *redisMeta) doDeleteSlice(chunkid uint64, size uint32) error {
-	return m.rdb.HDel(Background, m.sliceRefs(), m.sliceKey(chunkid, size)).Err()
+func (m *redisMeta) doDeleteSlice(id uint64, size uint32) error {
+	return m.rdb.HDel(Background, m.sliceRefs(), m.sliceKey(id, size)).Err()
 }
 
 func (m *redisMeta) Name() string {
@@ -495,9 +495,9 @@ func (m *redisMeta) chunkKey(inode Ino, indx uint32) string {
 	return m.prefix + "c" + inode.String() + "_" + strconv.FormatInt(int64(indx), 10)
 }
 
-func (m *redisMeta) sliceKey(chunkid uint64, size uint32) string {
+func (m *redisMeta) sliceKey(id uint64, size uint32) string {
 	// inside hashset
-	return "k" + strconv.FormatUint(chunkid, 10) + "_" + strconv.FormatUint(uint64(size), 10)
+	return "k" + strconv.FormatUint(id, 10) + "_" + strconv.FormatUint(uint64(size), 10)
 }
 
 func (m *redisMeta) xattrKey(inode Ino) string {
@@ -1932,14 +1932,14 @@ func (m *redisMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	return err
 }
 
-func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) syscall.Errno {
+func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
 	f := m.of.find(inode)
 	if f != nil {
 		f.RLock()
 		defer f.RUnlock()
 	}
-	if cs, ok := m.of.ReadChunk(inode, indx); ok {
-		*chunks = cs
+	if ss, ok := m.of.ReadChunk(inode, indx); ok {
+		*slices = ss
 		return 0
 	}
 	defer m.timeit(time.Now())
@@ -1948,9 +1948,9 @@ func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) s
 		return errno(err)
 	}
 	ss := readSlices(vals)
-	*chunks = buildSlice(ss)
-	m.of.CacheChunk(inode, indx, *chunks)
-	if !m.conf.ReadOnly && (len(vals) >= 5 || len(*chunks) >= 5) {
+	*slices = buildSlice(ss)
+	m.of.CacheChunk(inode, indx, *slices)
+	if !m.conf.ReadOnly && (len(vals) >= 5 || len(*slices) >= 5) {
 		go m.compactChunk(inode, indx, false)
 	}
 	return 0
@@ -1992,9 +1992,9 @@ func (m *redisMeta) Write(ctx Context, inode Ino, indx uint32, off uint32, slice
 
 		var rpush *redis.IntCmd
 		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
-			rpush = pipe.RPush(ctx, m.chunkKey(inode, indx), marshalSlice(off, slice.Chunkid, slice.Size, slice.Off, slice.Len))
+			rpush = pipe.RPush(ctx, m.chunkKey(inode, indx), marshalSlice(off, slice.Id, slice.Size, slice.Off, slice.Len))
 			// most of chunk are used by single inode, so use that as the default (1 == not exists)
-			// pipe.Incr(ctx, r.sliceKey(slice.Chunkid, slice.Size))
+			// pipe.Incr(ctx, r.sliceKey(slice.ID, slice.Size))
 			pipe.Set(ctx, m.inodeKey(inode), m.marshal(&attr), 0)
 			if newSpace > 0 {
 				pipe.IncrBy(ctx, m.usedSpaceKey(), newSpace)
@@ -2099,20 +2099,20 @@ func (m *redisMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino,
 						indx := uint32(doff / ChunkSize)
 						dpos := uint32(doff % ChunkSize)
 						if dpos+s.Len > ChunkSize {
-							pipe.RPush(ctx, m.chunkKey(fout, indx), marshalSlice(dpos, s.Chunkid, s.Size, s.Off, ChunkSize-dpos))
-							if s.Chunkid > 0 {
-								pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.Chunkid, s.Size), 1)
+							pipe.RPush(ctx, m.chunkKey(fout, indx), marshalSlice(dpos, s.Id, s.Size, s.Off, ChunkSize-dpos))
+							if s.Id > 0 {
+								pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.Id, s.Size), 1)
 							}
 
 							skip := ChunkSize - dpos
-							pipe.RPush(ctx, m.chunkKey(fout, indx+1), marshalSlice(0, s.Chunkid, s.Size, s.Off+skip, s.Len-skip))
-							if s.Chunkid > 0 {
-								pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.Chunkid, s.Size), 1)
+							pipe.RPush(ctx, m.chunkKey(fout, indx+1), marshalSlice(0, s.Id, s.Size, s.Off+skip, s.Len-skip))
+							if s.Id > 0 {
+								pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.Id, s.Size), 1)
 							}
 						} else {
-							pipe.RPush(ctx, m.chunkKey(fout, indx), marshalSlice(dpos, s.Chunkid, s.Size, s.Off, s.Len))
-							if s.Chunkid > 0 {
-								pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.Chunkid, s.Size), 1)
+							pipe.RPush(ctx, m.chunkKey(fout, indx), marshalSlice(dpos, s.Id, s.Size, s.Off, s.Len))
+							if s.Id > 0 {
+								pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.Id, s.Size), 1)
 							}
 						}
 					}
@@ -2206,10 +2206,10 @@ func (m *redisMeta) doCleanupSlices() {
 			if strings.HasPrefix(val, "-") { // < 0
 				ps := strings.Split(key, "_")
 				if len(ps) == 2 {
-					chunkid, _ := strconv.ParseUint(ps[0][1:], 10, 64)
+					id, _ := strconv.ParseUint(ps[0][1:], 10, 64)
 					size, _ := strconv.ParseUint(ps[1], 10, 32)
-					if chunkid > 0 && size > 0 {
-						m.deleteSlice(chunkid, uint32(size))
+					if id > 0 && size > 0 {
+						m.deleteSlice(id, uint32(size))
 					}
 				}
 			} else if val == "0" {
@@ -2325,13 +2325,13 @@ func (m *redisMeta) deleteChunk(inode Ino, indx uint32) error {
 					pipe.LPop(ctx, key)
 					rb := utils.ReadBuffer([]byte(v))
 					_ = rb.Get32() // pos
-					chunkid := rb.Get64()
-					if chunkid == 0 {
+					id := rb.Get64()
+					if id == 0 {
 						continue
 					}
 					size := rb.Get32()
-					slices = append(slices, &slice{chunkid: chunkid, size: size})
-					rs = append(rs, pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(chunkid, size), -1))
+					slices = append(slices, &slice{id: id, size: size})
+					rs = append(rs, pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(id, size), -1))
 				}
 				return nil
 			})
@@ -2342,7 +2342,7 @@ func (m *redisMeta) deleteChunk(inode Ino, indx uint32) error {
 		}
 		for i, s := range slices {
 			if rs[i].Val() < 0 {
-				m.deleteSlice(s.chunkid, s.size)
+				m.deleteSlice(s.id, s.size)
 			}
 		}
 		if len(slices) < 100 {
@@ -2428,7 +2428,7 @@ func (r *redisMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 				}
 				_, e = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
 					for _, s := range ss {
-						rs = append(rs, pipe.HIncrBy(ctx, r.sliceRefs(), r.sliceKey(s.Chunkid, s.Size), -1))
+						rs = append(rs, pipe.HIncrBy(ctx, r.sliceRefs(), r.sliceKey(s.Id, s.Size), -1))
 					}
 					pipe.HDel(ctx, r.delSlices(), key)
 					return nil
@@ -2440,7 +2440,7 @@ func (r *redisMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 			}
 			for i, s := range ss {
 				if rs[i].Err() == nil && rs[i].Val() < 0 {
-					r.deleteSlice(s.Chunkid, s.Size)
+					r.deleteSlice(s.Id, s.Size)
 					count++
 				}
 			}
@@ -2483,18 +2483,18 @@ func (m *redisMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	ss := readSlices(vals)
 	skipped := skipSome(ss)
 	ss = ss[skipped:]
-	pos, size, chunks := compactChunk(ss)
+	pos, size, slices := compactChunk(ss)
 	if len(ss) < 2 || size == 0 {
 		return
 	}
 
-	var chunkid uint64
-	st := m.NewChunk(ctx, &chunkid)
+	var id uint64
+	st := m.NewSlice(ctx, &id)
 	if st != 0 {
 		return
 	}
 	logger.Debugf("compact %d:%d: skipped %d slices (%d bytes) %d slices (%d bytes)", inode, indx, skipped, pos, len(ss), size)
-	err = m.newMsg(CompactChunk, chunks, chunkid)
+	err = m.newMsg(CompactChunk, slices, id)
 	if err != nil {
 		if !strings.Contains(err.Error(), "not exist") && !strings.Contains(err.Error(), "not found") {
 			logger.Warnf("compact %d %d with %d slices: %s", inode, indx, len(ss), err)
@@ -2506,8 +2506,8 @@ func (m *redisMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	trash := m.toTrash(0)
 	if trash {
 		for _, s := range ss {
-			if s.chunkid > 0 {
-				buf = append(buf, m.encodeDelayedSlice(s.chunkid, s.size)...)
+			if s.id > 0 {
+				buf = append(buf, m.encodeDelayedSlice(s.id, s.size)...)
 			}
 		}
 	} else {
@@ -2530,19 +2530,19 @@ func (m *redisMeta) compactChunk(inode Ino, indx uint32, force bool) {
 
 		_, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
 			pipe.LTrim(ctx, key, int64(len(vals)), -1)
-			pipe.LPush(ctx, key, marshalSlice(pos, chunkid, size, 0, size))
+			pipe.LPush(ctx, key, marshalSlice(pos, id, size, 0, size))
 			for i := skipped; i > 0; i-- {
 				pipe.LPush(ctx, key, vals[i-1])
 			}
-			pipe.HSet(ctx, m.sliceRefs(), m.sliceKey(chunkid, size), "0") // create the key to tracking it
+			pipe.HSet(ctx, m.sliceRefs(), m.sliceKey(id, size), "0") // create the key to tracking it
 			if trash {
 				if len(buf) > 0 {
-					pipe.HSet(ctx, m.delSlices(), fmt.Sprintf("%d_%d", chunkid, time.Now().Unix()), buf)
+					pipe.HSet(ctx, m.delSlices(), fmt.Sprintf("%d_%d", id, time.Now().Unix()), buf)
 				}
 			} else {
 				for i, s := range ss {
-					if s.chunkid > 0 {
-						rs[i] = pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.chunkid, s.size), -1)
+					if s.id > 0 {
+						rs[i] = pipe.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(s.id, s.size), -1)
 					}
 				}
 			}
@@ -2552,7 +2552,7 @@ func (m *redisMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	}, key))
 	// there could be false-negative that the compaction is successful, double-check
 	if errno != 0 && errno != syscall.EINVAL {
-		if e := m.rdb.HGet(ctx, m.sliceRefs(), m.sliceKey(chunkid, size)).Err(); e == redis.Nil {
+		if e := m.rdb.HGet(ctx, m.sliceRefs(), m.sliceKey(id, size)).Err(); e == redis.Nil {
 			errno = syscall.EINVAL // failed
 		} else if e == nil {
 			errno = 0 // successful
@@ -2560,16 +2560,16 @@ func (m *redisMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	}
 
 	if errno == syscall.EINVAL {
-		m.rdb.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(chunkid, size), -1)
-		logger.Infof("compaction for %d:%d is wasted, delete slice %d (%d bytes)", inode, indx, chunkid, size)
-		m.deleteSlice(chunkid, size)
+		m.rdb.HIncrBy(ctx, m.sliceRefs(), m.sliceKey(id, size), -1)
+		logger.Infof("compaction for %d:%d is wasted, delete slice %d (%d bytes)", inode, indx, id, size)
+		m.deleteSlice(id, size)
 	} else if errno == 0 {
 		m.of.InvalidateChunk(inode, indx)
-		m.cleanupZeroRef(m.sliceKey(chunkid, size))
+		m.cleanupZeroRef(m.sliceKey(id, size))
 		if !trash {
 			for i, s := range ss {
-				if s.chunkid > 0 && rs[i].Err() == nil && rs[i].Val() < 0 {
-					m.deleteSlice(s.chunkid, s.size)
+				if s.id > 0 && rs[i].Err() == nil && rs[i].Val() < 0 {
+					m.deleteSlice(s.id, s.size)
 				}
 			}
 		}
@@ -2744,8 +2744,8 @@ func (m *redisMeta) ListSlices(ctx Context, slices map[Ino][]Slice, delete bool,
 			vals := cmd.(*redis.StringSliceCmd).Val()
 			ss := readSlices(vals)
 			for _, s := range ss {
-				if s.chunkid > 0 {
-					slices[Ino(inode)] = append(slices[Ino(inode)], Slice{Chunkid: s.chunkid, Size: s.size})
+				if s.id > 0 {
+					slices[Ino(inode)] = append(slices[Ino(inode)], Slice{Id: s.id, Size: s.size})
 					if showProgress != nil {
 						showProgress()
 					}
@@ -2769,7 +2769,7 @@ func (m *redisMeta) ListSlices(ctx Context, slices map[Ino][]Slice, delete bool,
 				}
 			}
 			for _, s := range ss {
-				if s.Chunkid > 0 {
+				if s.Id > 0 {
 					slices[1] = append(slices[1], s)
 				}
 			}
@@ -2959,7 +2959,7 @@ func (m *redisMeta) dumpEntries(es ...*DumpedEntry) error {
 						ss := readSlices(vals)
 						slices := make([]*DumpedSlice, 0, len(ss))
 						for _, s := range ss {
-							slices = append(slices, &DumpedSlice{Chunkid: s.chunkid, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
+							slices = append(slices, &DumpedSlice{Id: s.id, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
 						}
 						e.Chunks = append(e.Chunks, &DumpedChunk{0, slices})
 					}
@@ -3013,7 +3013,7 @@ func (m *redisMeta) dumpEntries(es ...*DumpedEntry) error {
 					ss := readSlices(vals)
 					slices := make([]*DumpedSlice, 0, len(ss))
 					for _, s := range ss {
-						slices = append(slices, &DumpedSlice{Chunkid: s.chunkid, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
+						slices = append(slices, &DumpedSlice{Id: s.id, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
 					}
 					e := es[lcs[i].i]
 					e.Chunks = append(e.Chunks, &DumpedChunk{lcs[i].indx, slices})
@@ -3259,7 +3259,7 @@ func (m *redisMeta) loadEntry(e *DumpedEntry, p redis.Pipeliner, tryExec func())
 			}
 			slices := make([]string, 0, len(c.Slices))
 			for _, s := range c.Slices {
-				slices = append(slices, string(marshalSlice(s.Pos, s.Chunkid, s.Size, s.Off, s.Len)))
+				slices = append(slices, string(marshalSlice(s.Pos, s.Id, s.Size, s.Off, s.Len)))
 				if len(slices) > batch {
 					p.RPush(ctx, m.chunkKey(inode, c.Index), slices)
 					tryExec()
diff --git a/pkg/meta/slice.go b/pkg/meta/slice.go
index 09efb006..7ebf6e1b 100644
--- a/pkg/meta/slice.go
+++ b/pkg/meta/slice.go
@@ -19,22 +19,22 @@ package meta
 import "github.com/juicedata/juicefs/pkg/utils"
 
 type slice struct {
-	chunkid uint64
-	size    uint32
-	off     uint32
-	len     uint32
-	pos     uint32
-	left    *slice
-	right   *slice
+	id    uint64
+	size  uint32
+	off   uint32
+	len   uint32
+	pos   uint32
+	left  *slice
+	right *slice
 }
 
-func newSlice(pos uint32, chunkid uint64, cleng, off, len uint32) *slice {
+func newSlice(pos uint32, id uint64, cleng, off, len uint32) *slice {
 	if len == 0 {
 		return nil
 	}
 	s := &slice{}
 	s.pos = pos
-	s.chunkid = chunkid
+	s.id = id
 	s.size = cleng
 	s.off = off
 	s.len = len
@@ -46,7 +46,7 @@ func newSlice(pos uint32, chunkid uint64, cleng, off, len uint32) *slice {
 func (s *slice) read(buf []byte) {
 	rb := utils.ReadBuffer(buf)
 	s.pos = rb.Get32()
-	s.chunkid = rb.Get64()
+	s.id = rb.Get64()
 	s.size = rb.Get32()
 	s.off = rb.Get32()
 	s.len = rb.Get32()
@@ -64,7 +64,7 @@ func (s *slice) cut(pos uint32) (left, right *slice) {
 		return left, s
 	} else if pos < s.pos+s.len {
 		l := pos - s.pos
-		right = newSlice(pos, s.chunkid, s.size, s.off+l, s.len-l)
+		right = newSlice(pos, s.id, s.size, s.off+l, s.len-l)
 		right.right = s.right
 		s.len = l
 		s.right = nil
@@ -90,10 +90,10 @@ func (s *slice) visit(f func(*slice)) {
 
 const sliceBytes = 24
 
-func marshalSlice(pos uint32, chunkid uint64, size, off, len uint32) []byte {
+func marshalSlice(pos uint32, id uint64, size, off, len uint32) []byte {
 	w := utils.NewBuffer(sliceBytes)
 	w.Put32(pos)
-	w.Put64(chunkid)
+	w.Put64(id)
 	w.Put32(size)
 	w.Put32(off)
 	w.Put32(len)
@@ -144,7 +144,7 @@ func buildSlice(ss []*slice) []Slice {
 			chunk = append(chunk, Slice{Size: s.pos - pos, Len: s.pos - pos})
 			pos = s.pos
 		}
-		chunk = append(chunk, Slice{Chunkid: s.chunkid, Size: s.size, Off: s.off, Len: s.len})
+		chunk = append(chunk, Slice{Id: s.id, Size: s.size, Off: s.off, Len: s.len})
 		pos += s.len
 	})
 	return chunk
@@ -153,7 +153,7 @@ func buildSlice(ss []*slice) []Slice {
 func compactChunk(ss []*slice) (uint32, uint32, []Slice) {
 	var chunk = buildSlice(ss)
 	var pos uint32
-	if len(chunk) > 0 && chunk[0].Chunkid == 0 {
+	if len(chunk) > 0 && chunk[0].Id == 0 {
 		pos = chunk[0].Len
 		chunk = chunk[1:]
 	}
@@ -176,7 +176,7 @@ func skipSome(chunk []*slice) int {
 			break
 		}
 		isFirst := func(pos uint32, s Slice) bool {
-			return pos == first.pos && s.Chunkid == first.chunkid && s.Off == first.off && s.Len == first.len
+			return pos == first.pos && s.Id == first.id && s.Off == first.off && s.Len == first.len
 		}
 		if !isFirst(pos, c[0]) {
 			// it's not the first slice, compact it
diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go
index c3726707..83a6a36d 100644
--- a/pkg/meta/sql.go
+++ b/pkg/meta/sql.go
@@ -90,14 +90,18 @@ type chunk struct {
 	Slices []byte `xorm:"blob notnull"`
 }
 
-type chunkRef struct {
-	Chunkid uint64 `xorm:"pk"`
-	Size    uint32 `xorm:"notnull"`
-	Refs    int    `xorm:"notnull"`
+type sliceRef struct {
+	Id   uint64 `xorm:"pk chunkid"`
+	Size uint32 `xorm:"notnull"`
+	Refs int    `xorm:"notnull"`
+}
+
+func (c *sliceRef) TableName() string {
+	return "jfs_chunk_ref"
 }
 
 type delslices struct {
-	Chunkid uint64 `xorm:"pk"`
+	Id      uint64 `xorm:"pk chunkid"`
 	Deleted int64  `xorm:"notnull"` // timestamp
 	Slices  []byte `xorm:"blob notnull"`
 }
@@ -220,9 +224,9 @@ func (m *dbMeta) Name() string {
 	return m.db.DriverName()
 }
 
-func (m *dbMeta) doDeleteSlice(chunkid uint64, size uint32) error {
+func (m *dbMeta) doDeleteSlice(id uint64, size uint32) error {
 	return m.txn(func(s *xorm.Session) error {
-		_, err := s.Exec("delete from jfs_chunk_ref where chunkid=?", chunkid)
+		_, err := s.Exec("delete from jfs_chunk_ref where chunkid=?", id)
 		return err
 	})
 }
@@ -245,7 +249,7 @@ func (m *dbMeta) Init(format Format, force bool) error {
 	if err := m.syncTable(new(node), new(symlink), new(xattr)); err != nil {
 		return fmt.Errorf("create table node, symlink, xattr: %s", err)
 	}
-	if err := m.syncTable(new(chunk), new(chunkRef), new(delslices)); err != nil {
+	if err := m.syncTable(new(chunk), new(sliceRef), new(delslices)); err != nil {
 		return fmt.Errorf("create table chunk, chunk_ref, delslices: %s", err)
 	}
 	if err := m.syncTable(new(session2), new(sustained), new(delfile)); err != nil {
@@ -335,7 +339,7 @@ func (m *dbMeta) Init(format Format, force bool) error {
 func (m *dbMeta) Reset() error {
 	return m.db.DropTables(&setting{}, &counter{},
 		&node{}, &edge{}, &symlink{}, &xattr{},
-		&chunk{}, &chunkRef{}, &delslices{},
+		&chunk{}, &sliceRef{}, &delslices{},
 		&session{}, &session2{}, &sustained{}, &delfile{},
 		&flock{}, &plock{})
 }
@@ -1918,14 +1922,14 @@ func (m *dbMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	return err
 }
 
-func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) syscall.Errno {
+func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
 	f := m.of.find(inode)
 	if f != nil {
 		f.RLock()
 		defer f.RUnlock()
 	}
-	if cs, ok := m.of.ReadChunk(inode, indx); ok {
-		*chunks = cs
+	if ss, ok := m.of.ReadChunk(inode, indx); ok {
+		*slices = ss
 		return 0
 	}
 	defer m.timeit(time.Now())
@@ -1941,9 +1945,9 @@ func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) sysc
 	if ss == nil {
 		return syscall.EIO
 	}
-	*chunks = buildSlice(ss)
-	m.of.CacheChunk(inode, indx, *chunks)
-	if !m.conf.ReadOnly && (len(c.Slices)/sliceBytes >= 5 || len(*chunks) >= 5) {
+	*slices = buildSlice(ss)
+	m.of.CacheChunk(inode, indx, *slices)
+	if !m.conf.ReadOnly && (len(c.Slices)/sliceBytes >= 5 || len(*slices) >= 5) {
 		go m.compactChunk(inode, indx, false)
 	}
 	return 0
@@ -1988,7 +1992,7 @@ func (m *dbMeta) Write(ctx Context, inode Ino, indx uint32, off uint32, slice Sl
 		if err != nil {
 			return err
 		}
-		buf := marshalSlice(off, slice.Chunkid, slice.Size, slice.Off, slice.Len)
+		buf := marshalSlice(off, slice.Id, slice.Size, slice.Off, slice.Len)
 		if ok {
 			if err := m.appendSlice(s, inode, indx, buf); err != nil {
 				return err
@@ -1998,7 +2002,7 @@ func (m *dbMeta) Write(ctx Context, inode Ino, indx uint32, off uint32, slice Sl
 				return err
 			}
 		}
-		if err = mustInsert(s, chunkRef{slice.Chunkid, slice.Size, 1}); err != nil {
+		if err = mustInsert(s, sliceRef{slice.Id, slice.Size, 1}); err != nil {
 			return err
 		}
 		_, err = s.Cols("length", "mtime", "ctime").Update(&n, &node{Inode: inode})
@@ -2069,12 +2073,12 @@ func (m *dbMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off
 		}
 
 		ses := s
-		updateSlices := func(indx uint32, buf []byte, chunkid uint64, size uint32) error {
+		updateSlices := func(indx uint32, buf []byte, id uint64, size uint32) error {
 			if err := m.appendSlice(ses, fout, indx, buf); err != nil {
 				return err
 			}
-			if chunkid > 0 {
-				if _, err := ses.Exec("update jfs_chunk_ref set refs=refs+1 where chunkid = ? AND size = ?", chunkid, size); err != nil {
+			if id > 0 {
+				if _, err := ses.Exec("update jfs_chunk_ref set refs=refs+1 where chunkid = ? AND size = ?", id, size); err != nil {
 					return err
 				}
 			}
@@ -2106,15 +2110,15 @@ func (m *dbMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off
 					indx := uint32(doff / ChunkSize)
 					dpos := uint32(doff % ChunkSize)
 					if dpos+s.Len > ChunkSize {
-						if err := updateSlices(indx, marshalSlice(dpos, s.Chunkid, s.Size, s.Off, ChunkSize-dpos), s.Chunkid, s.Size); err != nil {
+						if err := updateSlices(indx, marshalSlice(dpos, s.Id, s.Size, s.Off, ChunkSize-dpos), s.Id, s.Size); err != nil {
 							return err
 						}
 						skip := ChunkSize - dpos
-						if err := updateSlices(indx+1, marshalSlice(0, s.Chunkid, s.Size, s.Off+skip, s.Len-skip), s.Chunkid, s.Size); err != nil {
+						if err := updateSlices(indx+1, marshalSlice(0, s.Id, s.Size, s.Off+skip, s.Len-skip), s.Id, s.Size); err != nil {
 							return err
 						}
 					} else {
-						if err := updateSlices(indx, marshalSlice(dpos, s.Chunkid, s.Size, s.Off, s.Len), s.Chunkid, s.Size); err != nil {
+						if err := updateSlices(indx, marshalSlice(dpos, s.Id, s.Size, s.Off, s.Len), s.Id, s.Size); err != nil {
 							return err
 						}
 					}
@@ -2166,13 +2170,13 @@ func (m *dbMeta) doFindDeletedFiles(ts int64, limit int) (map[Ino]uint64, error)
 }
 
 func (m *dbMeta) doCleanupSlices() {
-	var cks []chunkRef
+	var cks []sliceRef
 	_ = m.roTxn(func(s *xorm.Session) error {
 		cks = nil
 		return s.Where("refs <= 0").Find(&cks)
 	})
 	for _, ck := range cks {
-		m.deleteSlice(ck.Chunkid, ck.Size)
+		m.deleteSlice(ck.Id, ck.Size)
 	}
 }
 
@@ -2189,10 +2193,10 @@ func (m *dbMeta) deleteChunk(inode Ino, indx uint32) error {
 		}
 		ss = readSliceBuf(c.Slices)
 		for _, sc := range ss {
-			if sc.chunkid == 0 {
+			if sc.id == 0 {
 				continue
 			}
-			_, err = s.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? AND size=?", sc.chunkid, sc.size)
+			_, err = s.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? AND size=?", sc.id, sc.size)
 			if err != nil {
 				return err
 			}
@@ -2208,10 +2212,10 @@ func (m *dbMeta) deleteChunk(inode Ino, indx uint32) error {
 		return fmt.Errorf("delete slice from chunk %s fail: %s, retry later", inode, err)
 	}
 	for _, s := range ss {
-		if s.chunkid == 0 {
+		if s.id == 0 {
 			continue
 		}
-		var ref = chunkRef{Chunkid: s.chunkid}
+		var ref = sliceRef{Id: s.id}
 		err := m.roTxn(func(s *xorm.Session) error {
 			ok, err := s.Get(&ref)
 			if err == nil && !ok {
@@ -2220,7 +2224,7 @@ func (m *dbMeta) deleteChunk(inode Ino, indx uint32) error {
 			return err
 		})
 		if err == nil && ref.Refs <= 0 {
-			m.deleteSlice(s.chunkid, s.size)
+			m.deleteSlice(s.id, s.size)
 		}
 	}
 	return nil
@@ -2256,7 +2260,7 @@ func (m *dbMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 	var ss []Slice
 	for _, ds := range result {
 		if err := m.txn(func(ses *xorm.Session) error {
-			ds := delslices{Chunkid: ds.Chunkid}
+			ds := delslices{Id: ds.Id}
 			if ok, e := ses.ForUpdate().Get(&ds); e != nil {
 				return e
 			} else if !ok {
@@ -2265,21 +2269,21 @@ func (m *dbMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 			ss = ss[:0]
 			m.decodeDelayedSlices(ds.Slices, &ss)
 			if len(ss) == 0 {
-				return fmt.Errorf("invalid value for delayed slices %d: %v", ds.Chunkid, ds.Slices)
+				return fmt.Errorf("invalid value for delayed slices %d: %v", ds.Id, ds.Slices)
 			}
 			for _, s := range ss {
-				if _, e := ses.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? and size=?", s.Chunkid, s.Size); e != nil {
+				if _, e := ses.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? and size=?", s.Id, s.Size); e != nil {
 					return e
 				}
 			}
-			_, e := ses.Delete(&delslices{Chunkid: ds.Chunkid})
+			_, e := ses.Delete(&delslices{Id: ds.Id})
 			return e
 		}); err != nil {
-			logger.Warnf("Cleanup delayed slices %d: %s", ds.Chunkid, err)
+			logger.Warnf("Cleanup delayed slices %d: %s", ds.Id, err)
 			continue
 		}
 		for _, s := range ss {
-			var ref = chunkRef{Chunkid: s.Chunkid}
+			var ref = sliceRef{Id: s.Id}
 			err := m.roTxn(func(s *xorm.Session) error {
 				ok, err := s.Get(&ref)
 				if err == nil && !ok {
@@ -2288,7 +2292,7 @@ func (m *dbMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 				return err
 			})
 			if err == nil && ref.Refs <= 0 {
-				m.deleteSlice(s.Chunkid, s.Size)
+				m.deleteSlice(s.Id, s.Size)
 				count++
 			}
 		}
@@ -2329,18 +2333,18 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	ss := readSliceBuf(c.Slices)
 	skipped := skipSome(ss)
 	ss = ss[skipped:]
-	pos, size, chunks := compactChunk(ss)
+	pos, size, slices := compactChunk(ss)
 	if len(ss) < 2 || size == 0 {
 		return
 	}
 
-	var chunkid uint64
-	st := m.NewChunk(Background, &chunkid)
+	var id uint64
+	st := m.NewSlice(Background, &id)
 	if st != 0 {
 		return
 	}
 	logger.Debugf("compact %d:%d: skipped %d slices (%d bytes) %d slices (%d bytes)", inode, indx, skipped, pos, len(ss), size)
-	err = m.newMsg(CompactChunk, chunks, chunkid)
+	err = m.newMsg(CompactChunk, slices, id)
 	if err != nil {
 		if !strings.Contains(err.Error(), "not exist") && !strings.Contains(err.Error(), "not found") {
 			logger.Warnf("compact %d %d with %d slices: %s", inode, indx, len(ss), err)
@@ -2351,8 +2355,8 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	trash := m.toTrash(0)
 	if trash {
 		for _, s := range ss {
-			if s.chunkid > 0 {
-				buf = append(buf, m.encodeDelayedSlice(s.chunkid, s.size)...)
+			if s.id > 0 {
+				buf = append(buf, m.encodeDelayedSlice(s.id, s.size)...)
 			}
 		}
 	}
@@ -2367,26 +2371,26 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 			return syscall.EINVAL
 		}
 
-		c2.Slices = append(append(c2.Slices[:skipped*sliceBytes], marshalSlice(pos, chunkid, size, 0, size)...), c2.Slices[len(c.Slices):]...)
+		c2.Slices = append(append(c2.Slices[:skipped*sliceBytes], marshalSlice(pos, id, size, 0, size)...), c2.Slices[len(c.Slices):]...)
 		if _, err := s.Where("Inode = ? AND indx = ?", inode, indx).Update(c2); err != nil {
 			return err
 		}
 		// create the key to tracking it
-		if err = mustInsert(s, chunkRef{chunkid, size, 1}); err != nil {
+		if err = mustInsert(s, sliceRef{id, size, 1}); err != nil {
 			return err
 		}
 		if trash {
 			if len(buf) > 0 {
-				if err = mustInsert(s, &delslices{chunkid, time.Now().Unix(), buf}); err != nil {
+				if err = mustInsert(s, &delslices{id, time.Now().Unix(), buf}); err != nil {
 					return err
 				}
 			}
 		} else {
 			for _, s_ := range ss {
-				if s_.chunkid == 0 {
+				if s_.id == 0 {
 					continue
 				}
-				if _, err := s.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? and size=?", s_.chunkid, s_.size); err != nil {
+				if _, err := s.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? and size=?", s_.id, s_.size); err != nil {
 					return err
 				}
 			}
@@ -2395,7 +2399,7 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	})
 	// there could be false-negative that the compaction is successful, double-check
 	if err != nil {
-		var c = chunkRef{Chunkid: chunkid}
+		var c = sliceRef{Id: id}
 		var ok bool
 		e := m.roTxn(func(s *xorm.Session) error {
 			var e error
@@ -2406,23 +2410,23 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 			if ok {
 				err = nil
 			} else {
-				logger.Infof("compacted chunk %d was not used", chunkid)
+				logger.Infof("compacted chunk %d was not used", id)
 				err = syscall.EINVAL
 			}
 		}
 	}
 
 	if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINVAL {
-		logger.Infof("compaction for %d:%d is wasted, delete slice %d (%d bytes)", inode, indx, chunkid, size)
-		m.deleteSlice(chunkid, size)
+		logger.Infof("compaction for %d:%d is wasted, delete slice %d (%d bytes)", inode, indx, id, size)
+		m.deleteSlice(id, size)
 	} else if err == nil {
 		m.of.InvalidateChunk(inode, indx)
 		if !trash {
 			for _, s := range ss {
-				if s.chunkid == 0 {
+				if s.id == 0 {
 					continue
 				}
-				var ref = chunkRef{Chunkid: s.chunkid}
+				var ref = sliceRef{Id: s.id}
 				var ok bool
 				err := m.roTxn(func(s *xorm.Session) error {
 					var e error
@@ -2430,7 +2434,7 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 					return e
 				})
 				if err == nil && ok && ref.Refs <= 0 {
-					m.deleteSlice(s.chunkid, s.size)
+					m.deleteSlice(s.id, s.size)
 				}
 			}
 		}
@@ -2487,8 +2491,8 @@ func (m *dbMeta) ListSlices(ctx Context, slices map[Ino][]Slice, delete bool, sh
 		for _, c := range cs {
 			ss := readSliceBuf(c.Slices)
 			for _, s := range ss {
-				if s.chunkid > 0 {
-					slices[c.Inode] = append(slices[c.Inode], Slice{Chunkid: s.chunkid, Size: s.size})
+				if s.id > 0 {
+					slices[c.Inode] = append(slices[c.Inode], Slice{Id: s.id, Size: s.size})
 					if showProgress != nil {
 						showProgress()
 					}
@@ -2525,7 +2529,7 @@ func (m *dbMeta) ListSlices(ctx Context, slices map[Ino][]Slice, delete bool, sh
 				}
 			}
 			for _, s := range ss {
-				if s.Chunkid > 0 {
+				if s.Id > 0 {
 					slices[1] = append(slices[1], s)
 				}
 			}
@@ -2656,7 +2660,7 @@ func (m *dbMeta) dumpEntry(s *xorm.Session, inode Ino, typ uint8) (*DumpedEntry,
 			ss := readSliceBuf(c.Slices)
 			slices := make([]*DumpedSlice, 0, len(ss))
 			for _, s := range ss {
-				slices = append(slices, &DumpedSlice{Chunkid: s.chunkid, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
+				slices = append(slices, &DumpedSlice{Id: s.id, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
 			}
 			e.Chunks = append(e.Chunks, &DumpedChunk{indx, slices})
 		}
@@ -2708,7 +2712,7 @@ func (m *dbMeta) dumpEntryFast(s *xorm.Session, inode Ino, typ uint8) *DumpedEnt
 			ss := readSliceBuf(c.Slices)
 			slices := make([]*DumpedSlice, 0, len(ss))
 			for _, s := range ss {
-				slices = append(slices, &DumpedSlice{Chunkid: s.chunkid, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
+				slices = append(slices, &DumpedSlice{Id: s.id, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
 			}
 			e.Chunks = append(e.Chunks, &DumpedChunk{indx, slices})
 		}
@@ -3005,7 +3009,7 @@ func (m *dbMeta) loadEntry(e *DumpedEntry, chs []chan interface{}) {
 			}
 			slices := make([]byte, 0, sliceBytes*len(c.Slices))
 			for _, s := range c.Slices {
-				slices = append(slices, marshalSlice(s.Pos, s.Chunkid, s.Size, s.Off, s.Len)...)
+				slices = append(slices, marshalSlice(s.Pos, s.Id, s.Size, s.Off, s.Len)...)
 			}
 			chs[2] <- &chunk{Inode: inode, Indx: c.Index, Slices: slices}
 		}
@@ -3048,7 +3052,7 @@ func (m *dbMeta) LoadMeta(r io.Reader) error {
 	if err = m.syncTable(new(node), new(edge), new(symlink), new(xattr)); err != nil {
 		return fmt.Errorf("create table node, edge, symlink, xattr: %s", err)
 	}
-	if err = m.syncTable(new(chunk), new(chunkRef), new(delslices)); err != nil {
+	if err = m.syncTable(new(chunk), new(sliceRef), new(delslices)); err != nil {
 		return fmt.Errorf("create table chunk, chunk_ref, delslices: %s", err)
 	}
 	if err = m.syncTable(new(session2), new(sustained), new(delfile)); err != nil {
@@ -3109,7 +3113,7 @@ func (m *dbMeta) LoadMeta(r io.Reader) error {
 
 	dm, counters, parents, refs, err := loadEntries(r,
 		func(e *DumpedEntry) { m.loadEntry(e, chs) },
-		func(ck *chunkKey) { chs[3] <- &chunkRef{ck.id, ck.size, 1} })
+		func(ck *chunkKey) { chs[3] <- &sliceRef{ck.id, ck.size, 1} })
 	if err != nil {
 		return err
 	}
@@ -3133,7 +3137,7 @@ func (m *dbMeta) LoadMeta(r io.Reader) error {
 	if err = m.txn(func(s *xorm.Session) error {
 		for k, v := range refs {
 			if v > 1 {
-				if _, e := s.Cols("refs").Update(&chunkRef{Refs: int(v)}, &chunkRef{Chunkid: k.id}); e != nil {
+				if _, e := s.Cols("refs").Update(&sliceRef{Refs: int(v)}, &sliceRef{Id: k.id}); e != nil {
 					return e
 				}
 			}
diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go
index dc7b638c..3a6d8693 100644
--- a/pkg/meta/tkv.go
+++ b/pkg/meta/tkv.go
@@ -100,8 +100,8 @@ func (m *kvMeta) Name() string {
 	return m.client.name()
 }
 
-func (m *kvMeta) doDeleteSlice(chunkid uint64, size uint32) error {
-	return m.deleteKeys(m.sliceKey(chunkid, size))
+func (m *kvMeta) doDeleteSlice(id uint64, size uint32) error {
+	return m.deleteKeys(m.sliceKey(id, size))
 }
 
 func (m *kvMeta) keyLen(args ...interface{}) int {
@@ -147,12 +147,12 @@ func (m *kvMeta) fmtKey(args ...interface{}) []byte {
 }
 
 /**
-  Ino iiiiiiii
-  Length llllllll
-  Indx nnnn
-  name ...
-  chunkid cccccccc
-  session  ssssssss
+  Ino     iiiiiiii
+  Length  llllllll
+  Indx    nnnn
+  name    ...
+  sliceId cccccccc
+  session ssssssss
 
 All keys:
   setting            format
@@ -190,12 +190,12 @@ func (m *kvMeta) chunkKey(inode Ino, indx uint32) []byte {
 	return m.fmtKey("A", inode, "C", indx)
 }
 
-func (m *kvMeta) sliceKey(chunkid uint64, size uint32) []byte {
-	return m.fmtKey("K", chunkid, size)
+func (m *kvMeta) sliceKey(id uint64, size uint32) []byte {
+	return m.fmtKey("K", id, size)
 }
 
-func (m *kvMeta) delSliceKey(ts int64, chunkid uint64) []byte {
-	return m.fmtKey("L", uint64(ts), chunkid)
+func (m *kvMeta) delSliceKey(ts int64, id uint64) []byte {
+	return m.fmtKey("L", uint64(ts), id)
 }
 
 func (m *kvMeta) symKey(inode Ino) []byte {
@@ -1630,14 +1630,14 @@ func (m *kvMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	return err
 }
 
-func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) syscall.Errno {
+func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
 	f := m.of.find(inode)
 	if f != nil {
 		f.RLock()
 		defer f.RUnlock()
 	}
-	if cs, ok := m.of.ReadChunk(inode, indx); ok {
-		*chunks = cs
+	if ss, ok := m.of.ReadChunk(inode, indx); ok {
+		*slices = ss
 		return 0
 	}
 	defer m.timeit(time.Now())
@@ -1649,9 +1649,9 @@ func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, chunks *[]Slice) sysc
 	if ss == nil {
 		return syscall.EIO
 	}
-	*chunks = buildSlice(ss)
-	m.of.CacheChunk(inode, indx, *chunks)
-	if !m.conf.ReadOnly && (len(val)/sliceBytes >= 5 || len(*chunks) >= 5) {
+	*slices = buildSlice(ss)
+	m.of.CacheChunk(inode, indx, *slices)
+	if !m.conf.ReadOnly && (len(val)/sliceBytes >= 5 || len(*slices) >= 5) {
 		go m.compactChunk(inode, indx, false)
 	}
 	return 0
@@ -1690,7 +1690,7 @@ func (m *kvMeta) Write(ctx Context, inode Ino, indx uint32, off uint32, slice Sl
 		attr.Mtimensec = uint32(now.Nanosecond())
 		attr.Ctime = now.Unix()
 		attr.Ctimensec = uint32(now.Nanosecond())
-		val := tx.append(m.chunkKey(inode, indx), marshalSlice(off, slice.Chunkid, slice.Size, slice.Off, slice.Len))
+		val := tx.append(m.chunkKey(inode, indx), marshalSlice(off, slice.Id, slice.Size, slice.Off, slice.Len))
 		tx.set(m.inodeKey(inode), m.marshal(&attr))
 		needCompact = (len(val)/sliceBytes)%100 == 99
 		return nil
@@ -1784,19 +1784,19 @@ func (m *kvMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off
 					indx := uint32(doff / ChunkSize)
 					dpos := uint32(doff % ChunkSize)
 					if dpos+s.Len > ChunkSize {
-						tx.append(m.chunkKey(fout, indx), marshalSlice(dpos, s.Chunkid, s.Size, s.Off, ChunkSize-dpos))
-						if s.Chunkid > 0 {
-							tx.incrBy(m.sliceKey(s.Chunkid, s.Size), 1)
+						tx.append(m.chunkKey(fout, indx), marshalSlice(dpos, s.Id, s.Size, s.Off, ChunkSize-dpos))
+						if s.Id > 0 {
+							tx.incrBy(m.sliceKey(s.Id, s.Size), 1)
 						}
 						skip := ChunkSize - dpos
-						tx.append(m.chunkKey(fout, indx+1), marshalSlice(0, s.Chunkid, s.Size, s.Off+skip, s.Len-skip))
-						if s.Chunkid > 0 {
-							tx.incrBy(m.sliceKey(s.Chunkid, s.Size), 1)
+						tx.append(m.chunkKey(fout, indx+1), marshalSlice(0, s.Id, s.Size, s.Off+skip, s.Len-skip))
+						if s.Id > 0 {
+							tx.incrBy(m.sliceKey(s.Id, s.Size), 1)
 						}
 					} else {
-						tx.append(m.chunkKey(fout, indx), marshalSlice(dpos, s.Chunkid, s.Size, s.Off, s.Len))
-						if s.Chunkid > 0 {
-							tx.incrBy(m.sliceKey(s.Chunkid, s.Size), 1)
+						tx.append(m.chunkKey(fout, indx), marshalSlice(dpos, s.Id, s.Size, s.Off, s.Len))
+						if s.Id > 0 {
+							tx.incrBy(m.sliceKey(s.Id, s.Size), 1)
 						}
 					}
 				}
@@ -1853,13 +1853,13 @@ func (m *kvMeta) doCleanupSlices() {
 	})
 	for k, v := range vals {
 		rb := utils.FromBuffer([]byte(k)[1:])
-		chunkid := rb.Get64()
+		id := rb.Get64()
 		size := rb.Get32()
 		refs := parseCounter(v)
 		if refs < 0 {
-			m.deleteSlice(chunkid, size)
+			m.deleteSlice(id, size)
 		} else {
-			m.cleanupZeroRef(chunkid, size)
+			m.cleanupZeroRef(id, size)
 		}
 	}
 }
@@ -1872,7 +1872,7 @@ func (m *kvMeta) deleteChunk(inode Ino, indx uint32) error {
 		slices := readSliceBuf(buf)
 		tx.dels(key)
 		for _, s := range slices {
-			if s.chunkid > 0 && tx.incrBy(m.sliceKey(s.chunkid, s.size), -1) < 0 {
+			if s.id > 0 && tx.incrBy(m.sliceKey(s.id, s.size), -1) < 0 {
 				todel = append(todel, s)
 			}
 		}
@@ -1882,18 +1882,18 @@ func (m *kvMeta) deleteChunk(inode Ino, indx uint32) error {
 		return err
 	}
 	for _, s := range todel {
-		m.deleteSlice(s.chunkid, s.size)
+		m.deleteSlice(s.id, s.size)
 	}
 	return nil
 }
 
-func (r *kvMeta) cleanupZeroRef(chunkid uint64, size uint32) {
+func (r *kvMeta) cleanupZeroRef(id uint64, size uint32) {
 	_ = r.txn(func(tx kvTxn) error {
-		v := tx.incrBy(r.sliceKey(chunkid, size), 0)
+		v := tx.incrBy(r.sliceKey(id, size), 0)
 		if v != 0 {
 			return syscall.EINVAL
 		}
-		tx.dels(r.sliceKey(chunkid, size))
+		tx.dels(r.sliceKey(id, size))
 		return nil
 	})
 }
@@ -1946,7 +1946,7 @@ func (m *kvMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 				return fmt.Errorf("invalid value for delayed slices %s: %v", key, buf)
 			}
 			for _, s := range ss {
-				rs = append(rs, tx.incrBy(m.sliceKey(s.Chunkid, s.Size), -1))
+				rs = append(rs, tx.incrBy(m.sliceKey(s.Id, s.Size), -1))
 			}
 			tx.dels(key)
 			return nil
@@ -1956,7 +1956,7 @@ func (m *kvMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 		}
 		for i, s := range ss {
 			if rs[i] < 0 {
-				m.deleteSlice(s.Chunkid, s.Size)
+				m.deleteSlice(s.Id, s.Size)
 				count++
 			}
 		}
@@ -1996,18 +1996,18 @@ func (m *kvMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	ss := readSliceBuf(buf)
 	skipped := skipSome(ss)
 	ss = ss[skipped:]
-	pos, size, chunks := compactChunk(ss)
+	pos, size, slices := compactChunk(ss)
 	if len(ss) < 2 || size == 0 {
 		return
 	}
 
-	var chunkid uint64
-	st := m.NewChunk(Background, &chunkid)
+	var id uint64
+	st := m.NewSlice(Background, &id)
 	if st != 0 {
 		return
 	}
 	logger.Debugf("compact %d:%d: skipped %d slices (%d bytes) %d slices (%d bytes)", inode, indx, skipped, pos, len(ss), size)
-	err = m.newMsg(CompactChunk, chunks, chunkid)
+	err = m.newMsg(CompactChunk, slices, id)
 	if err != nil {
 		if !strings.Contains(err.Error(), "not exist") && !strings.Contains(err.Error(), "not found") {
 			logger.Warnf("compact %d %d with %d slices: %s", inode, indx, len(ss), err)
@@ -2018,8 +2018,8 @@ func (m *kvMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	trash := m.toTrash(0)
 	if trash {
 		for _, s := range ss {
-			if s.chunkid > 0 {
-				dsbuf = append(dsbuf, m.encodeDelayedSlice(s.chunkid, s.size)...)
+			if s.id > 0 {
+				dsbuf = append(dsbuf, m.encodeDelayedSlice(s.id, s.size)...)
 			}
 		}
 	}
@@ -2030,18 +2030,18 @@ func (m *kvMeta) compactChunk(inode Ino, indx uint32, force bool) {
 			return syscall.EINVAL
 		}
 
-		buf2 = append(append(buf2[:skipped*sliceBytes], marshalSlice(pos, chunkid, size, 0, size)...), buf2[len(buf):]...)
+		buf2 = append(append(buf2[:skipped*sliceBytes], marshalSlice(pos, id, size, 0, size)...), buf2[len(buf):]...)
 		tx.set(m.chunkKey(inode, indx), buf2)
 		// create the key to tracking it
-		tx.set(m.sliceKey(chunkid, size), make([]byte, 8))
+		tx.set(m.sliceKey(id, size), make([]byte, 8))
 		if trash {
 			if len(dsbuf) > 0 {
-				tx.set(m.delSliceKey(time.Now().Unix(), chunkid), dsbuf)
+				tx.set(m.delSliceKey(time.Now().Unix(), id), dsbuf)
 			}
 		} else {
 			for _, s := range ss {
-				if s.chunkid > 0 {
-					tx.incrBy(m.sliceKey(s.chunkid, s.size), -1)
+				if s.id > 0 {
+					tx.incrBy(m.sliceKey(s.id, s.size), -1)
 				}
 			}
 		}
@@ -2050,31 +2050,31 @@ func (m *kvMeta) compactChunk(inode Ino, indx uint32, force bool) {
 	// there could be false-negative that the compaction is successful, double-check
 	if err != nil {
 		logger.Warnf("compact %d:%d failed: %s", inode, indx, err)
-		refs, e := m.get(m.sliceKey(chunkid, size))
+		refs, e := m.get(m.sliceKey(id, size))
 		if e == nil {
 			if len(refs) > 0 {
 				err = nil
 			} else {
-				logger.Infof("compacted chunk %d was not used", chunkid)
+				logger.Infof("compacted chunk %d was not used", id)
 				err = syscall.EINVAL
 			}
 		}
 	}
 
 	if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINVAL {
-		logger.Infof("compaction for %d:%d is wasted, delete slice %d (%d bytes)", inode, indx, chunkid, size)
-		m.deleteSlice(chunkid, size)
+		logger.Infof("compaction for %d:%d is wasted, delete slice %d (%d bytes)", inode, indx, id, size)
+		m.deleteSlice(id, size)
 	} else if err == nil {
 		m.of.InvalidateChunk(inode, indx)
-		m.cleanupZeroRef(chunkid, size)
+		m.cleanupZeroRef(id, size)
 		if !trash {
 			var refs int64
 			for _, s := range ss {
-				if s.chunkid > 0 && m.client.txn(func(tx kvTxn) error {
-					refs = tx.incrBy(m.sliceKey(s.chunkid, s.size), 0)
+				if s.id > 0 && m.client.txn(func(tx kvTxn) error {
+					refs = tx.incrBy(m.sliceKey(s.id, s.size), 0)
 					return nil
 				}) == nil && refs < 0 {
-					m.deleteSlice(s.chunkid, s.size)
+					m.deleteSlice(s.id, s.size)
 				}
 			}
 		}
@@ -2133,8 +2133,8 @@ func (m *kvMeta) ListSlices(ctx Context, slices map[Ino][]Slice, delete bool, sh
 		inode := m.decodeInode([]byte(key)[1:9])
 		ss := readSliceBuf(value)
 		for _, s := range ss {
-			if s.chunkid > 0 {
-				slices[inode] = append(slices[inode], Slice{Chunkid: s.chunkid, Size: s.size})
+			if s.id > 0 {
+				slices[inode] = append(slices[inode], Slice{Id: s.id, Size: s.size})
 				if showProgress != nil {
 					showProgress()
 				}
@@ -2164,7 +2164,7 @@ func (m *kvMeta) ListSlices(ctx Context, slices map[Ino][]Slice, delete bool, sh
 			}
 		}
 		for _, s := range ss {
-			if s.Chunkid > 0 {
+			if s.Id > 0 {
 				slices[1] = append(slices[1], s)
 			}
 		}
@@ -2272,7 +2272,7 @@ func (m *kvMeta) dumpEntry(inode Ino, e *DumpedEntry) error {
 				ss := readSliceBuf(v)
 				slices := make([]*DumpedSlice, 0, len(ss))
 				for _, s := range ss {
-					slices = append(slices, &DumpedSlice{Chunkid: s.chunkid, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
+					slices = append(slices, &DumpedSlice{Id: s.id, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
 				}
 				e.Chunks = append(e.Chunks, &DumpedChunk{indx, slices})
 			}
@@ -2419,7 +2419,7 @@ func (m *kvMeta) DumpMeta(w io.Writer, root Ino) (err error) {
 					ss := readSliceBuf(value)
 					slices := make([]*DumpedSlice, 0, len(ss))
 					for _, s := range ss {
-						slices = append(slices, &DumpedSlice{Chunkid: s.chunkid, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
+						slices = append(slices, &DumpedSlice{Id: s.id, Pos: s.pos, Size: s.size, Off: s.off, Len: s.len})
 					}
 					e.Chunks = append(e.Chunks, &DumpedChunk{indx, slices})
 				case 'D':
@@ -2583,7 +2583,7 @@ func (m *kvMeta) loadEntry(e *DumpedEntry, kv chan *pair) {
 			}
 			slices := make([]byte, 0, sliceBytes*len(c.Slices))
 			for _, s := range c.Slices {
-				slices = append(slices, marshalSlice(s.Pos, s.Chunkid, s.Size, s.Off, s.Len)...)
+				slices = append(slices, marshalSlice(s.Pos, s.Id, s.Size, s.Off, s.Len)...)
 			}
 			kv <- &pair{m.chunkKey(inode, c.Index), slices}
 		}
diff --git a/pkg/vfs/compact.go b/pkg/vfs/compact.go
index fb93ce18..4dbd18e0 100644
--- a/pkg/vfs/compact.go
+++ b/pkg/vfs/compact.go
@@ -37,7 +37,7 @@ var (
 func readSlice(store chunk.ChunkStore, s *meta.Slice, page *chunk.Page, off int) error {
 	buf := page.Data
 	read := 0
-	reader := store.NewReader(s.Chunkid, int(s.Size))
+	reader := store.NewReader(s.Id, int(s.Size))
 	for read < len(buf) {
 		p := page.Slice(read, len(buf)-read)
 		n, err := reader.ReadAt(context.Background(), p, off+int(s.Off))
@@ -51,7 +51,7 @@ func readSlice(store chunk.ChunkStore, s *meta.Slice, page *chunk.Page, off int)
 	return nil
 }
 
-func Compact(conf chunk.Config, store chunk.ChunkStore, slices []meta.Slice, chunkid uint64) error {
+func Compact(conf chunk.Config, store chunk.ChunkStore, slices []meta.Slice, id uint64) error {
 	for utils.AllocMemory()-store.UsedMemory() > int64(conf.BufferSize)*3/2 {
 		time.Sleep(time.Millisecond * 100)
 	}
@@ -60,13 +60,13 @@ func Compact(conf chunk.Config, store chunk.ChunkStore, slices []meta.Slice, chu
 		size += s.Len
 	}
 	compactSizeHistogram.Observe(float64(size))
-	logger.Debugf("compact %d slices (%d bytes) to chunk %d", len(slices), size, chunkid)
+	logger.Debugf("compact %d slices (%d bytes) to new slice %d", len(slices), size, id)
 
-	writer := store.NewWriter(chunkid)
+	writer := store.NewWriter(id)
 
 	var pos int
 	for i, s := range slices {
-		if s.Chunkid == 0 {
+		if s.Id == 0 {
 			_, err := writer.WriteAt(make([]byte, int(s.Len)), int64(pos))
 			if err != nil {
 				writer.Abort()
@@ -80,7 +80,7 @@ func Compact(conf chunk.Config, store chunk.ChunkStore, slices []meta.Slice, chu
 			l := utils.Min(conf.BlockSize, int(s.Len)-read)
 			p := chunk.NewOffPage(l)
 			if err := readSlice(store, &slices[i], p, read); err != nil {
-				logger.Debugf("can't compact chunk %d, retry later, read %d: %s", chunkid, i, err)
+				logger.Debugf("can't compact to slice %d, retry later, read %d: %s", id, i, err)
 				p.Release()
 				writer.Abort()
 				return err
@@ -88,7 +88,7 @@ func Compact(conf chunk.Config, store chunk.ChunkStore, slices []meta.Slice, chu
 			_, err := writer.WriteAt(p.Data, int64(pos+read))
 			p.Release()
 			if err != nil {
-				logger.Errorf("can't compact chunk %d, retry later, write: %s", chunkid, err)
+				logger.Errorf("can't compact to slice %d, retry later, write: %s", id, err)
 				writer.Abort()
 				return err
 			}
diff --git a/pkg/vfs/fill.go b/pkg/vfs/fill.go
index f9812f28..2233e3c5 100644
--- a/pkg/vfs/fill.go
+++ b/pkg/vfs/fill.go
@@ -188,8 +188,8 @@ func (v *VFS) fillInode(ctx meta.Context, inode Ino, size uint64, bytes *uint64)
 			if bytes != nil {
 				atomic.AddUint64(bytes, uint64(s.Size))
 			}
-			if err := v.Store.FillCache(s.Chunkid, s.Size); err != nil {
-				return fmt.Errorf("Failed to cache inode %d slice %d: %s", inode, s.Chunkid, err)
+			if err := v.Store.FillCache(s.Id, s.Size); err != nil {
+				return fmt.Errorf("Failed to cache inode %d slice %d: %s", inode, s.Id, err)
 			}
 			if ctx.Canceled() {
 				return syscall.EINTR
diff --git a/pkg/vfs/internal.go b/pkg/vfs/internal.go
index dcf9aed8..81d14fa5 100644
--- a/pkg/vfs/internal.go
+++ b/pkg/vfs/internal.go
@@ -313,9 +313,9 @@ func (v *VFS) handleInternalMsg(ctx meta.Context, cmd uint32, r *utils.Buffer, d
 				_ = v.Meta.Read(ctx, inode, uint32(indx), &cs)
 				for _, c := range cs {
 					if raw {
-						fmt.Fprintf(w, "\t%d:\t%d\t%d\t%d\t%d\n", indx, c.Chunkid, c.Size, c.Off, c.Len)
+						fmt.Fprintf(w, "\t%d:\t%d\t%d\t%d\t%d\n", indx, c.Id, c.Size, c.Off, c.Len)
 					} else {
-						for _, o := range v.caclObjects(c.Chunkid, c.Size, c.Off, c.Len) {
+						for _, o := range v.caclObjects(c.Id, c.Size, c.Off, c.Len) {
 							fmt.Fprintf(w, "\t%d:\t%s\t%d\t%d\t%d\n", indx, o.key, o.size, o.off, o.len)
 						}
 					}
diff --git a/pkg/vfs/reader.go b/pkg/vfs/reader.go
index ae45f294..c9606171 100644
--- a/pkg/vfs/reader.go
+++ b/pkg/vfs/reader.go
@@ -171,8 +171,8 @@ func (s *sliceReader) run() {
 	f.Lock()
 	length := f.length
 	f.Unlock()
-	var chunks []meta.Slice
-	err := f.r.m.Read(meta.Background, inode, indx, &chunks)
+	var slices []meta.Slice
+	err := f.r.m.Read(meta.Background, inode, indx, &slices)
 	f.Lock()
 	if s.state != BUSY || f.err != 0 || f.closing {
 		s.done(0, 0)
@@ -204,7 +204,7 @@ func (s *sliceReader) run() {
 	defer p.Release()
 	var n int
 	ctx := context.TODO()
-	n = f.r.Read(ctx, p, chunks, (uint32(s.block.off))%meta.ChunkSize)
+	n = f.r.Read(ctx, p, slices, (uint32(s.block.off))%meta.ChunkSize)
 
 	f.Lock()
 	if s.state != BUSY || f.shouldStop() {
@@ -782,7 +782,7 @@ func (r *dataReader) Invalidate(inode Ino, off, length uint64) {
 func (r *dataReader) readSlice(ctx context.Context, s *meta.Slice, page *chunk.Page, off int) error {
 	buf := page.Data
 	read := 0
-	if s.Chunkid == 0 {
+	if s.Id == 0 {
 		for read < len(buf) {
 			buf[read] = 0
 			read++
@@ -790,14 +790,14 @@ func (r *dataReader) readSlice(ctx context.Context, s *meta.Slice, page *chunk.P
 		return nil
 	}
 
-	reader := r.store.NewReader(s.Chunkid, int(s.Size))
+	reader := r.store.NewReader(s.Id, int(s.Size))
 	for read < len(buf) {
 		p := page.Slice(read, len(buf)-read)
 		n, err := reader.ReadAt(ctx, p, off+int(s.Off))
 		p.Release()
 		if n == 0 && err != nil {
-			logger.Warningf("fail to read chunkid %d (off:%d, size:%d, clen: %d): %s",
-				s.Chunkid, off+int(s.Off), len(buf)-read, s.Size, err)
+			logger.Warningf("fail to read sliceId %d (off:%d, size:%d, clen: %d): %s",
+				s.Id, off+int(s.Off), len(buf)-read, s.Size, err)
 			return err
 		}
 		read += n
@@ -806,9 +806,9 @@ func (r *dataReader) readSlice(ctx context.Context, s *meta.Slice, page *chunk.P
 	return nil
 }
 
-func (r *dataReader) Read(ctx context.Context, page *chunk.Page, chunks []meta.Slice, offset uint32) int {
-	if len(chunks) > 16 {
-		return r.readManyChunks(ctx, page, chunks, offset)
+func (r *dataReader) Read(ctx context.Context, page *chunk.Page, slices []meta.Slice, offset uint32) int {
+	if len(slices) > 16 {
+		return r.readManySlices(ctx, page, slices, offset)
 	}
 	read := 0
 	var pos uint32
@@ -816,18 +816,18 @@ func (r *dataReader) Read(ctx context.Context, page *chunk.Page, chunks []meta.S
 	waits := 0
 	buf := page.Data
 	size := len(buf)
-	for i := 0; i < len(chunks); i++ {
-		if read < size && offset < pos+chunks[i].Len {
-			toread := utils.Min(size-read, int(pos+chunks[i].Len-offset))
+	for i := 0; i < len(slices); i++ {
+		if read < size && offset < pos+slices[i].Len {
+			toread := utils.Min(size-read, int(pos+slices[i].Len-offset))
 			go func(s *meta.Slice, p *chunk.Page, off, pos uint32) {
 				defer p.Release()
 				errs <- r.readSlice(ctx, s, p, int(off))
-			}(&chunks[i], page.Slice(read, toread), offset-pos, pos)
+			}(&slices[i], page.Slice(read, toread), offset-pos, pos)
 			read += toread
 			offset += uint32(toread)
 			waits++
 		}
-		pos += chunks[i].Len
+		pos += slices[i].Len
 	}
 	for read < size {
 		buf[read] = 0
@@ -847,7 +847,7 @@ func (r *dataReader) Read(ctx context.Context, page *chunk.Page, chunks []meta.S
 	return read
 }
 
-func (r *dataReader) readManyChunks(ctx context.Context, page *chunk.Page, chunks []meta.Slice, offset uint32) int {
+func (r *dataReader) readManySlices(ctx context.Context, page *chunk.Page, slices []meta.Slice, offset uint32) int {
 	read := 0
 	var pos uint32
 	var err error
@@ -857,10 +857,10 @@ func (r *dataReader) readManyChunks(ctx context.Context, page *chunk.Page, chunk
 	size := len(buf)
 	concurrency := make(chan byte, 16)
 
-CHUNKS:
-	for i := 0; i < len(chunks); i++ {
-		if read < size && offset < pos+chunks[i].Len {
-			toread := utils.Min(size-read, int(pos+chunks[i].Len-offset))
+SLICES:
+	for i := 0; i < len(slices); i++ {
+		if read < size && offset < pos+slices[i].Len {
+			toread := utils.Min(size-read, int(pos+slices[i].Len-offset))
 		WAIT:
 			for {
 				select {
@@ -870,7 +870,7 @@ CHUNKS:
 					waits--
 					if e != nil {
 						err = e
-						break CHUNKS
+						break SLICES
 					}
 				}
 			}
@@ -878,13 +878,13 @@ CHUNKS:
 				defer p.Release()
 				errs <- r.readSlice(ctx, s, p, off)
 				<-concurrency
-			}(&chunks[i], page.Slice(read, toread), int(offset-pos), pos)
+			}(&slices[i], page.Slice(read, toread), int(offset-pos), pos)
 
 			read += toread
 			offset += uint32(toread)
 			waits++
 		}
-		pos += chunks[i].Len
+		pos += slices[i].Len
 	}
 	// wait for all jobs done, otherwise they may access invalid memory
 	for waits > 0 {
diff --git a/pkg/vfs/writer.go b/pkg/vfs/writer.go
index c8cb0301..c6e6f15e 100644
--- a/pkg/vfs/writer.go
+++ b/pkg/vfs/writer.go
@@ -69,7 +69,7 @@ func (s *sliceWriter) prepareID(ctx meta.Context, retry bool) {
 	for s.id == 0 {
 		var id uint64
 		f.Unlock()
-		st := f.w.m.NewChunk(ctx, &id)
+		st := f.w.m.NewSlice(ctx, &id)
 		f.Lock()
 		if st != 0 && st != syscall.EIO {
 			s.err = st
@@ -197,7 +197,7 @@ func (c *chunkWriter) commitThread() {
 		f.Unlock()
 
 		if err == 0 {
-			var ss = meta.Slice{Chunkid: s.id, Size: s.length, Off: s.soff, Len: s.slen}
+			var ss = meta.Slice{Id: s.id, Size: s.length, Off: s.soff, Len: s.slen}
 			err = f.w.m.Write(meta.Background, f.inode, c.indx, s.off, ss)
 			f.w.reader.Invalidate(f.inode, uint64(c.indx)*meta.ChunkSize+uint64(s.off), uint64(ss.Len))
 		}
diff --git a/sdk/java/libjfs/main.go b/sdk/java/libjfs/main.go
index 6c86e904..0fe82a35 100644
--- a/sdk/java/libjfs/main.go
+++ b/sdk/java/libjfs/main.go
@@ -472,15 +472,15 @@ func jfs_init(cname, jsonConf, user, group, superuser, supergroup *C.char) uintp
 			chunkConf.CacheDir = strings.Join(ds, string(os.PathListSeparator))
 		}
 		store := chunk.NewCachedStore(blob, chunkConf, registerer)
-		m.OnMsg(meta.DeleteChunk, func(args ...interface{}) error {
-			chunkid := args[0].(uint64)
+		m.OnMsg(meta.DeleteSlice, func(args ...interface{}) error {
+			id := args[0].(uint64)
 			length := args[1].(uint32)
-			return store.Remove(chunkid, int(length))
+			return store.Remove(id, int(length))
 		})
 		m.OnMsg(meta.CompactChunk, func(args ...interface{}) error {
 			slices := args[0].([]meta.Slice)
-			chunkid := args[1].(uint64)
-			return vfs.Compact(chunkConf, store, slices, chunkid)
+			id := args[1].(uint64)
+			return vfs.Compact(chunkConf, store, slices, id)
 		})
 		err = m.NewSession()
 		if err != nil {


Test patch:
diff --git a/cmd/mdtest.go b/cmd/mdtest.go
index 7b25f40b..0ad0d666 100644
--- a/cmd/mdtest.go
+++ b/cmd/mdtest.go
@@ -46,15 +46,15 @@ func createFile(jfs *fs.FileSystem, bar *utils.Bar, np int, root string, d int,
 		}
 		if bytes > 0 {
 			for indx := 0; indx*meta.ChunkSize < bytes; indx++ {
-				var chunkid uint64
-				if st := m.NewChunk(ctx, &chunkid); st != 0 {
+				var id uint64
+				if st := m.NewSlice(ctx, &id); st != 0 {
 					return fmt.Errorf("writechunk %s: %s", fn, st)
 				}
 				size := meta.ChunkSize
 				if bytes < (indx+1)*meta.ChunkSize {
 					size = bytes - indx*meta.ChunkSize
 				}
-				if st := m.Write(ctx, f.Inode(), uint32(indx), 0, meta.Slice{Chunkid: chunkid, Size: uint32(size), Len: uint32(size)}); st != 0 {
+				if st := m.Write(ctx, f.Inode(), uint32(indx), 0, meta.Slice{Id: id, Size: uint32(size), Len: uint32(size)}); st != 0 {
 					return fmt.Errorf("writeend %s: %s", fn, st)
 				}
 			}
diff --git a/pkg/chunk/cached_store_test.go b/pkg/chunk/cached_store_test.go
index 70c1e8b8..dfdbc2d6 100644
--- a/pkg/chunk/cached_store_test.go
+++ b/pkg/chunk/cached_store_test.go
@@ -29,8 +29,8 @@ import (
 	"github.com/juicedata/juicefs/pkg/object"
 )
 
-func forgeChunk(store ChunkStore, chunkid uint64, size int) error {
-	w := store.NewWriter(chunkid)
+func forgetSlice(store ChunkStore, sliceId uint64, size int) error {
+	w := store.NewWriter(sliceId)
 	buf := bytes.Repeat([]byte{0x41}, size)
 	if _, err := w.WriteAt(buf, 0); err != nil {
 		return err
@@ -74,13 +74,13 @@ func testStore(t *testing.T, store ChunkStore) {
 	bsize := defaultConf.BlockSize / 2
 	errs := make(chan error, 3)
 	for i := 2; i < 5; i++ {
-		go func(chunkid uint64) {
-			if err := forgeChunk(store, chunkid, bsize); err != nil {
+		go func(sliceId uint64) {
+			if err := forgetSlice(store, sliceId, bsize); err != nil {
 				errs <- err
 				return
 			}
 			time.Sleep(time.Millisecond * 100) // waiting for flush
-			errs <- store.Remove(chunkid, bsize)
+			errs <- store.Remove(sliceId, bsize)
 		}(uint64(i))
 	}
 	for i := 0; i < 3; i++ {
@@ -193,8 +193,8 @@ func TestStoreDelayed(t *testing.T) {
 	store := NewCachedStore(mem, conf, nil)
 	time.Sleep(time.Second) // waiting for cache scanned
 	testStore(t, store)
-	if err := forgeChunk(store, 10, 1024); err != nil {
-		t.Fatalf("forge chunk 10 1024: %s", err)
+	if err := forgetSlice(store, 10, 1024); err != nil {
+		t.Fatalf("forge slice 10 1024: %s", err)
 	}
 	defer store.Remove(10, 1024)
 	time.Sleep(time.Second) // waiting for upload
@@ -217,13 +217,13 @@ func TestFillCache(t *testing.T) {
 	conf.CacheSize = 10
 	_ = os.RemoveAll(conf.CacheDir)
 	store := NewCachedStore(mem, conf, nil)
-	if err := forgeChunk(store, 10, 1024); err != nil {
-		t.Fatalf("forge chunk 10 1024: %s", err)
+	if err := forgetSlice(store, 10, 1024); err != nil {
+		t.Fatalf("forge slice 10 1024: %s", err)
 	}
 	defer store.Remove(10, 1024)
 	bsize := conf.BlockSize
-	if err := forgeChunk(store, 11, bsize); err != nil {
-		t.Fatalf("forge chunk 11 %d: %s", bsize, err)
+	if err := forgetSlice(store, 11, bsize); err != nil {
+		t.Fatalf("forge slice 11 %d: %s", bsize, err)
 	}
 	defer store.Remove(11, bsize)
 
diff --git a/pkg/fuse/fuse_test.go b/pkg/fuse/fuse_test.go
index b0b79333..9415ca18 100644
--- a/pkg/fuse/fuse_test.go
+++ b/pkg/fuse/fuse_test.go
@@ -93,8 +93,8 @@ func mount(url, mp string) {
 
 	m.OnMsg(meta.CompactChunk, meta.MsgCallback(func(args ...interface{}) error {
 		slices := args[0].([]meta.Slice)
-		chunkid := args[1].(uint64)
-		return vfs.Compact(chunkConf, store, slices, chunkid)
+		sliceId := args[1].(uint64)
+		return vfs.Compact(chunkConf, store, slices, sliceId)
 	}))
 
 	conf := &vfs.Config{
diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go
index 4aa770f9..43ae398e 100644
--- a/pkg/meta/base_test.go
+++ b/pkg/meta/base_test.go
@@ -80,7 +80,7 @@ func testMeta(t *testing.T, m Meta) {
 }
 
 func testMetaClient(t *testing.T, m Meta) {
-	m.OnMsg(DeleteChunk, func(args ...interface{}) error { return nil })
+	m.OnMsg(DeleteSlice, func(args ...interface{}) error { return nil })
 	ctx := Background
 	var attr = &Attr{}
 	if st := m.GetAttr(ctx, 1, attr); st != 0 || attr.Mode != 0777 { // getattr of root always succeed
@@ -366,7 +366,7 @@ func testMetaClient(t *testing.T, m Meta) {
 	}
 
 	// data
-	var chunkid uint64
+	var sliceId uint64
 	// try to open a file that does not exist
 	if st := m.Open(ctx, 99999, syscall.O_RDWR, &Attr{}); st != syscall.ENOENT {
 		t.Fatalf("open not exist inode got %d, expected %d", st, syscall.ENOENT)
@@ -375,19 +375,19 @@ func testMetaClient(t *testing.T, m Meta) {
 		t.Fatalf("open f: %s", st)
 	}
 	_ = m.Close(ctx, inode)
-	if st := m.NewChunk(ctx, &chunkid); st != 0 {
+	if st := m.NewSlice(ctx, &sliceId); st != 0 {
 		t.Fatalf("write chunk: %s", st)
 	}
-	var s = Slice{Chunkid: chunkid, Size: 100, Len: 100}
+	var s = Slice{Id: sliceId, Size: 100, Len: 100}
 	if st := m.Write(ctx, inode, 0, 100, s); st != 0 {
 		t.Fatalf("write end: %s", st)
 	}
-	var chunks []Slice
-	if st := m.Read(ctx, inode, 0, &chunks); st != 0 {
+	var slices []Slice
+	if st := m.Read(ctx, inode, 0, &slices); st != 0 {
 		t.Fatalf("read chunk: %s", st)
 	}
-	if len(chunks) != 2 || chunks[0].Chunkid != 0 || chunks[0].Size != 100 || chunks[1].Chunkid != chunkid || chunks[1].Size != 100 {
-		t.Fatalf("chunks: %v", chunks)
+	if len(slices) != 2 || slices[0].Id != 0 || slices[0].Size != 100 || slices[1].Id != sliceId || slices[1].Size != 100 {
+		t.Fatalf("slices: %v", slices)
 	}
 	if st := m.Fallocate(ctx, inode, fallocPunchHole|fallocKeepSize, 100, 50); st != 0 {
 		t.Fatalf("fallocate: %s", st)
@@ -410,11 +410,11 @@ func testMetaClient(t *testing.T, m Meta) {
 	if st := m.Fallocate(ctx, parent, fallocPunchHole|fallocKeepSize, 100, 50); st != syscall.EPERM {
 		t.Fatalf("fallocate dir: %s", st)
 	}
-	if st := m.Read(ctx, inode, 0, &chunks); st != 0 {
+	if st := m.Read(ctx, inode, 0, &slices); st != 0 {
 		t.Fatalf("read chunk: %s", st)
 	}
-	if len(chunks) != 3 || chunks[1].Chunkid != 0 || chunks[1].Len != 50 || chunks[2].Chunkid != chunkid || chunks[2].Len != 50 {
-		t.Fatalf("chunks: %v", chunks)
+	if len(slices) != 3 || slices[1].Id != 0 || slices[1].Len != 50 || slices[2].Id != sliceId || slices[2].Len != 50 {
+		t.Fatalf("slices: %v", slices)
 	}
 
 	// xattr
@@ -511,11 +511,11 @@ func testMetaClient(t *testing.T, m Meta) {
 		t.Fatalf("unlink f3: %s", st)
 	}
 	time.Sleep(time.Millisecond * 100) // wait for delete
-	if st := m.Read(ctx, inode, 0, &chunks); st != 0 {
+	if st := m.Read(ctx, inode, 0, &slices); st != 0 {
 		t.Fatalf("read chunk: %s", st)
 	}
-	if len(chunks) != 0 {
-		t.Fatalf("chunks: %v", chunks)
+	if len(slices) != 0 {
+		t.Fatalf("slices: %v", slices)
 	}
 	if st := m.Rmdir(ctx, 1, "d"); st != 0 {
 		t.Fatalf("rmdir d: %s", st)
@@ -812,10 +812,10 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 	}
 	var l sync.Mutex
 	deleted := make(map[uint64]int)
-	m.OnMsg(DeleteChunk, func(args ...interface{}) error {
+	m.OnMsg(DeleteSlice, func(args ...interface{}) error {
 		l.Lock()
-		chunkid := args[0].(uint64)
-		deleted[chunkid] = 1
+		sliceId := args[0].(uint64)
+		deleted[sliceId] = 1
 		l.Unlock()
 		return nil
 	})
@@ -834,13 +834,13 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 	}()
 
 	// random write
-	var chunkid uint64
-	m.NewChunk(ctx, &chunkid)
-	_ = m.Write(ctx, inode, 1, uint32(0), Slice{Chunkid: chunkid, Size: 64 << 20, Len: 64 << 20})
-	m.NewChunk(ctx, &chunkid)
-	_ = m.Write(ctx, inode, 1, uint32(30<<20), Slice{Chunkid: chunkid, Size: 8, Len: 8})
-	m.NewChunk(ctx, &chunkid)
-	_ = m.Write(ctx, inode, 1, uint32(40<<20), Slice{Chunkid: chunkid, Size: 8, Len: 8})
+	var sliceId uint64
+	m.NewSlice(ctx, &sliceId)
+	_ = m.Write(ctx, inode, 1, uint32(0), Slice{Id: sliceId, Size: 64 << 20, Len: 64 << 20})
+	m.NewSlice(ctx, &sliceId)
+	_ = m.Write(ctx, inode, 1, uint32(30<<20), Slice{Id: sliceId, Size: 8, Len: 8})
+	m.NewSlice(ctx, &sliceId)
+	_ = m.Write(ctx, inode, 1, uint32(40<<20), Slice{Id: sliceId, Size: 8, Len: 8})
 	var cs1 []Slice
 	_ = m.Read(ctx, inode, 1, &cs1)
 	if len(cs1) != 5 {
@@ -858,9 +858,9 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 	// append
 	var size uint32 = 100000
 	for i := 0; i < 200; i++ {
-		var chunkid uint64
-		m.NewChunk(ctx, &chunkid)
-		if st := m.Write(ctx, inode, 0, uint32(i)*size, Slice{Chunkid: chunkid, Size: size, Len: size}); st != 0 {
+		var sliceId uint64
+		m.NewSlice(ctx, &sliceId)
+		if st := m.Write(ctx, inode, 0, uint32(i)*size, Slice{Id: sliceId, Size: size, Len: size}); st != 0 {
 			t.Fatalf("write %d: %s", i, st)
 		}
 		time.Sleep(time.Millisecond)
@@ -868,15 +868,15 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 	if c, ok := m.(compactor); ok {
 		c.compactChunk(inode, 0, true)
 	}
-	var chunks []Slice
-	if st := m.Read(ctx, inode, 0, &chunks); st != 0 {
+	var slices []Slice
+	if st := m.Read(ctx, inode, 0, &slices); st != 0 {
 		t.Fatalf("read 0: %s", st)
 	}
-	if len(chunks) >= 10 {
-		t.Fatalf("inode %d should be compacted, but have %d slices", inode, len(chunks))
+	if len(slices) >= 10 {
+		t.Fatalf("inode %d should be compacted, but have %d slices", inode, len(slices))
 	}
 	var total uint32
-	for _, s := range chunks {
+	for _, s := range slices {
 		total += s.Len
 	}
 	if total != size*200 {
@@ -889,8 +889,8 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 		t.Fatalf("compactall: %s", st)
 	}
 	p.Done()
-	slices := make(map[Ino][]Slice)
-	if st := m.ListSlices(ctx, slices, false, nil); st != 0 {
+	sliceMap := make(map[Ino][]Slice)
+	if st := m.ListSlices(ctx, sliceMap, false, nil); st != 0 {
 		t.Fatalf("list all slices: %s", st)
 	}
 
@@ -899,10 +899,10 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 	l.Unlock()
 	if trash {
 		if deletes > 10 {
-			t.Fatalf("deleted chunks %d is greater than 10", deletes)
+			t.Fatalf("deleted slices %d is greater than 10", deletes)
 		}
-		if len(slices[1]) < 200 {
-			t.Fatalf("list delayed slices %d is less than 200", len(slices[1]))
+		if len(sliceMap[1]) < 200 {
+			t.Fatalf("list delayed slices %d is less than 200", len(sliceMap[1]))
 		}
 		m.(engine).doCleanupDelayedSlices(time.Now().Unix()+1, 1000)
 		l.Lock()
@@ -910,12 +910,12 @@ func testCompaction(t *testing.T, m Meta, trash bool) {
 		l.Unlock()
 	}
 	if deletes < 200 {
-		t.Fatalf("deleted chunks %d is less than 200", deletes)
+		t.Fatalf("deleted slices %d is less than 200", deletes)
 	}
 }
 
 func testConcurrentWrite(t *testing.T, m Meta) {
-	m.OnMsg(DeleteChunk, func(args ...interface{}) error {
+	m.OnMsg(DeleteSlice, func(args ...interface{}) error {
 		return nil
 	})
 	m.OnMsg(CompactChunk, func(args ...interface{}) error {
@@ -939,9 +939,9 @@ func testConcurrentWrite(t *testing.T, m Meta) {
 		go func(indx uint32) {
 			defer g.Done()
 			for j := 0; j < 100; j++ {
-				var chunkid uint64
-				m.NewChunk(ctx, &chunkid)
-				var slice = Slice{Chunkid: chunkid, Size: 100, Len: 100}
+				var sliceId uint64
+				m.NewSlice(ctx, &sliceId)
+				var slice = Slice{Id: sliceId, Size: 100, Len: 100}
 				st := m.Write(ctx, inode, indx, 0, slice)
 				if st != 0 {
 					errno = st
@@ -957,7 +957,7 @@ func testConcurrentWrite(t *testing.T, m Meta) {
 }
 
 func testTruncateAndDelete(t *testing.T, m Meta) {
-	m.OnMsg(DeleteChunk, func(args ...interface{}) error {
+	m.OnMsg(DeleteSlice, func(args ...interface{}) error {
 		return nil
 	})
 	// remove quota
@@ -976,11 +976,11 @@ func testTruncateAndDelete(t *testing.T, m Meta) {
 		t.Fatalf("create file %s", st)
 	}
 	defer m.Unlink(ctx, 1, "f")
-	var cid uint64
-	if st := m.NewChunk(ctx, &cid); st != 0 {
+	var sliceId uint64
+	if st := m.NewSlice(ctx, &sliceId); st != 0 {
 		t.Fatalf("new chunk: %s", st)
 	}
-	if st := m.Write(ctx, inode, 0, 100, Slice{cid, 100, 0, 100}); st != 0 {
+	if st := m.Write(ctx, inode, 0, 100, Slice{sliceId, 100, 0, 100}); st != 0 {
 		t.Fatalf("write file %s", st)
 	}
 	if st := m.Truncate(ctx, inode, 0, 200<<20, attr); st != 0 {
@@ -1000,7 +1000,7 @@ func testTruncateAndDelete(t *testing.T, m Meta) {
 		totalSlices += len(ss)
 	}
 	if totalSlices != 1 {
-		t.Fatalf("number of chunks: %d != 1, %+v", totalSlices, slices)
+		t.Fatalf("number of slices: %d != 1, %+v", totalSlices, slices)
 	}
 	_ = m.Close(ctx, inode)
 	if st := m.Unlink(ctx, 1, "f"); st != 0 {
@@ -1016,12 +1016,12 @@ func testTruncateAndDelete(t *testing.T, m Meta) {
 	}
 	// the last chunk could be found and deleted
 	if totalSlices > 1 {
-		t.Fatalf("number of chunks: %d > 1, %+v", totalSlices, slices)
+		t.Fatalf("number of slices: %d > 1, %+v", totalSlices, slices)
 	}
 }
 
 func testCopyFileRange(t *testing.T, m Meta) {
-	m.OnMsg(DeleteChunk, func(args ...interface{}) error {
+	m.OnMsg(DeleteSlice, func(args ...interface{}) error {
 		return nil
 	})
 	_ = m.Init(Format{Name: "test"}, false)
@@ -1051,23 +1051,23 @@ func testCopyFileRange(t *testing.T, m Meta) {
 	if copied != expected {
 		t.Fatalf("expect copy %d bytes, but got %d", expected, copied)
 	}
-	var expectedChunks = [][]Slice{
+	var expectedSlices = [][]Slice{
 		{{0, 30 << 20, 0, 30 << 20}, {10, 200, 50, 50}, {0, 0, 200, ChunkSize - 30<<20 - 50}},
 		{{0, 0, 150 + (ChunkSize - 30<<20), 30<<20 - 150}, {0, 0, 0, 100 << 10}, {11, 40 << 20, 0, (34 << 20) + 150 - (100 << 10)}},
 		{{11, 40 << 20, (34 << 20) + 150 - (100 << 10), 6<<20 - 150 + 100<<10}, {0, 0, 40<<20 + 100<<10, ChunkSize - 40<<20 - 100<<10}, {0, 0, 0, 150 + (ChunkSize - 30<<20)}},
 		{{0, 0, 150 + (ChunkSize - 30<<20), 30<<20 - 150}, {12, 63 << 20, 10 << 20, (8 << 20) + 150}},
 	}
 	for i := uint32(0); i < 4; i++ {
-		var chunks []Slice
-		if st := m.Read(ctx, iout, i, &chunks); st != 0 {
+		var slices []Slice
+		if st := m.Read(ctx, iout, i, &slices); st != 0 {
 			t.Fatalf("read chunk %d: %s", i, st)
 		}
-		if len(chunks) != len(expectedChunks[i]) {
-			t.Fatalf("expect chunk %d: %+v, but got %+v", i, expectedChunks[i], chunks)
+		if len(slices) != len(expectedSlices[i]) {
+			t.Fatalf("expect chunk %d: %+v, but got %+v", i, expectedSlices[i], slices)
 		}
-		for j, s := range chunks {
-			if s != expectedChunks[i][j] {
-				t.Fatalf("expect slice %d,%d: %+v, but got %+v", i, j, expectedChunks[i][j], s)
+		for j, s := range slices {
+			if s != expectedSlices[i][j] {
+				t.Fatalf("expect slice %d,%d: %+v, but got %+v", i, j, expectedSlices[i][j], s)
 			}
 		}
 	}
diff --git a/pkg/meta/benchmarks_test.go b/pkg/meta/benchmarks_test.go
index 3b2e03a2..c88c757d 100644
--- a/pkg/meta/benchmarks_test.go
+++ b/pkg/meta/benchmarks_test.go
@@ -508,9 +508,9 @@ func benchReadlink(b *testing.B, m Meta) {
 
 func benchNewChunk(b *testing.B, m Meta) {
 	ctx := Background
-	var chunkid uint64
+	var sliceId uint64
 	for i := 0; i < b.N; i++ {
-		if err := m.NewChunk(ctx, &chunkid); err != 0 {
+		if err := m.NewSlice(ctx, &sliceId); err != 0 {
 			b.Fatalf("newchunk: %s", err)
 		}
 	}
@@ -527,16 +527,16 @@ func benchWrite(b *testing.B, m Meta) {
 		b.Fatalf("create: %s", err)
 	}
 	var (
-		chunkid uint64
+		sliceId uint64
 		offset  uint32
 		step    uint32 = 1024
 	)
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		if err := m.NewChunk(ctx, &chunkid); err != 0 {
+		if err := m.NewSlice(ctx, &sliceId); err != 0 {
 			b.Fatalf("newchunk: %s", err)
 		}
-		if err := m.Write(ctx, inode, 0, offset, Slice{Chunkid: chunkid, Size: step, Len: step}); err != 0 {
+		if err := m.Write(ctx, inode, 0, offset, Slice{Id: sliceId, Size: step, Len: step}); err != 0 {
 			b.Fatalf("write: %s", err)
 		}
 		offset += step
@@ -556,13 +556,13 @@ func benchRead(b *testing.B, m Meta, n int) {
 	if err := m.Create(ctx, parent, "file", 0644, 022, 0, &inode, nil); err != 0 {
 		b.Fatalf("create: %s", err)
 	}
-	var chunkid uint64
+	var sliceId uint64
 	var step uint32 = 1024
 	for j := 0; j < n; j++ {
-		if err := m.NewChunk(ctx, &chunkid); err != 0 {
+		if err := m.NewSlice(ctx, &sliceId); err != 0 {
 			b.Fatalf("newchunk: %s", err)
 		}
-		if err := m.Write(ctx, inode, 0, uint32(j)*step, Slice{Chunkid: chunkid, Size: step, Len: step}); err != 0 {
+		if err := m.Write(ctx, inode, 0, uint32(j)*step, Slice{Id: sliceId, Size: step, Len: step}); err != 0 {
 			b.Fatalf("write: %s", err)
 		}
 	}
@@ -612,7 +612,7 @@ func benchmarkLink(b *testing.B, m Meta) {
 }
 
 func benchmarkData(b *testing.B, m Meta) {
-	m.OnMsg(DeleteChunk, func(args ...interface{}) error { return nil })
+	m.OnMsg(DeleteSlice, func(args ...interface{}) error { return nil })
 	m.OnMsg(CompactChunk, func(args ...interface{}) error { return nil })
 	b.Run("newchunk", func(b *testing.B) { benchNewChunk(b, m) })
 	b.Run("write", func(b *testing.B) { benchWrite(b, m) })
diff --git a/pkg/meta/load_dump_test.go b/pkg/meta/load_dump_test.go
index a613377b..896e3d8c 100644
--- a/pkg/meta/load_dump_test.go
+++ b/pkg/meta/load_dump_test.go
@@ -126,12 +126,12 @@ func testLoad(t *testing.T, uri, fname string) Meta {
 	if attr.Nlink != 1 || attr.Length != 24 {
 		t.Fatalf("nlink: %d, length: %d", attr.Nlink, attr.Length)
 	}
-	var chunks []Slice
-	if st := m.Read(ctx, 2, 0, &chunks); st != 0 {
+	var slices []Slice
+	if st := m.Read(ctx, 2, 0, &slices); st != 0 {
 		t.Fatalf("read chunk: %s", st)
 	}
-	if len(chunks) != 1 || chunks[0].Chunkid != 4 || chunks[0].Size != 24 {
-		t.Fatalf("chunks: %v", chunks)
+	if len(slices) != 1 || slices[0].Id != 4 || slices[0].Size != 24 {
+		t.Fatalf("slices: %v", slices)
 	}
 	if st := m.GetAttr(ctx, 4, attr); st != 0 || attr.Nlink != 2 { // hard link
 		t.Fatalf("getattr: %s, %d", st, attr.Nlink)
diff --git a/pkg/vfs/compact_test.go b/pkg/vfs/compact_test.go
index 2d6190f5..ca5e96f6 100644
--- a/pkg/vfs/compact_test.go
+++ b/pkg/vfs/compact_test.go
@@ -56,7 +56,7 @@ func TestCompact(t *testing.T) {
 		if e := w.Finish(len(buf)); e != nil {
 			t.Fatalf("flush chunk %d: %s", cid, e)
 		}
-		slices = append(slices, meta.Slice{Chunkid: cid, Size: uint32(len(buf)), Len: uint32(len(buf))})
+		slices = append(slices, meta.Slice{Id: cid, Size: uint32(len(buf)), Len: uint32(len(buf))})
 	}
 
 	// compact
diff --git a/pkg/vfs/fill_test.go b/pkg/vfs/fill_test.go
index cf5b849b..967f5769 100644
--- a/pkg/vfs/fill_test.go
+++ b/pkg/vfs/fill_test.go
@@ -42,7 +42,7 @@ func TestFill(t *testing.T) {
 	var slices []meta.Slice
 	_ = v.Meta.Read(meta.Background, fe.Inode, 0, &slices)
 	for _, s := range slices {
-		_ = v.Store.Remove(s.Chunkid, int(s.Size))
+		_ = v.Store.Remove(s.Id, int(s.Size))
 	}
 	// bad cases
 	v.fillCache(meta.Background, []string{"/test/file", "/sym2", "/sym3", "/.stats", "/not_exists"}, 2, nil, nil)