{"record":{"id":"3f9d67eee1973bc6","repo":"juicedata/juicefs","slug":"set-total-inodes-s","errorCode":null,"errorMessage":"set total inodes: %s","messagePattern":"set total inodes: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/meta/redis.go","lineNumber":852,"sourceCode":"func (m *redisMeta) updateStats(space int64, inodes int64) {\n\tatomic.AddInt64(&m.usedSpace, space)\n\tatomic.AddInt64(&m.usedInodes, inodes)\n}\n\nfunc (m *redisMeta) doSyncVolumeStat(ctx Context, used, inodes int64) error {\n\tif m.conf.ReadOnly {\n\t\treturn syscall.EROFS\n\t}\n\tif err := m.doScanSustainedInodes(ctx, func(uid, gid uint32, length uint64) error {\n\t\tused += align4K(length)\n\t\tinodes++\n\t\treturn nil\n\t}); err != nil {\n\t\treturn err\n\t}\n\tlogger.Debugf(\"Used space: %s, inodes: %d\", humanize.IBytes(uint64(used)), inodes)\n\tif err := m.rdb.Set(ctx, m.totalInodesKey(), strconv.FormatInt(inodes, 10), 0).Err(); err != nil {\n\t\treturn fmt.Errorf(\"set total inodes: %s\", err)\n\t}\n\treturn m.rdb.Set(ctx, m.usedSpaceKey(), strconv.FormatInt(used, 10), 0).Err()\n}\n\nfunc (m *redisMeta) doScanSustainedInodes(ctx Context, fn func(uid, gid uint32, length uint64) error) error {\n\tvar inoKeys []string\n\tif err := m.scan(ctx, \"session[0-9]*\", func(keys []string) error {\n\t\tfor i := 0; i < len(keys); i += 1 {\n\t\t\tkey := keys[i]\n\t\t\tinodes, err := m.rdb.SMembers(ctx, key).Result()\n\t\t\tif err != nil {\n\t\t\t\tlogger.Warnf(\"SMembers %s: %s\", key, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, sinode := range inodes {\n\t\t\t\tino, err := strconv.ParseInt(sinode, 10, 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.Warnf(\"invalid sustained: %s->%s\", key, sinode)","sourceCodeStart":834,"sourceCodeEnd":870,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/meta/redis.go#L834-L870","documentation":"doSyncVolumeStat recomputes the volume-wide inode count by scanning the inode keyspace and then persists the result into the Redis key `totalInodes` (m.totalInodesKey()). This error wraps a Redis SET failure on that write, so the reported inode total in the volume stats was not saved even though the scan succeeded. It is a Redis-side write failure (connection, cluster, read-only replica, memory, or encoding problem), not a metadata inconsistency.","triggerScenarios":"Any call path that runs doSyncVolumeStat (e.g. syncing usage stats after `juicefs gc`, stat maintenance, or `fsck`-style operations) where `m.rdb.Set(ctx, m.totalInodesKey(), ...)` returns an error from Redis.","commonSituations":"Redis connection drop mid-operation; writing to a read-only replica after failover; Redis OOM on maxmemory; cluster MOVED/ASK redirection not handled; Lua/keys command disallowed by ACL.","solutions":["Check Redis connectivity and health (redis-cli ping, INFO replication) and ensure the client points at a writable master, not a read-only replica.","Inspect the wrapped error text after 'set total inodes:' — it contains the exact Redis error (OOM, READONLY, MOVED) and fix that root cause.","If maxmemory/OOM, raise maxmemory or free memory in Redis.","Retry the sync operation; the stat scan is idempotent and will recompute the same value."],"exampleFix":"// before\nif err := m.rdb.Set(ctx, m.totalInodesKey(), strconv.FormatInt(inodes, 10), 0).Err(); err != nil {\n    return fmt.Errorf(\"set total inodes: %s\", err)\n}\n// after (caller-side retry)\nfor i := 0; i < 3; i++ {\n    err := syncStats(ctx) // runs doSyncVolumeStat\n    if err == nil || !strings.Contains(err.Error(), \"set total inodes\") {\n        break\n    }\n    time.Sleep(time.Second)\n}","handlingStrategy":"retry","validationCode":"if err := rdb.Ping(ctx).Err(); err != nil { return fmt.Errorf(\"redis unavailable: %w\", err) }\nif role, _ := rdb.Info(ctx, \"replication\").Result(); strings.Contains(role, \"role:slave\") { return errors.New(\"target is read-only replica\") }","typeGuard":null,"tryCatchPattern":"err := syncVolumeStat(ctx)\nif err != nil && strings.Contains(err.Error(), \"set total inodes\") {\n    // retry with backoff; wrapped redis error follows the colon\n    var rErr error\n    fmt.Sscanf(err.Error(), \"set total inodes: %v\", &rErr)\n    retryWithBackoff(3, time.Second, func() error { return syncVolumeStat(ctx) })\n}","preventionTips":["Monitor Redis health (INFO replication, maxmemory) before running gc/fsck-style jobs","Always point clients at masters, never read-only replicas","Use sentinel/cluster-aware client configuration for automatic failover"],"tags":["redis","metadata","write-failed","volume-stats"],"backgroundTag":"database-write-failed","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}