v2rayA/v2rayA · error

subscription count mismatch: BoltDB=%d, SQLite=%d

Error message

subscription count mismatch: BoltDB=%d, SQLite=%d

What it means

This error is thrown during the one-time BoltDB→SQLite migration when a post-migration verification step compares the number of subscription entries serialized inside the BoltDB 'subscriptions' JSON blob against the row count of the new SQLite 'subscriptions' table. A mismatch means some (or all) subscription records were not carried over, or extra rows exist, so the migration aborts rather than silently lose data.

Source

Thrown at service/db/migrate.go:556

			serversData := touchBkt.Get([]byte("servers"))
			if serversData != nil {
				boltServerCount := len(gjson.ParseBytes(serversData).Array())
				var sqlServerCount int
				sqldb.QueryRow("SELECT COUNT(*) FROM servers WHERE type = 'server'").Scan(&sqlServerCount)
				if boltServerCount != sqlServerCount {
					return fmt.Errorf("server count mismatch: BoltDB=%d, SQLite=%d", boltServerCount, sqlServerCount)
				}
				log.Info("Servers: %d entries verified", boltServerCount)
			}

			// Verify subscriptions
			subsData := touchBkt.Get([]byte("subscriptions"))
			if subsData != nil {
				boltSubCount := len(gjson.ParseBytes(subsData).Array())
				var sqlSubCount int
				sqldb.QueryRow("SELECT COUNT(*) FROM subscriptions").Scan(&sqlSubCount)
				if boltSubCount != sqlSubCount {
					return fmt.Errorf("subscription count mismatch: BoltDB=%d, SQLite=%d", boltSubCount, sqlSubCount)
				}
				log.Info("Subscriptions: %d entries verified", boltSubCount)
			}
		}

		return nil
	})

	if err != nil {
		return err
	}

	log.Warn("Migration verification passed!")
	return nil
}

View on GitHub (pinned to 71e5442fc5)

Solutions

  1. Inspect the SQLite table: SELECT COUNT(*) FROM subscriptions; and compare with the count of elements in the BoltDB 'subscriptions' JSON blob to see which side is stale.
  2. If a previous partial migration duplicated rows, start from a clean SQLite DB (delete/rename the SQLite database file) and re-run the migration from the original BoltDB file.
  3. If BoltDB is the stale side (extra/obsolete subscriptions), prune the BoltDB subscriptions blob or accept the current SQLite state and skip re-import by not pointing at the old BoltDB file.
  4. Check the migration/importer logs for per-subscription insert failures; fix the malformed subscription entries that were skipped, then re-migrate.
  5. Back up both database files before any of the above.

Example fix

// before: verifying against an already-populated SQLite DB
err := migrate.FromBoltDB(boltPath)
// after: re-run migration against a fresh SQLite DB
os.Rename("v2raya.db", "v2raya.db.bak") // move old SQLite DB aside
err := migrate.FromBoltDB(boltPath) // clean import, counts now match
Defensive patterns

Strategy: validation

Validate before calling

// Before migrating, snapshot and compare expected counts
var sqlCount int
if err := sqldb.QueryRow("SELECT COUNT(*) FROM subscriptions").Scan(&sqlCount); err != nil {
    return fmt.Errorf("count subscriptions: %w", err)
}
boltCount := len(gjson.GetBytes(subsBlob, "subscriptions").Array())
if boltCount != sqlCount {
    return fmt.Errorf("pre-check mismatch: BoltDB=%d, SQLite=%d — restore a clean SQLite DB before migrating", boltCount, sqlCount)
}

Type guard

func isCountMismatch(err error) bool {
    return err != nil && strings.Contains(err.Error(), "count mismatch")
}

Try / catch

// Go: detect the mismatch sentinel and offer recovery instead of crashing
if err := migrate.FromBoltDB(boltPath); err != nil {
    if isCountMismatch(err) {
        log.Warn("migration verification failed: %v — keeping backup at %s", err, backupPath)
        os.Rename(backupPath, dbPath) // roll back to pre-migration DB
    }
    return err
}

Prevention

When it happens

Trigger: Running the migration on a database where the BoltDB subscriptions JSON array contains a different number of elements than the SQLite subscriptions table has rows — e.g. a previous partial/failed migration left rows inserted, subscription rows were manually deleted/added after migration, or the importer skipped subscriptions that failed to parse.

Common situations: Re-running a migration after an earlier interrupted run; v2rayA upgrade where an old BoltDB file is imported into a SQLite DB that already has subscription rows; corrupt or hand-edited subscription JSON in BoltDB; DB file copied/restored from backups of mixed versions.

Related errors


AI-assisted analysis of v2rayA/v2rayA@71e5442fc5 (2026-09-05). Data as JSON: /api/errors/6b4d3ca3cb8a01ea. Report an issue: GitHub.