juicedata/juicefs · error

set total inodes: %s

Error message

set total inodes: %s

What it means

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.

Source

Thrown at pkg/meta/redis.go:852

func (m *redisMeta) updateStats(space int64, inodes int64) {
	atomic.AddInt64(&m.usedSpace, space)
	atomic.AddInt64(&m.usedInodes, inodes)
}

func (m *redisMeta) doSyncVolumeStat(ctx Context, used, inodes int64) error {
	if m.conf.ReadOnly {
		return syscall.EROFS
	}
	if err := m.doScanSustainedInodes(ctx, func(uid, gid uint32, length uint64) error {
		used += align4K(length)
		inodes++
		return nil
	}); err != nil {
		return err
	}
	logger.Debugf("Used space: %s, inodes: %d", humanize.IBytes(uint64(used)), inodes)
	if err := m.rdb.Set(ctx, m.totalInodesKey(), strconv.FormatInt(inodes, 10), 0).Err(); err != nil {
		return fmt.Errorf("set total inodes: %s", err)
	}
	return m.rdb.Set(ctx, m.usedSpaceKey(), strconv.FormatInt(used, 10), 0).Err()
}

func (m *redisMeta) doScanSustainedInodes(ctx Context, fn func(uid, gid uint32, length uint64) error) error {
	var inoKeys []string
	if err := m.scan(ctx, "session[0-9]*", func(keys []string) error {
		for i := 0; i < len(keys); i += 1 {
			key := keys[i]
			inodes, err := m.rdb.SMembers(ctx, key).Result()
			if err != nil {
				logger.Warnf("SMembers %s: %s", key, err)
				continue
			}
			for _, sinode := range inodes {
				ino, err := strconv.ParseInt(sinode, 10, 64)
				if err != nil {
					logger.Warnf("invalid sustained: %s->%s", key, sinode)

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Check Redis connectivity and health (redis-cli ping, INFO replication) and ensure the client points at a writable master, not a read-only replica.
  2. Inspect the wrapped error text after 'set total inodes:' — it contains the exact Redis error (OOM, READONLY, MOVED) and fix that root cause.
  3. If maxmemory/OOM, raise maxmemory or free memory in Redis.
  4. Retry the sync operation; the stat scan is idempotent and will recompute the same value.

Example fix

// before
if err := m.rdb.Set(ctx, m.totalInodesKey(), strconv.FormatInt(inodes, 10), 0).Err(); err != nil {
    return fmt.Errorf("set total inodes: %s", err)
}
// after (caller-side retry)
for i := 0; i < 3; i++ {
    err := syncStats(ctx) // runs doSyncVolumeStat
    if err == nil || !strings.Contains(err.Error(), "set total inodes") {
        break
    }
    time.Sleep(time.Second)
}
Defensive patterns

Strategy: retry

Validate before calling

if err := rdb.Ping(ctx).Err(); err != nil { return fmt.Errorf("redis unavailable: %w", err) }
if role, _ := rdb.Info(ctx, "replication").Result(); strings.Contains(role, "role:slave") { return errors.New("target is read-only replica") }

Try / catch

err := syncVolumeStat(ctx)
if err != nil && strings.Contains(err.Error(), "set total inodes") {
    // retry with backoff; wrapped redis error follows the colon
    var rErr error
    fmt.Sscanf(err.Error(), "set total inodes: %v", &rErr)
    retryWithBackoff(3, time.Second, func() error { return syncVolumeStat(ctx) })
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/3f9d67eee1973bc6. Report an issue: GitHub.